Skip to main content

three_d/core/
program.rs

1use crate::core::*;
2use std::collections::HashMap;
3use std::sync::RwLock;
4
5///
6/// A shader program consisting of a programmable vertex shader followed by a programmable fragment shader.
7/// Functionality includes transferring per vertex data to the vertex shader (see the use_attribute functionality)
8/// and transferring uniform data to both shader stages (see the use_uniform and use_texture functionality)
9/// and execute the shader program (see the draw functionality).
10///
11pub struct Program {
12    context: Context,
13    id: crate::context::Program,
14    attributes: HashMap<String, u32>,
15    textures: RwLock<HashMap<String, u32>>,
16    uniforms: HashMap<String, crate::context::UniformLocation>,
17    uniform_blocks: RwLock<HashMap<String, (u32, u32)>>,
18}
19
20impl Program {
21    ///
22    /// Creates a new shader program from the given vertex and fragment glsl shader source.
23    ///
24    pub fn from_source(
25        context: &Context,
26        vertex_shader_source: &str,
27        fragment_shader_source: &str,
28    ) -> Result<Self, CoreError> {
29        unsafe {
30            let vert_shader = context
31                .create_shader(crate::context::VERTEX_SHADER)
32                .expect("Failed creating vertex shader");
33            let frag_shader = context
34                .create_shader(crate::context::FRAGMENT_SHADER)
35                .expect("Failed creating fragment shader");
36
37            let header: &str = if context.version().is_embedded {
38                "#version 300 es
39                    #ifdef GL_FRAGMENT_PRECISION_HIGH
40                        precision highp float;
41                        precision highp int;
42                        precision highp sampler2DArray;
43                        precision highp sampler3D;
44                    #else
45                        precision mediump float;
46                        precision mediump int;
47                        precision mediump sampler2DArray;
48                        precision mediump sampler3D;
49                    #endif\n"
50            } else {
51                "#version 330 core\n"
52            };
53            let vertex_shader_source = format!("{}{}", header, vertex_shader_source);
54            let fragment_shader_source = format!("{}{}", header, fragment_shader_source);
55
56            context.shader_source(vert_shader, &vertex_shader_source);
57            context.shader_source(frag_shader, &fragment_shader_source);
58            context.compile_shader(vert_shader);
59            context.compile_shader(frag_shader);
60
61            let id = context.create_program().expect("Failed creating program");
62            context.attach_shader(id, vert_shader);
63            context.attach_shader(id, frag_shader);
64            context.link_program(id);
65
66            if !context.get_program_link_status(id) {
67                let log = context.get_shader_info_log(vert_shader);
68                if !log.is_empty() {
69                    Err(shader_compilation_error(
70                        "vertex",
71                        log,
72                        vertex_shader_source,
73                    ))?;
74                }
75                let log = context.get_shader_info_log(frag_shader);
76                if !log.is_empty() {
77                    Err(shader_compilation_error(
78                        "fragment",
79                        log,
80                        fragment_shader_source,
81                    ))?;
82                }
83                let log = context.get_program_info_log(id);
84                if !log.is_empty() {
85                    Err(CoreError::ShaderLink(log))?;
86                }
87                Err(CoreError::ShaderCompilerError)?;
88            }
89
90            context.detach_shader(id, vert_shader);
91            context.detach_shader(id, frag_shader);
92            context.delete_shader(vert_shader);
93            context.delete_shader(frag_shader);
94
95            // Init vertex attributes
96            let num_attribs = context.get_active_attributes(id);
97            let mut attributes = HashMap::new();
98            for i in 0..num_attribs {
99                if let Some(crate::context::ActiveAttribute { name, .. }) = context
100                    .get_active_attribute(id, i)
101                    .filter(|a| !a.name.starts_with("gl_"))
102                {
103                    if let Some(location) = context.get_attrib_location(id, &name) {
104                        attributes.insert(name, location);
105                    }
106                }
107            }
108
109            // Init uniforms
110            let num_uniforms = context.get_active_uniforms(id);
111            let mut uniforms = HashMap::new();
112            for i in 0..num_uniforms {
113                if let Some(crate::context::ActiveUniform { name, .. }) = context
114                    .get_active_uniform(id, i)
115                    .filter(|a| !a.name.starts_with("gl_"))
116                {
117                    if let Some(location) = context.get_uniform_location(id, &name) {
118                        let name = name.split('[').next().unwrap().to_string();
119                        uniforms.insert(name, location);
120                    }
121                }
122            }
123
124            Ok(Program {
125                context: context.clone(),
126                id,
127                attributes,
128                uniforms,
129                uniform_blocks: RwLock::new(HashMap::new()),
130                textures: RwLock::new(HashMap::new()),
131            })
132        }
133    }
134
135    ///
136    /// Send the given uniform data to this shader program and associate it with the given named variable.
137    /// The glsl shader variable must be of type `uniform int` if the data is an integer, `uniform vec2` if it is of type [Vec2] etc.
138    /// The uniform variable is uniformly available across all processing of vertices and fragments.
139    ///
140    /// # Panic
141    /// Will panic if the uniform is not defined or not used in the shader code.
142    /// In the latter case the variable is removed by the shader compiler.
143    ///
144    pub fn use_uniform<T: UniformDataType>(&self, name: &str, data: T) {
145        let location = self.get_uniform_location(name);
146        T::send_uniform(&self.context, location, &[data]);
147        self.unuse_program();
148    }
149
150    ///
151    /// Calls [Self::use_uniform] if [Self::requires_uniform] returns true.
152    ///
153    pub fn use_uniform_if_required<T: UniformDataType>(&self, name: &str, data: T) {
154        if self.requires_uniform(name) {
155            self.use_uniform(name, data);
156        }
157    }
158
159    ///
160    /// Send the given array of uniform data to this shader program and associate it with the given named variable.
161    /// The glsl shader variable must be of same type and length as the data, so if the data is an array of three [Vec2], the variable must be `uniform vec2[3]`.
162    /// The uniform variable is uniformly available across all processing of vertices and fragments.
163    ///
164    /// # Panic
165    /// Will panic if the uniform is not defined in the shader code or not used.
166    /// In the latter case the variable is removed by the shader compiler.
167    ///
168    pub fn use_uniform_array<T: UniformDataType>(&self, name: &str, data: &[T]) {
169        let location = self.get_uniform_location(name);
170        T::send_uniform(&self.context, location, data);
171        self.unuse_program();
172    }
173
174    fn get_uniform_location(&self, name: &str) -> &crate::context::UniformLocation {
175        self.use_program();
176        self.uniforms.get(name).unwrap_or_else(|| {
177            panic!(
178                "the uniform {} is sent to the shader but not defined or never used",
179                name
180            )
181        })
182    }
183
184    ///
185    /// Use the given [Texture2D] in this shader program and associate it with the given named variable.
186    /// The glsl shader variable must be of type `uniform sampler2D` and can only be accessed in the fragment shader.
187    ///
188    /// # Panic
189    /// Will panic if the texture is not defined in the shader code or not used.
190    /// In the latter case the variable is removed by the shader compiler.
191    ///
192    pub fn use_texture(&self, name: &str, texture: &Texture2D) {
193        self.use_texture_internal(name);
194        texture.bind();
195    }
196
197    ///
198    /// Use the given [DepthTexture2D] in this shader program and associate it with the given named variable.
199    /// The glsl shader variable must be of type `uniform sampler2D` and can only be accessed in the fragment shader.
200    ///
201    /// # Panic
202    /// Will panic if the texture is not defined in the shader code or not used.
203    /// In the latter case the variable is removed by the shader compiler.
204    ///
205    pub fn use_depth_texture(&self, name: &str, texture: &DepthTexture2D) {
206        self.use_texture_internal(name);
207        texture.bind();
208    }
209
210    ///
211    /// Use the given texture array in this shader program and associate it with the given named variable.
212    /// The glsl shader variable must be of type `uniform sampler2DArray` and can only be accessed in the fragment shader.
213    ///
214    /// # Panic
215    /// Will panic if the texture is not defined in the shader code or not used.
216    /// In the latter case the variable is removed by the shader compiler.
217    ///
218    pub fn use_texture_array(&self, name: &str, texture: &Texture2DArray) {
219        self.use_texture_internal(name);
220        texture.bind();
221    }
222
223    ///
224    /// Use the given texture array in this shader program and associate it with the given named variable.
225    /// The glsl shader variable must be of type `uniform sampler2DArray` and can only be accessed in the fragment shader.
226    ///
227    /// # Panic
228    /// Will panic if the texture is not defined in the shader code or not used.
229    /// In the latter case the variable is removed by the shader compiler.
230    ///
231    pub fn use_depth_texture_array(&self, name: &str, texture: &DepthTexture2DArray) {
232        self.use_texture_internal(name);
233        texture.bind();
234    }
235
236    ///
237    /// Use the given texture cube map in this shader program and associate it with the given named variable.
238    /// The glsl shader variable must be of type `uniform samplerCube` and can only be accessed in the fragment shader.
239    ///
240    /// # Panic
241    /// Will panic if the texture is not defined in the shader code or not used.
242    /// In the latter case the variable is removed by the shader compiler.
243    ///
244    pub fn use_texture_cube(&self, name: &str, texture: &TextureCubeMap) {
245        self.use_texture_internal(name);
246        texture.bind();
247    }
248
249    ///
250    /// Use the given texture cube map in this shader program and associate it with the given named variable.
251    /// The glsl shader variable must be of type `uniform samplerCube` and can only be accessed in the fragment shader.
252    ///
253    /// # Panic
254    /// Will panic if the texture is not defined in the shader code or not used.
255    /// In the latter case the variable is removed by the shader compiler.
256    ///
257    pub fn use_depth_texture_cube(&self, name: &str, texture: &DepthTextureCubeMap) {
258        self.use_texture_internal(name);
259        texture.bind();
260    }
261
262    ///
263    /// Use the given 3D texture in this shader program and associate it with the given named variable.
264    /// The glsl shader variable must be of type `uniform sampler3D` and can only be accessed in the fragment shader.
265    ///
266    /// # Panic
267    /// Will panic if the texture is not defined in the shader code or not used.
268    /// In the latter case the variable is removed by the shader compiler.
269    ///
270    pub fn use_texture_3d(&self, name: &str, texture: &Texture3D) {
271        self.use_texture_internal(name);
272        texture.bind();
273    }
274
275    ///
276    /// Use this function if you want to use a texture which was created using low-level context calls and not using the functionality in the [texture] module.
277    /// This function is only needed in special cases for example if you have a special source of texture data.
278    ///
279    #[deprecated = "Instead, create normal textures, eg. Texture2D, using the new_unchecked() methods, eg. Texture2D::new_unchecked()"]
280    pub fn use_raw_texture(&self, name: &str, target: u32, id: crate::context::Texture) {
281        self.use_texture_internal(name);
282        unsafe {
283            self.context.bind_texture(target, Some(id));
284        }
285    }
286
287    fn use_texture_internal(&self, name: &str) -> u32 {
288        if !self.textures.read().unwrap().contains_key(name) {
289            let mut map = self.textures.write().unwrap();
290            let index = map.len() as u32;
291            map.insert(name.to_owned(), index);
292        };
293        let index = *self.textures.read().unwrap().get(name).unwrap();
294        self.use_uniform(name, index as i32);
295        unsafe {
296            self.context
297                .active_texture(crate::context::TEXTURE0 + index);
298        }
299        index
300    }
301
302    ///
303    /// Use the given [UniformBuffer] in this shader program and associate it with the given named variable.
304    ///
305    pub fn use_uniform_block(&self, name: &str, buffer: &UniformBuffer) {
306        if !self.uniform_blocks.read().unwrap().contains_key(name) {
307            let mut map = self.uniform_blocks.write().unwrap();
308            let location = unsafe {
309                self.context
310                    .get_uniform_block_index(self.id, name)
311                    .unwrap_or_else(|| panic!("the uniform block {} is sent to the shader but not defined or never used",
312                        name))
313            };
314            let index = map.len() as u32;
315            map.insert(name.to_owned(), (location, index));
316        };
317        let (location, index) = *self.uniform_blocks.read().unwrap().get(name).unwrap();
318        unsafe {
319            self.context.uniform_block_binding(self.id, location, index);
320            buffer.bind(index);
321            self.context
322                .bind_buffer(crate::context::UNIFORM_BUFFER, None);
323        }
324    }
325
326    ///
327    /// Uses the given [VertexBuffer] data in this shader program and associates it with the given named variable.
328    /// Each value in the buffer is used when rendering one vertex using the [Program::draw_arrays] or [Program::draw_elements] methods.
329    /// Therefore the buffer must contain the same number of values as the number of vertices specified in those draw calls.
330    ///
331    /// # Panic
332    /// Will panic if the attribute is not defined in the shader code or not used.
333    /// In the latter case the variable is removed by the shader compiler.
334    ///
335    pub fn use_vertex_attribute<T: BufferDataType>(&self, name: &str, buffer: &VertexBuffer<T>) {
336        if buffer.count() > 0 {
337            buffer.bind();
338            let loc = self.location(name);
339            unsafe {
340                self.context.bind_vertex_array(Some(self.context.vao));
341                self.context.enable_vertex_attrib_array(loc);
342                if !T::normalized()
343                    && (T::data_type() == crate::context::UNSIGNED_BYTE
344                        || T::data_type() == crate::context::BYTE
345                        || T::data_type() == crate::context::UNSIGNED_SHORT
346                        || T::data_type() == crate::context::SHORT
347                        || T::data_type() == crate::context::UNSIGNED_INT
348                        || T::data_type() == crate::context::INT)
349                {
350                    self.context.vertex_attrib_pointer_i32(
351                        loc,
352                        T::size() as i32,
353                        T::data_type(),
354                        0,
355                        0,
356                    );
357                } else {
358                    self.context.vertex_attrib_pointer_f32(
359                        loc,
360                        T::size() as i32,
361                        T::data_type(),
362                        T::normalized(),
363                        0,
364                        0,
365                    );
366                }
367                self.context.vertex_attrib_divisor(loc, 0);
368                self.context.bind_buffer(crate::context::ARRAY_BUFFER, None);
369            }
370            self.unuse_program();
371        }
372    }
373
374    ///
375    /// Uses the given [InstanceBuffer] data in this shader program and associates it with the given named variable.
376    /// Each value in the buffer is used when rendering one instance using the [Program::draw_arrays_instanced] or [Program::draw_elements_instanced] methods.
377    /// Therefore the buffer must contain the same number of values as the number of instances specified in those draw calls.
378    ///
379    /// # Panic
380    /// Will panic if the attribute is not defined in the shader code or not used.
381    /// In the latter case the variable is removed by the shader compiler.
382    ///
383    pub fn use_instance_attribute<T: BufferDataType>(
384        &self,
385        name: &str,
386        buffer: &InstanceBuffer<T>,
387    ) {
388        if buffer.count() > 0 {
389            buffer.bind();
390            let loc = self.location(name);
391            unsafe {
392                self.context.bind_vertex_array(Some(self.context.vao));
393                self.context.enable_vertex_attrib_array(loc);
394                if !T::normalized()
395                    && (T::data_type() == crate::context::UNSIGNED_BYTE
396                        || T::data_type() == crate::context::BYTE
397                        || T::data_type() == crate::context::UNSIGNED_SHORT
398                        || T::data_type() == crate::context::SHORT
399                        || T::data_type() == crate::context::UNSIGNED_INT
400                        || T::data_type() == crate::context::INT)
401                {
402                    self.context.vertex_attrib_pointer_i32(
403                        loc,
404                        T::size() as i32,
405                        T::data_type(),
406                        0,
407                        0,
408                    );
409                } else {
410                    self.context.vertex_attrib_pointer_f32(
411                        loc,
412                        T::size() as i32,
413                        T::data_type(),
414                        T::normalized(),
415                        0,
416                        0,
417                    );
418                }
419                self.context.vertex_attrib_divisor(loc, 1);
420                self.context.bind_buffer(crate::context::ARRAY_BUFFER, None);
421            }
422            self.unuse_program();
423        }
424    }
425
426    ///
427    /// Draws triangles with the given render states and viewport using this shader program.
428    /// The number of vertices to draw is defined by the `count` parameter.
429    /// Requires that all attributes and uniforms have been defined using the use_attribute and use_uniform methods.
430    /// Assumes that the data for the three vertices in a triangle is defined contiguous in each vertex buffer.
431    /// If you want to use an [ElementBuffer], see [Program::draw_elements].
432    ///
433    pub fn draw_arrays(&self, render_states: RenderStates, viewport: Viewport, count: u32) {
434        self.draw_with(render_states, viewport, move || unsafe {
435            self.context
436                .draw_arrays(crate::context::TRIANGLES, 0, count as i32);
437        })
438    }
439
440    ///
441    /// Same as [Program::draw_arrays] except it renders 'instance_count' instances of the same set of triangles.
442    /// Use the [Program::use_instance_attribute], method to send unique data for each instance to the shader.
443    ///
444    pub fn draw_arrays_instanced(
445        &self,
446        render_states: RenderStates,
447        viewport: Viewport,
448        count: u32,
449        instance_count: u32,
450    ) {
451        self.draw_with(render_states, viewport, move || unsafe {
452            self.context.draw_arrays_instanced(
453                crate::context::TRIANGLES,
454                0,
455                count as i32,
456                instance_count as i32,
457            );
458            self.context
459                .bind_buffer(crate::context::ELEMENT_ARRAY_BUFFER, None);
460        })
461    }
462
463    ///
464    /// Draws the triangles defined by the given [ElementBuffer] with the given render states and viewport using this shader program.
465    /// Requires that all attributes and uniforms have been defined using the use_attribute and use_uniform methods.
466    /// If you do not want to use an [ElementBuffer], see [Program::draw_arrays]. If you only want to draw a subset of the triangles in the given [ElementBuffer], see [Program::draw_subset_of_elements].
467    ///
468    pub fn draw_elements<T: ElementBufferDataType>(
469        &self,
470        render_states: RenderStates,
471        viewport: Viewport,
472        element_buffer: &ElementBuffer<T>,
473    ) {
474        self.draw_subset_of_elements(
475            render_states,
476            viewport,
477            element_buffer,
478            0,
479            element_buffer.count(),
480        )
481    }
482
483    ///
484    /// Draws a subset of the triangles defined by the given [ElementBuffer] with the given render states and viewport using this shader program.
485    /// The number of vertices to draw is defined by the `count` parameter.
486    /// Requires that all attributes and uniforms have been defined using the use_attribute and use_uniform methods.
487    /// If you do not want to use an [ElementBuffer], see [Program::draw_arrays].
488    ///
489    pub fn draw_subset_of_elements<T: ElementBufferDataType>(
490        &self,
491        render_states: RenderStates,
492        viewport: Viewport,
493        element_buffer: &ElementBuffer<T>,
494        first: u32,
495        count: u32,
496    ) {
497        self.draw_elements_with(render_states, viewport, element_buffer, move || unsafe {
498            self.context.draw_elements(
499                crate::context::TRIANGLES,
500                count as i32,
501                T::data_type(),
502                first as i32,
503            );
504        })
505    }
506
507    ///
508    /// Same as [Program::draw_elements] except it renders 'instance_count' instances of the same set of triangles.
509    /// Use the [Program::use_instance_attribute] method to send unique data for each instance to the shader.
510    ///
511    pub fn draw_elements_instanced<T: ElementBufferDataType>(
512        &self,
513        render_states: RenderStates,
514        viewport: Viewport,
515        element_buffer: &ElementBuffer<T>,
516        instance_count: u32,
517    ) {
518        self.draw_subset_of_elements_instanced(
519            render_states,
520            viewport,
521            element_buffer,
522            0,
523            element_buffer.count(),
524            instance_count,
525        )
526    }
527
528    ///
529    /// Same as [Program::draw_subset_of_elements] except it renders 'instance_count' instances of the same set of triangles.
530    /// Use the [Program::use_instance_attribute] method to send unique data for each instance to the shader.
531    ///
532    pub fn draw_subset_of_elements_instanced<T: ElementBufferDataType>(
533        &self,
534        render_states: RenderStates,
535        viewport: Viewport,
536        element_buffer: &ElementBuffer<T>,
537        first: u32,
538        count: u32,
539        instance_count: u32,
540    ) {
541        self.draw_elements_with(render_states, viewport, element_buffer, move || unsafe {
542            self.context.draw_elements_instanced(
543                crate::context::TRIANGLES,
544                count as i32,
545                T::data_type(),
546                first as i32,
547                instance_count as i32,
548            );
549        })
550    }
551
552    ///
553    /// Calls drawing callback `draw` after setting up rendering to use this shader program, cleaning up before return.
554    /// Requires that all attributes and uniforms have been defined using the [use_attribute] and [use_uniform] methods.
555    ///
556    pub fn draw_with(&self, render_states: RenderStates, viewport: Viewport, draw: impl FnOnce()) {
557        self.context.set_viewport(viewport);
558        self.context.set_render_states(render_states);
559        self.use_program();
560
561        draw();
562
563        unsafe {
564            for location in self.attributes.values() {
565                self.context.disable_vertex_attrib_array(*location);
566            }
567            self.context.bind_vertex_array(None);
568        }
569
570        self.unuse_program();
571
572        #[cfg(debug_assertions)]
573        self.context
574            .error_check()
575            .expect("Unexpected rendering error occured")
576    }
577
578    ///
579    /// Calls drawing callback `draw` after setting up rendering to use this shader program and element buffer `elements`, cleaning up before return.
580    /// Requires that all attributes and uniforms have been defined using the [use_attribute] and [use_uniform] methods.
581    ///
582    pub fn draw_elements_with<T: ElementBufferDataType>(
583        &self,
584        render_states: RenderStates,
585        viewport: Viewport,
586        element_buffer: &ElementBuffer<T>,
587        draw: impl FnOnce(),
588    ) {
589        self.draw_with(render_states, viewport, move || {
590            element_buffer.bind();
591            draw();
592            unsafe {
593                self.context
594                    .bind_buffer(crate::context::ELEMENT_ARRAY_BUFFER, None);
595            }
596        })
597    }
598
599    ///
600    /// Returns true if this program uses the uniform with the given name.
601    ///
602    pub fn requires_uniform(&self, name: &str) -> bool {
603        self.uniforms.contains_key(name)
604    }
605
606    ///
607    /// Returns true if this program uses the attribute with the given name.
608    ///
609    pub fn requires_attribute(&self, name: &str) -> bool {
610        self.attributes.contains_key(name)
611    }
612
613    fn location(&self, name: &str) -> u32 {
614        self.use_program();
615        *self.attributes.get(name).unwrap_or_else(|| {
616            panic!(
617                "the attribute {} is sent to the shader but not defined or never used",
618                name
619            )
620        })
621    }
622
623    fn use_program(&self) {
624        unsafe {
625            self.context.use_program(Some(self.id));
626        }
627    }
628
629    fn unuse_program(&self) {
630        unsafe {
631            self.context.use_program(None);
632        }
633    }
634}
635
636impl Drop for Program {
637    fn drop(&mut self) {
638        unsafe {
639            self.context.delete_program(self.id);
640        }
641    }
642}
643fn shader_compilation_error(typ: &str, log: String, source: String) -> CoreError {
644    let lines: Vec<String> = source
645        .lines()
646        .enumerate()
647        .map(|(index, l)| format!("{:0>3}: {}", index + 1, l))
648        .collect();
649    CoreError::ShaderCompilation(typ.to_string(), lines.join("\n"), log)
650}