moon_engine/
shader.rs

1//! The [`Shader`] struct, and [`ShaderType`] enum.
2
3use std::fmt;
4use web_sys::WebGlProgram;
5use web_sys::WebGlShader;
6use web_sys::WebGlUniformLocation;
7
8use crate::{gl, GL};
9
10/// Type of Shader
11#[repr(u32)]
12pub enum ShaderType {
13    /// Vertex Shader.
14    VERTEX = GL::VERTEX_SHADER,
15    /// Fragment Shader.
16    FRAGMENT = GL::FRAGMENT_SHADER,
17}
18
19/// A program that is run on the GPU.
20///
21/// A [Shader] contains a [`Program`](WebGlProgram), that can be bound and run on the GPU using [WebGL](GL).
22#[derive(Debug)]
23pub struct Shader {
24    /// A name to refer to the shader with, and for debugging purposes.
25    pub name: &'static str,
26    program: Option<WebGlProgram>,
27}
28
29impl Default for Shader {
30    fn default() -> Self {
31        Self {
32            name: "Uninitialized Shader",
33            program: None,
34        }
35    }
36}
37
38impl fmt::Display for Shader {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        if self.program.is_some() {
41            write!(f, "{}", self.name)
42        } else {
43            write!(f, "{} (SHADER PROGRAM MISSING)", self.name)
44        }
45    }
46}
47
48impl gl::Bind for Shader {
49    /// Bind the `Shader`
50    fn bind(&self, gl: &GL) {
51        gl.use_program(self.program.as_ref());
52    }
53    fn unbind(&self, gl: &GL) {
54        gl.use_program(None);
55    }
56}
57
58impl Drop for Shader {
59    fn drop(&mut self) {
60        let gl = gl::get_context();
61        gl.delete_program(self.program.as_ref());
62    }
63}
64
65impl Shader {
66    /// Create a new Shader Program with default Vertex and Fragment shaders.
67    pub fn new(gl: &GL) -> Self {
68        let name = "Default Shader";
69        let vertex_shader =
70            Shader::create_vertex(gl, include_str!("../res/shader/default.vert.glsl"))
71                .expect("Could not create Vertex Shader!");
72
73        let fragment_shader =
74            Shader::create_fragment(gl, include_str!("../res/shader/default.frag.glsl"))
75                .expect("Could not create Fragment Shader!");
76
77        let program =
78            Shader::program_with_vertex_and_fragment(gl, &vertex_shader, &fragment_shader).ok();
79
80        Self { name, program }
81    }
82
83    /// Create a new Shader with default Fragment Shader and a custom Vertex Shader.
84    pub fn new_with_vertex(
85        gl: &GL,
86        vertex_shader: WebGlShader,
87        name: Option<&'static str>,
88    ) -> Self {
89        let name = name.unwrap_or("Custom Vertex Shader");
90
91        let fragment_shader =
92            Shader::create_fragment(gl, include_str!("../res/shader/default.frag.glsl"))
93                .expect("Could not create Fragment Shader!");
94
95        let program =
96            Shader::program_with_vertex_and_fragment(gl, &vertex_shader, &fragment_shader).ok();
97
98        Self { name, program }
99    }
100
101    /// Create a fragment `WebGlShader`.
102    pub fn create_fragment(gl: &GL, source: &str) -> Result<WebGlShader, String> {
103        Self::create_with_type(gl, ShaderType::FRAGMENT, source)
104    }
105
106    /// Create a vertex `WebGlShader`.
107    pub fn create_vertex(gl: &GL, source: &str) -> Result<WebGlShader, String> {
108        Self::create_with_type(gl, ShaderType::VERTEX, source)
109    }
110
111    /// Create a new `WebGlShader` with a given `ShaderType`.
112    pub fn create_with_type(
113        gl: &GL,
114        shader_type: ShaderType,
115        source: &str,
116    ) -> Result<WebGlShader, String> {
117        let shader = gl
118            .create_shader(shader_type as u32)
119            .ok_or_else(|| String::from("Unable to create Shader object."))?;
120        gl.shader_source(&shader, source);
121        gl.compile_shader(&shader);
122
123        if gl
124            .get_shader_parameter(&shader, GL::COMPILE_STATUS)
125            .as_bool()
126            .unwrap_or(false)
127        {
128            Ok(shader)
129        } else {
130            Err(gl
131                .get_shader_info_log(&shader)
132                .unwrap_or_else(|| String::from("Could not compile shader.")))
133        }
134    }
135
136    /// Create a new [`WebGlProgram`] with the given vertex and fragment [`shaders`](WebGlShader).
137    pub fn program_with_vertex_and_fragment(
138        gl: &GL,
139        vertex_shader: &WebGlShader,
140        fragment_shader: &WebGlShader,
141    ) -> Result<WebGlProgram, String> {
142        let program = gl
143            .create_program()
144            .ok_or_else(|| String::from("Unable to create Program object."))?;
145        gl.attach_shader(&program, vertex_shader);
146        gl.attach_shader(&program, fragment_shader);
147        gl.link_program(&program);
148
149        if gl
150            .get_program_parameter(&program, GL::LINK_STATUS)
151            .as_bool()
152            .unwrap_or(false)
153        {
154            gl.delete_shader(Some(vertex_shader));
155            gl.delete_shader(Some(fragment_shader));
156            Ok(program)
157        } else {
158            Err(gl
159                .get_program_info_log(&program)
160                .unwrap_or_else(|| String::from("Could not link program.")))
161        }
162    }
163
164    /// Get the location of a uniform on the `Shader`.
165    pub fn get_uniform_location(&self, gl: &GL, name: &str) -> Option<WebGlUniformLocation> {
166        self.program
167            .as_ref()
168            .map(|program| gl.get_uniform_location(program, name))
169            .unwrap()
170    }
171
172    /// Get the location of an attribute on the `Shader`.
173    pub fn get_attrib_location(&self, gl: &GL, name: &str) -> Option<i32> {
174        self.program
175            .as_ref()
176            .map(|program| gl.get_attrib_location(program, name))
177    }
178}