1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// Internal dependencies
use crate::Error;

// External dependencies
use std::{ffi::CString, fs::File, ptr};
use cgmath::{Matrix, Matrix4};
use gl::types::GLint;
use std::io::Read;
use gl;


/// # General information
///
/// A shader instance stores the id representing a combination of a vertex and fragment shader compiled (OpenGL Shading Language)
/// and attached to an OpenGL program. Later on, an instance of such shader can be attached to an OpenGL context to use in conjunction with a series
/// of vertices to draw to screen.
///
/// # Fields
///
/// * `id` - An id field setup by OpenGL to uniquely identify shaders being passed.
///
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct Shader {
    pub(crate) id: u32,
}

impl Shader {
    /// # General information
    ///
    /// Creates a new shader program composed of both a vertex and a fragment shader. Since it uses `gl` crate, it's necessary that an openGL context has
    /// been initialized. Unsafe part stops rust from caching errors while compiling shaders, therefore print statements will be sent to the terminal containing
    /// a message in case an error has happened. Later use of faulty shaders will stop the program from running, but debbuging becomes hard since little
    /// information is provided by the `gl` crate. Should enable logging errors at a later date.
    /// **Regarding the steps the function uses**: first it opens and read files to strings. Then, shaders are casted to CStrings. After that, each shader is sent
    /// to be compiled and linked to a u32 variable. Finally, the u32 varaibles are linked to an OpenGL program with an id and cache is erased (compiled programs
    /// are already associated to a program, therefore can be safely erased). This last id is returned inside Shader structure.
    ///
    /// # Parameters
    ///
    /// * `vertex_path` - Path to a vertex shader file.
    /// * `fragment_path` - Path to a fragment shader file.
    ///
    pub fn new(
        vertex_path: impl AsRef<str>,
        fragment_path: impl AsRef<str>,
    ) -> Result<Self, Error> {
        // Opening files.
        let mut vertex_shader = File::open(vertex_path.as_ref()).map_err(|e| Error::Io(e))?;
        let mut fragment_shader = File::open(fragment_path.as_ref()).map_err(|e| Error::Io(e))?;

        // Reading files.
        let mut vertex_shader_read = String::new();
        let mut fragment_shader_read = String::new();
        vertex_shader
            .read_to_string(&mut vertex_shader_read)
            .map_err(|e| Error::Io(e))?;
        fragment_shader
            .read_to_string(&mut fragment_shader_read)
            .map_err(|e| Error::Io(e))?;

        // Casting shaders.
        let vertex_shader_read = CString::new(vertex_shader_read.as_bytes())
            .map_err(|e| Error::Custom(e.to_string()))?;
        let fragment_shader_read = CString::new(fragment_shader_read.as_bytes())
            .map_err(|e| Error::Custom(e.to_string()))?;

        // Compiling shaders with GLSL.
        // Vertex shader.
        let vertex_shader: u32;
        unsafe {
            vertex_shader = gl::CreateShader(gl::VERTEX_SHADER);
            gl::ShaderSource(vertex_shader, 1, &vertex_shader_read.as_ptr(), ptr::null());
            gl::CompileShader(vertex_shader);
            let mut success = gl::FALSE as GLint;
            gl::GetShaderiv(vertex_shader, gl::COMPILE_STATUS, &mut success);
            if success == gl::FALSE as GLint {
                // log does not serve
                return Err(Error::custom("Error while compiling vertex shader!"));
            }
        };
        // Fragment shader.
        let fragment_shader: u32;
        unsafe {
            fragment_shader = gl::CreateShader(gl::FRAGMENT_SHADER);
            gl::ShaderSource(
                fragment_shader,
                1,
                &fragment_shader_read.as_ptr(),
                ptr::null(),
            );
            gl::CompileShader(fragment_shader);
            let mut success = gl::FALSE as GLint;
            gl::GetShaderiv(fragment_shader, gl::COMPILE_STATUS, &mut success);
            if success == gl::FALSE as GLint {
                // log does not serve
                return Err(Error::custom("Error while compiling fragment shader!"));
            }
        }

        // Linkage to OpenGL program.
        let id: u32;
        unsafe {
            id = gl::CreateProgram();
            gl::AttachShader(id, vertex_shader);
            gl::AttachShader(id, fragment_shader);
            gl::LinkProgram(id);
            let mut success = gl::FALSE as GLint;
            gl::GetProgramiv(id, gl::LINK_STATUS, &mut success);
            if success == gl::FALSE as GLint {
                return Err(Error::custom("Error while linking program shader!"));
            }
            gl::DeleteShader(vertex_shader);
            gl::DeleteShader(fragment_shader);
        };

        Ok(Shader { id })
    }

    /// Use a certain pair of shaders identified by id. Program can have multiple shaders at once, but only one can be used at a time.
    pub fn use_shader(&self) {
        unsafe {
            gl::UseProgram(self.id);
        }
    }

    /// Send a 4x4 matrix variable to vertex shader. Matrix variable has to be declared as a uniform in shader and it's name must be known for this to work.
    pub fn set_mat4(&self, opengl_variable_name: &str, mat4_value: &Matrix4<f32>) -> Result<(),Error> {
        let c_str_name = CString::new(opengl_variable_name.as_bytes())?;
        unsafe {
            gl::UniformMatrix4fv(
                gl::GetUniformLocation(self.id, c_str_name.as_ptr()),
                1,
                gl::FALSE,
                mat4_value.as_ptr(),
            );
        }
        Ok(())
    }
}