1use crate::ElementsBuffer;
2use core::convert::TryFrom;
3use num_enum::{IntoPrimitive, TryFromPrimitive};
4use std::convert::TryInto;
5use std::rc::Rc;
6use web_sys::{WebGlProgram, WebGlRenderingContext, WebGlShader, WebGlUniformLocation};
7
8use super::data_buffer::{Item, ItemsBuffer};
9use super::gl::Gl;
10use super::gl::GlError;
11use super::settings::Settings;
12use super::texture::{Texture, TEXTURES_COUNT};
13use super::types::DataType;
14use crate::uniforms::{UniformValue, Uniforms};
15
16#[repr(u32)]
17#[derive(Clone, Copy, Debug, TryFromPrimitive, IntoPrimitive, PartialEq, Eq)]
18pub enum PrimitiveType {
19 Points = WebGlRenderingContext::POINTS,
20 LineStrip = WebGlRenderingContext::LINE_STRIP,
21 LineLoop = WebGlRenderingContext::LINE_LOOP,
22 Lines = WebGlRenderingContext::LINES,
23 TriangleStrip = WebGlRenderingContext::TRIANGLE_STRIP,
24 TriangleFan = WebGlRenderingContext::TRIANGLE_FAN,
25 Triangles = WebGlRenderingContext::TRIANGLES,
26}
27
28#[derive(Clone, Debug)]
29struct AttributeInfo {
30 name: String,
31 location: u32,
32 data_type: DataType,
33}
34
35#[derive(Clone, Debug)]
36struct UniformInfo {
37 name: String,
38 location: WebGlUniformLocation,
39 data_type: DataType,
40}
41
42#[derive(Clone, Debug)]
43struct Shader {
44 gl: Gl,
45 handle: WebGlShader,
46 source: String,
47}
48
49impl PartialEq for Shader {
50 fn eq(&self, other: &Shader) -> bool {
51 self.handle == other.handle
52 }
53}
54
55impl Drop for Shader {
56 fn drop(&mut self) {
57 self.gl.context().delete_shader(Some(&self.handle));
58 }
59}
60
61impl Shader {
62 fn new(gl: Gl, source: &str, shader_type: u32) -> Result<Shader, GlError> {
63 let ctx = gl.context();
64 let handle = ctx
65 .create_shader(shader_type)
66 .ok_or_else(|| GlError::UnknownError(None))?;
67
68 ctx.shader_source(&handle, source);
69 ctx.compile_shader(&handle);
70
71 let status = ctx
72 .get_shader_parameter(&handle, WebGlRenderingContext::COMPILE_STATUS)
73 .as_bool()
74 .ok_or_else(|| GlError::UnknownError(None))?;
75
76 if !status {
77 return Err(GlError::ShaderCompilationError {
78 source: source.into(),
79 info: ctx.get_shader_info_log(&handle),
80 });
81 }
82
83 return Ok(Shader {
84 gl,
85 handle,
86 source: source.into(),
87 });
88 }
89}
90
91#[derive(Debug, Clone)]
92struct ProgramData {
93 gl: Gl,
94 handle: WebGlProgram,
95 vertex_shader: Shader,
96 fragment_shader: Shader,
97 attributes: Vec<AttributeInfo>,
98 uniforms: Vec<UniformInfo>,
99}
100
101impl Drop for ProgramData {
102 fn drop(&mut self) {
103 let wgl = self.gl.context();
104 wgl.delete_program(Some(&self.handle));
105 }
106}
107
108#[derive(Debug, Clone)]
109pub struct Program {
110 data: Rc<ProgramData>,
111}
112
113impl PartialEq for Program {
114 fn eq(&self, other: &Program) -> bool {
115 self.data.handle == other.data.handle
116 }
117}
118
119impl Eq for Program {}
120
121impl Program {
122 fn collect_attributes(
123 ctx: &WebGlRenderingContext,
124 program: &WebGlProgram,
125 ) -> Result<Vec<AttributeInfo>, GlError> {
126 let attributes_count = ctx
127 .get_program_parameter(&program, WebGlRenderingContext::ACTIVE_ATTRIBUTES)
128 .as_f64()
129 .ok_or_else(|| GlError::UnknownError(Some("Failed to get attributes count".into())))?
130 as u32;
131
132 let mut result = Vec::with_capacity(attributes_count as usize);
133
134 for i in 0..attributes_count {
135 let info = ctx.get_active_attrib(&program, i).ok_or_else(|| {
136 GlError::UnknownError(Some("Failed to get attribute info".into()))
137 })?;
138
139 if info.size() != 1 {
141 return Err(GlError::UnsupportedType(Some(info.name())));
142 }
143
144 let location = ctx.get_attrib_location(&program, &info.name());
145 result.push(AttributeInfo {
146 name: info.name(),
147 data_type: DataType::try_from(info.type_())
148 .map_err(|_| GlError::UnsupportedType(Some(info.name())))?,
149 location: location.try_into().map_err(|_| {
150 GlError::UnknownError(Some("Negative attribute location".to_string()))
151 })?,
152 });
153 }
154 return Ok(result);
155 }
156
157 fn collect_uniforms(
158 ctx: &WebGlRenderingContext,
159 program: &WebGlProgram,
160 ) -> Result<Vec<UniformInfo>, GlError> {
161 let uniforms_count = ctx
162 .get_program_parameter(&program, WebGlRenderingContext::ACTIVE_UNIFORMS)
163 .as_f64()
164 .ok_or_else(|| GlError::UnknownError(Some("Failed to get uniforms count".into())))?
165 as u32;
166
167 let mut result = Vec::with_capacity(uniforms_count as usize);
168
169 for i in 0..uniforms_count {
170 let info = ctx
171 .get_active_uniform(&program, i)
172 .ok_or_else(|| GlError::UnknownError(Some("Failed to get uniform info".into())))?;
173
174 if info.size() != 1 {
176 return Err(GlError::UnsupportedType(Some(info.name())));
177 }
178
179 let location = ctx
180 .get_uniform_location(&program, &info.name())
181 .ok_or_else(|| {
182 GlError::UnknownError(Some("Failed to get uniform location".into()))
183 })?;
184 result.push(UniformInfo {
185 name: info.name(),
186 data_type: DataType::try_from(info.type_())
187 .map_err(|_| GlError::UnsupportedType(Some(info.name())))?,
188 location,
189 });
190 }
191
192 return Ok(result);
193 }
194
195 pub(crate) fn new(
196 gl: Gl,
197 fragment_shader_source: &str,
198 vertex_shader_source: &str,
199 ) -> Result<Self, GlError> {
200 let ctx: &WebGlRenderingContext = gl.context();
201
202 let vertex_shader = Shader::new(
203 gl.clone(),
204 vertex_shader_source,
205 WebGlRenderingContext::VERTEX_SHADER,
206 )?;
207 let fragment_shader = Shader::new(
208 gl.clone(),
209 fragment_shader_source,
210 WebGlRenderingContext::FRAGMENT_SHADER,
211 )?;
212
213 let program = ctx.create_program().unwrap();
214 ctx.attach_shader(&program, &vertex_shader.handle);
215 ctx.attach_shader(&program, &fragment_shader.handle);
216 ctx.link_program(&program);
217
218 let link_status = ctx
219 .get_program_parameter(&program, WebGlRenderingContext::LINK_STATUS)
220 .as_bool()
221 .ok_or_else(|| GlError::UnknownError(Some("Failed to get linking status".into())))?;
222
223 if !link_status {
224 return Err(GlError::ProgramLinkingError {
225 vertex: vertex_shader_source.into(),
226 fragment: fragment_shader_source.into(),
227 info: ctx.get_program_info_log(&program),
228 });
229 }
230
231 return Ok(Program {
232 data: Rc::new(ProgramData {
233 gl: gl.clone(),
234 handle: program.clone(),
235 vertex_shader,
236 fragment_shader,
237 attributes: Program::collect_attributes(&ctx, &program)?,
238 uniforms: Program::collect_uniforms(&ctx, &program)?,
239 }),
240 });
241 }
242
243 pub(crate) fn handle(&self) -> WebGlProgram {
244 self.data.handle.clone()
245 }
246
247 pub(self) fn set_attributes<T: Item>(&self, buffer: &ItemsBuffer<T>, divisor: u32) {
248 let gl: &WebGlRenderingContext = self.data.gl.context();
249 let instanced = self.data.gl.instanced_arrays();
250 let mut offset: usize = 0;
251
252 self.data.gl.apply(
253 Gl::settings()
254 .items_buffer((*buffer).clone())
255 .program(self.clone()),
256 || {
257 for item in T::layout() {
258 (&self.data.attributes)
259 .iter()
260 .find(|i| i.name == item.name)
261 .map(|info| {
262 gl.vertex_attrib_pointer_with_i32(
263 info.location,
264 item.data_type.size_in_floats().unwrap().try_into().unwrap(),
265 WebGlRenderingContext::FLOAT,
266 false,
267 (T::stride() * 4).try_into().unwrap(),
268 (offset * 4).try_into().unwrap(),
269 );
270 instanced.vertex_attrib_divisor_angle(info.location, divisor)
271 });
272 offset += item.data_type.size_in_floats().unwrap();
273 }
274 },
275 );
276 }
277
278 pub(self) fn enable_attributes<R, F: FnOnce() -> R>(&self, callback: F) -> R {
279 let attributes: Vec<u32> = (&self.data.attributes).iter().map(|v| v.location).collect();
280 self.data
281 .gl
282 .apply(Gl::settings().enabled_attributes(&attributes), callback)
283 }
284
285 pub(self) fn set_uniforms<R, F: FnOnce() -> R>(
286 &self,
287 uniforms: &impl Uniforms,
288 callback: F,
289 ) -> R {
290 let items = uniforms.uniforms();
291 let info = &self.data.uniforms;
292 let gl = &self.data.gl;
293 let context: &WebGlRenderingContext = gl.context();
294
295 let mut textures: Vec<Texture> = Vec::with_capacity(TEXTURES_COUNT.try_into().unwrap());
296
297 gl.apply(Gl::settings().program(self.clone()), || {
298 for i in items.iter() {
299 info.iter().find(|info| info.name == i.name).map(|info| {
300 let location = Some(&info.location);
301 match &i.value {
302 UniformValue::None => match info.data_type {
303 DataType::Boolean => context.uniform1i(location, 0),
304 DataType::Float => context.uniform1f(location, 0.0),
305 DataType::Vec2 => {
306 context.uniform2f(location, 0.0, 0.0);
307 }
308 DataType::Vec3 => {
309 context.uniform3f(location, 0.0, 0.0, 0.0);
310 }
311 DataType::Vec4 => {
312 context.uniform4f(location, 0.0, 0.0, 0.0, 0.0);
313 }
314 DataType::Mat2 => {
315 let mat = [0.0; 4];
316 context.uniform_matrix2fv_with_f32_array(location, false, &mat)
317 }
318 DataType::Mat3 => {
319 let mat = [0.0; 9];
320 context.uniform_matrix3fv_with_f32_array(location, false, &mat)
321 }
322 DataType::Mat4 => {
323 let mat = [0.0; 16];
324 context.uniform_matrix4fv_with_f32_array(location, false, &mat)
325 }
326 DataType::Sampler => {
327 context.uniform1i(location, -1);
328 }
329 },
330 UniformValue::Boolean(value) => {
331 context.uniform1i(location, if *value { 1 } else { 0 })
332 }
333 UniformValue::Float(value) => context.uniform1f(location, *value),
334 UniformValue::Vec2(value) => {
335 context.uniform2fv_with_f32_array(location, value)
336 }
337 UniformValue::Vec3(value) => {
338 context.uniform3fv_with_f32_array(location, value)
339 }
340 UniformValue::Vec4(value) => {
341 context.uniform4fv_with_f32_array(location, value)
342 }
343 UniformValue::Mat2(value) => {
344 context.uniform_matrix2fv_with_f32_array(location, false, value)
345 }
346 UniformValue::Mat3(value) => {
347 context.uniform_matrix3fv_with_f32_array(location, false, value)
348 }
349 UniformValue::Mat4(value) => {
350 context.uniform_matrix4fv_with_f32_array(location, false, value)
351 }
352 UniformValue::Texture(value) => {
353 context.uniform1i(location, textures.len().try_into().unwrap());
354 textures.push(value.clone())
355 }
356 }
357 });
358 }
359 });
360
361 gl.apply(Gl::settings().texture_list(textures), callback)
362 }
363
364 pub fn draw_arrays<T: Item, U: Uniforms>(
365 &self,
366 primitive_type: PrimitiveType,
367 uniforms: &U,
368 attributes: &ItemsBuffer<T>,
369 ) {
370 let gl = &self.data.gl;
371 gl.apply(Gl::settings().program(self.clone()), || {
372 self.enable_attributes(|| {
373 self.set_uniforms(uniforms, || {
374 self.set_attributes(attributes, 0);
375 gl.context().draw_arrays(
376 primitive_type.into(),
377 0,
378 attributes.len().try_into().unwrap(),
379 )
380 });
381 });
382 });
383 }
384
385 pub fn draw_instances<T: Item, I: Item, U: Uniforms>(
386 &self,
387 primitive_type: PrimitiveType,
388 uniforms: &U,
389 attributes: &ItemsBuffer<T>,
390 instances: &ItemsBuffer<I>,
391 ) {
392 let gl = &self.data.gl;
393 gl.apply(Gl::settings().program(self.clone()), || {
394 self.enable_attributes(|| {
395 self.set_uniforms(uniforms, || {
396 self.set_attributes(attributes, 0);
397 self.set_attributes(instances, 1);
398 gl.instanced_arrays().draw_arrays_instanced_angle(
399 primitive_type.into(),
400 0,
401 attributes.len().try_into().unwrap(),
402 instances.len().try_into().unwrap(),
403 );
404 });
405 });
406 });
407 }
408
409 pub fn draw_element_arrays<T: Item, U: Uniforms>(
410 &self,
411 primitive_type: PrimitiveType,
412 uniforms: &U,
413 attributes: &ItemsBuffer<T>,
414 elements: &ElementsBuffer,
415 ) {
416 let gl = &self.data.gl;
417 gl.apply(
418 Gl::settings()
419 .program(self.clone())
420 .element_buffer(elements.clone()),
421 || {
422 self.enable_attributes(|| {
423 self.set_uniforms(uniforms, || {
424 self.set_attributes(attributes, 0);
425 gl.context().draw_elements_with_i32(
426 primitive_type.into(),
427 elements.len() as i32,
428 WebGlRenderingContext::UNSIGNED_INT,
429 0,
430 )
431 });
432 });
433 },
434 );
435 }
436
437 pub fn draw_element_instances<T: Item, I: Item, U: Uniforms>(
438 &self,
439 primitive_type: PrimitiveType,
440 uniforms: &U,
441 attributes: &ItemsBuffer<T>,
442 elements: &ElementsBuffer,
443 instances: &ItemsBuffer<I>,
444 ) {
445 let gl = &self.data.gl;
446 gl.apply(
447 Gl::settings()
448 .program(self.clone())
449 .element_buffer(elements.clone()),
450 || {
451 self.enable_attributes(|| {
452 self.set_uniforms(uniforms, || {
453 self.set_attributes(attributes, 0);
454 self.set_attributes(instances, 1);
455 gl.instanced_arrays()
456 .draw_elements_instanced_angle_with_i32(
457 primitive_type.into(),
458 elements.len() as i32,
459 WebGlRenderingContext::UNSIGNED_INT,
460 0,
461 instances.len() as i32,
462 );
463 });
464 });
465 },
466 );
467 }
468
469 pub fn vertex_source(&self) -> &String {
470 &self.data.vertex_shader.source
471 }
472
473 pub fn fragment_source(&self) -> &String {
474 &self.data.fragment_shader.source
475 }
476}