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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
use std::ffi::{c_void, CStr};
use std::ptr;
use memoffset::offset_of;

use crate::material::{Material, AttributeType, set_attribute};

// The user defined filters in which the OpenGL callback will use to ignore certain debug messages.
static mut DEBUG_FILTERS: [DebugFilter; 4] = [DebugFilter::None, DebugFilter::None, DebugFilter::None, DebugFilter::None];

// The renderer takes in an array of DebugFilters to filter out one or more
// specific types of messages from the OpenGL callbacks.
#[derive(Copy, Clone)]
pub enum DebugFilter {
    None,
    Info,
    Low,
    Medium,
    High
}

// In the init process, one of the parameters is a `FaceCulling` enum
// to give the user more control
pub enum FaceCulling {
    None,
    Front,
    Back,
    FrontAndBack,
}

// Used for readability instead of using just `4`
//
// Should NOT be exposed to the user.
static FOUR_BYTES: usize = 4;

// Data-Oriented struct containing all of the OpenGL buffers to be used per object.
// VAO (Vertex Array Object): Contains the VertexAttribPointer data when being drawn.
// VBO (Vertex Buffer Object): Contains the position, color, etc. data.
// IBO (Index Buffer Object): Contains the indices (the order in which the vertices are rendered).
// Index Size: The length of the IBO array data.
//
// Should NOT be exposed to the user.
struct Buffers {
    pub vao: u32,
    pub vbo: u32,
    pub ibo: u32,
    pub index_size: i32,
}

// Public types used for the `Vertex` struct.
//
// Should be exposed to the user.
pub type Position = (f32, f32, f32);
pub type Color = (f32, f32, f32, f32);
pub type TexCoords = (f32, f32);
pub type Normals = (f32, f32, f32);
pub type TextureID = f32;

// Used for the `Vertex` struct to get the amount of floats in the entire struct
static VERTEX_DATA_SIZE: isize = 13;

// Public struct exposed to the user that allows for the creation of objects.
#[repr(C, packed)]
#[derive(Copy, Clone)]
pub struct Vertex {
    pub position: Position,
    pub color: Color,
    pub tex_coords: TexCoords,
    pub normals: Normals,
    pub texture_id: TextureID,
}

// Data-Oriented struct that controls what happens with each created object and renders them.
//
// Should be exposed to the user.
pub struct Renderer {
    buffers: Vec<Buffers>,
    materials: Vec<Material>,
    attribute_queue: Vec<Vec<(String, AttributeType)>>,
}

#[allow(unused_assignments)]
impl Renderer {
    // Loads the GL functions, therefore requiring a context to load their proc address
    pub fn new<F>(mut address: F, multisample: bool, depth_test: bool, cull_face: FaceCulling) -> Self
        where F: FnMut(&'static str) -> *const c_void {
        gl::load_with(|symbol| address(symbol));
        unsafe {
            gl::Enable(gl::DEBUG_OUTPUT);
            gl::Enable(gl::DEBUG_OUTPUT_SYNCHRONOUS);
            gl::DebugMessageCallback(Some(message_callback), 0 as *const c_void);

            if multisample { gl::Enable(gl::MULTISAMPLE) }
            if depth_test { gl::Enable(gl::DEPTH_TEST) }
            match cull_face {
                FaceCulling::Front => {
                    gl::Enable(gl::CULL_FACE);
                    gl::CullFace(gl::FRONT);
                },
                FaceCulling::Back => {
                    gl::Enable(gl::CULL_FACE);
                    gl::CullFace(gl::BACK);
                },
                FaceCulling::FrontAndBack => {
                    gl::Enable(gl::CULL_FACE);
                    gl::CullFace(gl::FRONT_AND_BACK);
                }
                _ => {}
            }
        }


        Renderer {
            buffers: vec![],
            materials: vec![],
            attribute_queue: vec![],
        }
    }

    pub fn set_debug_filters(&self, filters: Vec<DebugFilter>) {
        if filters.len() > 4 { panic!("Cannot have a debug filter count greater than 4!") }

        for i in 0..filters.len() {
            unsafe { DEBUG_FILTERS[i] = filters[i].clone(); }
        }
    }

    // Creates an object with the given vertex count, positions, colors, indices, and material.
    // The Object Manager will draw the object when given the chance using the `render` function.
    pub fn create_object(&mut self,
        vertices: Option<Vec<Vertex>>,
        indices: Option<Vec<u32>>,
        material: Material) -> u32
    {

        let (vao, vbo, ibo, index_size) = unsafe {
            let mut vao = 0u32;
            gl::GenVertexArrays(1, &mut vao);
            gl::BindVertexArray(vao);

            let mut vbo = 0u32;
            match vertices {
                Some(vertex_data) => {
                    let mut buffer_data: Vec<f32> = vec![];
                    for vertex in vertex_data.iter() {
                        buffer_data.extend(vec![
                            vertex.position.0,
                            vertex.position.1,
                            vertex.position.2,
                            vertex.color.0,
                            vertex.color.1,
                            vertex.color.2,
                            vertex.color.3,
                            vertex.tex_coords.0,
                            vertex.tex_coords.1,
                            vertex.normals.0,
                            vertex.normals.1,
                            vertex.normals.2,
                            vertex.texture_id
                        ]);
                    }

                    gl::GenBuffers(1, &mut vbo);
                    gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
                    gl::BufferData(
                        gl::ARRAY_BUFFER,
                        (FOUR_BYTES * buffer_data.len()) as isize,
                        buffer_data.as_ptr() as *const c_void,
                        gl::DYNAMIC_DRAW,
                    );
                },
                None => {}
            }

            let stride: i32 = (VERTEX_DATA_SIZE * FOUR_BYTES as isize) as i32;
            Renderer::enable_vertex_attrib_ptr(0, 3, stride, offset_of!(Vertex, position));
            Renderer::enable_vertex_attrib_ptr(1, 4, stride, offset_of!(Vertex, color));
            Renderer::enable_vertex_attrib_ptr(2, 2, stride, offset_of!(Vertex, tex_coords));
            Renderer::enable_vertex_attrib_ptr(3, 3, stride, offset_of!(Vertex, normals));
            Renderer::enable_vertex_attrib_ptr(4, 1, stride, offset_of!(Vertex, texture_id));

            let index_size;
            let mut ibo = 0u32;
            match indices {
                Some(index_data) => {
                    gl::GenBuffers(1, &mut ibo);
                    gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ibo);
                    gl::BufferData(
                        gl::ELEMENT_ARRAY_BUFFER,
                        (FOUR_BYTES * index_data.len()) as isize,
                        index_data.as_ptr() as *const c_void,
                        gl::DYNAMIC_DRAW,
                    );

                    index_size = index_data.len() as i32;
                },
                None => { index_size = 0i32; }
            }

            (vao, vbo, ibo, index_size)
        };

        self.buffers.push(Buffers {
            vao,
            vbo,
            ibo,
            index_size,
        });

        self.materials.push(material);
        self.attribute_queue.push(vec![]);

        self.buffers.len() as u32 - 1u32
    }

    pub fn set_material_attribute(&mut self, object: u32, n: &str, t: AttributeType) {
        self.attribute_queue[object as usize].push((n.to_string(), t));
    }

    // Changes the vertex/index data of a given object.
    pub fn change_object(&mut self, object: u32, vertices: Option<Vec<Vertex>>, indices: Option<Vec<u32>>) {
        unsafe {
            match vertices {
                Some(vertices) => {
                    gl::BindBuffer(gl::ARRAY_BUFFER, self.buffers[object as usize].vbo);
                    gl::BufferSubData(gl::ARRAY_BUFFER, 0, vertices.len() as isize * FOUR_BYTES as isize * VERTEX_DATA_SIZE,
                                      vertices.as_ptr() as *const c_void);
                }
                None => {}
            }

            match indices {
                Some(indices) => {
                    gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.buffers[object as usize].ibo);
                    gl::BufferSubData(gl::ELEMENT_ARRAY_BUFFER, 0, indices.len() as isize * FOUR_BYTES as isize,
                                      indices.as_ptr() as * const c_void);

                    self.buffers[object as usize].index_size = indices.len() as i32;
                }
                None => {}
            }
        }
    }

    // Draws all created objects using their buffers
    pub fn render(&mut self, bg_color: (f32, f32, f32, f32)) {
        unsafe {
            gl::ClearColor(bg_color.0, bg_color.1, bg_color.2, bg_color.3);
            gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);

            let mut i = 0usize;
            for object in self.buffers.iter() {
                gl::BindVertexArray(object.vao);
                self.materials[i].use_material();

                for (attribute_name, attribute_type) in self.attribute_queue[i].iter() {
                    set_attribute(self.materials[i].get_program_id(), (*attribute_name).clone(), (*attribute_type).clone());
                }
                self.attribute_queue[i] = Vec::new();

                gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, object.ibo);
                gl::DrawElements(
                    gl::TRIANGLES,
                    object.index_size,
                    gl::UNSIGNED_INT,
                    ptr::null(),
                );

                i += 1;
            }
        }
    }

    pub fn resize_viewport(&mut self, x: i32, y: i32) {
        unsafe {
            gl::Viewport(0, 0, x, y);
        }
    }

    // This will destroy all buffers and free the occupied memory.
    pub fn terminate(&mut self) {
        let mut i = 0;
        for buffers in self.buffers.iter() {
            unsafe {
                gl::DeleteVertexArrays(1, &buffers.vao);
                gl::DeleteBuffers(1, &buffers.vbo);
                gl::DeleteBuffers(1, &buffers.ibo);

                self.materials[i].delete();
            }

            i += 1;
        }
    }

    pub fn enable_vertex_attrib_ptr(index: u32, size: i32, stride: i32, offset: usize) {
        unsafe {
            gl::EnableVertexAttribArray(index);
            gl::VertexAttribPointer(
                index,
                size,
                gl::FLOAT,
                gl::FALSE,
                stride,
                offset as *const c_void,
            );
        }
    }
}

extern "system" fn message_callback(_: u32, _:  u32, _: u32, severity: u32, _: i32, message: *const i8, _: *mut c_void) {
    let mut severity_str = String::new();
    let mut is_filtered = false;

    if severity == gl::DEBUG_SEVERITY_NOTIFICATION {
        unsafe {
            for filter in DEBUG_FILTERS.iter() {
                match filter {
                    DebugFilter::Info => { is_filtered = true; }
                    _ => {}
                }
            }
        }

        severity_str = "Notification".to_string()
    }
    if severity == gl::DEBUG_SEVERITY_LOW {
        unsafe {
            for filter in DEBUG_FILTERS.iter() {
                match filter {
                    DebugFilter::Low => { is_filtered = true; }
                    _ => {}
                }
            }
        }

        severity_str = "Low".to_string()
    }
    if severity == gl::DEBUG_SEVERITY_MEDIUM {
        unsafe {
            for filter in DEBUG_FILTERS.iter() {
                match filter {
                    DebugFilter::Medium => { is_filtered = true; }
                    _ => {}
                }
            }
        }

        severity_str = "Medium".to_string()
    }
    if severity == gl::DEBUG_SEVERITY_HIGH {
        unsafe {
            for filter in DEBUG_FILTERS.iter() {
                match filter {
                    DebugFilter::High => { is_filtered = true; }
                    _ => {}
                }
            }
        }

        severity_str = "High".to_string()
    }

    if is_filtered { return; }

    unsafe { println!("GL CALLBACK (severity: {}): {}", severity_str, CStr::from_ptr(message).to_str().unwrap()); }
}