kiss3d/resource/
effect.rs

1use std::fs::File;
2use std::io::Read;
3use std::marker::PhantomData;
4use std::mem;
5use std::path::Path;
6use std::str;
7
8use crate::context::{Context, GLintptr, Program, Shader, UniformLocation};
9use crate::resource::{GLPrimitive, GPUVec};
10use crate::verify;
11
12/// Structure encapsulating a program.
13pub struct Effect {
14    program: Program,
15    vshader: Shader,
16    fshader: Shader,
17}
18
19impl Effect {
20    /// Creates a new shader program from two files containing the vertex and fragment shader.
21    pub fn new(vshader_path: &Path, fshader_path: &Path) -> Option<Effect> {
22        let mut vshader = String::new();
23        let mut fshader = String::new();
24
25        if File::open(vshader_path)
26            .map(|mut v| v.read_to_string(&mut vshader))
27            .is_err()
28        {
29            return None;
30        }
31
32        if File::open(fshader_path)
33            .map(|mut f| f.read_to_string(&mut fshader))
34            .is_err()
35        {
36            return None;
37        }
38
39        Some(Effect::new_from_str(&vshader[..], &fshader[..]))
40    }
41
42    /// Creates a new shader program from strings of the vertex and fragment shader.
43    pub fn new_from_str(vshader: &str, fshader: &str) -> Effect {
44        let (program, vshader, fshader) = load_shader_program(vshader, fshader);
45
46        Effect {
47            program,
48            vshader,
49            fshader,
50        }
51    }
52
53    /// Gets a uniform variable from the shader program.
54    pub fn get_uniform<T: GLPrimitive>(&self, name: &str) -> Option<ShaderUniform<T>> {
55        let ctxt = Context::get();
56        let location = ctxt.get_uniform_location(&self.program, name);
57
58        if ctxt.get_error() == 0 {
59            if let Some(id) = location {
60                let data_type = PhantomData;
61                return Some(ShaderUniform { id, data_type });
62            }
63        }
64
65        None
66    }
67
68    /// Gets an attribute from the shader program.
69    pub fn get_attrib<T: GLPrimitive>(&self, name: &str) -> Option<ShaderAttribute<T>> {
70        let ctxt = Context::get();
71        let location = ctxt.get_attrib_location(&self.program, name);
72
73        if ctxt.get_error() == 0 && location != -1 {
74            let id = location as u32;
75            let data_type = PhantomData;
76            return Some(ShaderAttribute { id, data_type });
77        }
78
79        None
80    }
81
82    /// Make this program active.
83    pub fn use_program(&mut self) {
84        verify!(Context::get().use_program(Some(&self.program)));
85    }
86}
87
88impl Drop for Effect {
89    fn drop(&mut self) {
90        let ctxt = Context::get();
91        if verify!(ctxt.is_program(Some(&self.program))) {
92            verify!(ctxt.delete_program(Some(&self.program)));
93        }
94        if verify!(ctxt.is_shader(Some(&self.fshader))) {
95            verify!(ctxt.delete_shader(Some(&self.fshader)));
96        }
97        if verify!(ctxt.is_shader(Some(&self.vshader))) {
98            verify!(ctxt.delete_shader(Some(&self.vshader)));
99        }
100    }
101}
102
103/// Structure encapsulating an uniform variable.
104pub struct ShaderUniform<T> {
105    id: UniformLocation,
106    data_type: PhantomData<T>,
107}
108
109impl<T: GLPrimitive> ShaderUniform<T> {
110    /// Upload a value to this variable.
111    pub fn upload(&mut self, value: &T) {
112        value.upload(&self.id)
113    }
114}
115
116/// Structure encapsulating an attribute.
117pub struct ShaderAttribute<T> {
118    id: u32,
119    data_type: PhantomData<T>,
120}
121
122impl<T: GLPrimitive> ShaderAttribute<T> {
123    /// Disable this attribute.
124    pub fn disable(&mut self) {
125        verify!(Context::get().disable_vertex_attrib_array(self.id));
126    }
127
128    /// Enable this attribute.
129    pub fn enable(&mut self) {
130        verify!(Context::get().enable_vertex_attrib_array(self.id));
131    }
132
133    /// The binding index.
134    pub fn id(&self) -> u32 {
135        self.id
136    }
137
138    /// Binds this attribute to a gpu vector.
139    pub fn bind(&mut self, vector: &mut GPUVec<T>) {
140        vector.bind();
141
142        verify!(Context::get().vertex_attrib_pointer(
143            self.id,
144            T::size() as i32,
145            T::GLTYPE,
146            false,
147            0,
148            0
149        ));
150    }
151
152    /// Binds this attribute to non contiguous parts of a gpu vector.
153    pub fn bind_sub_buffer(&mut self, vector: &mut GPUVec<T>, strides: usize, start_index: usize) {
154        unsafe { self.bind_sub_buffer_generic(vector, strides, start_index) }
155    }
156
157    /// Binds this attribute to non contiguous parts of a gpu vector.
158    ///
159    /// The type of the provided GPU buffer is not forced to match the type of this attribute.
160    #[allow(clippy::missing_safety_doc)]
161    pub unsafe fn bind_sub_buffer_generic<T2: GLPrimitive>(
162        &mut self,
163        vector: &mut GPUVec<T2>,
164        strides: usize,
165        start_index: usize,
166    ) {
167        vector.bind();
168
169        verify!(Context::get().vertex_attrib_pointer(
170            self.id,
171            T::size() as i32,
172            T::GLTYPE,
173            false,
174            ((strides + 1) * mem::size_of::<T2>()) as i32,
175            (start_index * mem::size_of::<T2>()) as GLintptr
176        ));
177    }
178}
179
180/// Loads a shader program using the given source codes for the vertex and fragment shader.
181///
182/// Fails after displaying opengl compilation errors if the shaders are invalid.
183fn load_shader_program(vertex_shader: &str, fragment_shader: &str) -> (Program, Shader, Shader) {
184    // Create and compile the vertex shader
185    let ctxt = Context::get();
186    let vshader = verify!(ctxt
187        .create_shader(Context::VERTEX_SHADER)
188        .expect("Could not create vertex shader."));
189
190    verify!(ctxt.shader_source(&vshader, vertex_shader));
191    verify!(ctxt.compile_shader(&vshader));
192    check_shader_error(&vshader);
193
194    // Create and compile the fragment shader
195    let fshader = verify!(ctxt
196        .create_shader(Context::FRAGMENT_SHADER)
197        .expect("Could not create fragment shader."));
198    verify!(ctxt.shader_source(&fshader, fragment_shader));
199    verify!(ctxt.compile_shader(&fshader));
200    check_shader_error(&fshader);
201
202    // Link the vertex and fragment shader into a shader program
203    let program = verify!(ctxt.create_program().expect("Could not create program."));
204    verify!(ctxt.attach_shader(&program, &vshader));
205    verify!(ctxt.attach_shader(&program, &fshader));
206    verify!(ctxt.link_program(&program));
207    (program, vshader, fshader)
208}
209
210/// Checks if a shader handle is valid.
211///
212/// If it is not valid, it fails with a descriptive error message.
213fn check_shader_error(shader: &Shader) {
214    let ctxt = Context::get();
215    let compiles = ctxt.get_shader_parameter_int(shader, Context::COMPILE_STATUS);
216
217    if compiles == Some(0) {
218        if let Some(log) = ctxt.get_shader_info_log(shader) {
219            panic!("Shader compilation failed: {}", log);
220        } else {
221            println!("Shader compilation failed.");
222        }
223    }
224}