webgl_rc/
program.rs

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            // Arrays are not supported
140            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            // Arrays are not supported
175            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}