use glutin::prelude::GlDisplay; use crate::backend::opengl::{self, Uniform}; use crate::math::{ Mat4, Vec3 }; use crate::{geometry, backend::gpu::GpuMesh}; use crate::shaders::Shader; use std::ops::Deref; use std::ffi::{ CStr, CString }; /// Render state containing camera and lighting information pub struct RenderState { pub projection: Mat4, pub view: Mat4, pub model: Mat4, pub camera_pos: Vec3, pub light_pos: Vec3, pub light_color: Vec3, } impl RenderState { pub fn new(aspect_ratio: f32) -> Self { let camera_pos = Vec3([0.0, 2.0, 5.0]); Self { projection: Mat4::perspective(45.0_f32.to_radians(), aspect_ratio, 0.1, 100.0), view: Mat4::look_at( camera_pos, Vec3 ([0.0, 0.0, 0.0]), Vec3 ([0.0, 1.0, 0.0]), ), model: Mat4::identity(), camera_pos, light_pos: Vec3([3.0, 5.0, 3.0]), light_color: Vec3([1.0, 1.0, 1.0]), } } } fn get_gl_string(gl: &opengl::Gl, variant: gl::types::GLenum) -> Option<&'static CStr>{ unsafe { let s = gl.GetString(variant); (!s.is_null()).then(|| CStr::from_ptr(s.cast())) } } pub struct GlRenderer { gl: std::rc::Rc, } impl GlRenderer { pub fn new(gl_display: &D) -> Self { let gl = opengl::Gl::load_with(|symbol| { let symbol = CString::new(symbol).unwrap(); gl_display.get_proc_address(symbol.as_c_str()).cast() }); if let Some(renderer) = get_gl_string(&gl, gl::RENDERER) { println!("Running on {}", renderer.to_string_lossy()); } if let Some(version) = get_gl_string(&gl, gl::VERSION) { println!("OpenGL version {}", version.to_string_lossy()); } if let Some(shaders_version) = get_gl_string(&gl, gl::SHADING_LANGUAGE_VERSION) { println!("Shaders version on {}", shaders_version.to_string_lossy()); } // Enable depth testing for 3D rendering unsafe { gl.Enable(gl::DEPTH_TEST); gl.DepthFunc(gl::LESS); } Self { gl: std::rc::Rc::new(gl), } } pub fn upload_mesh(&self, mesh: &geometry::Mesh) -> GpuMesh { unsafe { let gl = &self.gl; let mut vao = std::mem::zeroed(); gl.GenVertexArrays(1, &mut vao); gl.BindVertexArray(vao); // Upload vertex data (position, normal, texcoord) let mut vbo = std::mem::zeroed(); gl.GenBuffers(1, &mut vbo); gl.BindBuffer(gl::ARRAY_BUFFER, vbo); // Vertex layout: [pos.x, pos.y, pos.z, norm.x, norm.y, norm.z, tex.x, tex.y] let vertex_data: Vec = mesh.vertices .iter() .flat_map(|v| [ v.position.x(), v.position.y(), v.position.z(), v.normal.x(), v.normal.y(), v.normal.z(), v.tex_coord.x(), v.tex_coord.y(), ]) .collect(); gl.BufferData( gl::ARRAY_BUFFER, (vertex_data.len() * std::mem::size_of::()) as gl::types::GLsizeiptr, vertex_data.as_ptr() as *const _, gl::STATIC_DRAW, ); let stride = 8 * std::mem::size_of::() as gl::types::GLsizei; // Position attribute (location = 0) gl.VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, stride, std::ptr::null()); gl.EnableVertexAttribArray(0); // Normal attribute (location = 1) gl.VertexAttribPointer(1, 3, gl::FLOAT, gl::FALSE, stride, (3 * std::mem::size_of::()) as *const _); gl.EnableVertexAttribArray(1); // Texcoord attribute (location = 2) gl.VertexAttribPointer(2, 2, gl::FLOAT, gl::FALSE, stride, (6 * std::mem::size_of::()) as *const _); gl.EnableVertexAttribArray(2); // Upload index data if available let (ebo, index_count) = if !mesh.indices.is_empty() { let mut ebo = std::mem::zeroed(); gl.GenBuffers(1, &mut ebo); gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo); gl.BufferData( gl::ELEMENT_ARRAY_BUFFER, (mesh.indices.len() * std::mem::size_of::()) as gl::types::GLsizeiptr, mesh.indices.as_ptr() as *const _, gl::STATIC_DRAW, ); (Some(ebo), Some(mesh.indices.len() as i32)) } else { (None, None) }; gl.BindVertexArray(0); GpuMesh { vao, vbo, ebo, vertex_count: mesh.vertices.len() as i32, index_count, } } } fn compile_shader(gl: &opengl::Gl, shader: gl::types::GLenum, source: &[u8]) -> gl::types::GLuint { unsafe { let shader = gl.CreateShader(shader); gl.ShaderSource(shader, 1, [source.as_ptr().cast()].as_ptr(), std::ptr::null()); gl.CompileShader(shader); shader } } pub fn create_shader(&self, shader_src: &opengl::GlShaderSource) -> Shader { unsafe { let vertex_shader = Self::compile_shader( &self.gl, gl::VERTEX_SHADER, shader_src.vertex_src.as_bytes() ); let fragment_shader = Self::compile_shader( &self.gl, gl::FRAGMENT_SHADER, shader_src.fragment_src.as_bytes() ); let program = self.gl.CreateProgram(); self.gl.AttachShader(program, vertex_shader); self.gl.AttachShader(program, fragment_shader); self.gl.LinkProgram(program); self.gl.DeleteShader(vertex_shader); self.gl.DeleteShader(fragment_shader); Shader(program) } } pub fn apply_uniform(&self, shader: Shader, uniform: Uniform) { unsafe { match uniform { Uniform::Float(name, value) => { let location = self.GetUniformLocation(shader.0, name.as_ptr()); self.Uniform1f(location, value); } Uniform::Int(name, value) => { let location = self.GetUniformLocation(shader.0, name.as_ptr()); self.Uniform1i(location, value); } Uniform::Vec3(name, value) => { let location = self.GetUniformLocation(shader.0, name.as_ptr()); self.Uniform3f(location, value[0], value[1], value[2]); } Uniform::Vec4(name, value) => { let location = self.GetUniformLocation(shader.0, name.as_ptr()); self.Uniform4f(location, value[0], value[1], value[2], value[3]); } Uniform::Mat4(name, value) => { let location = self.GetUniformLocation(shader.0, name.as_ptr()); self.UniformMatrix4fv(location, 1, gl::FALSE, value.as_ptr()); } Uniform::None => {} } } } pub fn resize(&self, width: i32, height: i32) { unsafe { self.gl.Viewport(0, 0, width, height); } } pub fn draw_mesh(&self, mesh: &GpuMesh) { unsafe { self.BindVertexArray(mesh.vao); if let (Some(ebo), Some(count)) = (mesh.ebo, mesh.index_count) { self.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo); self.DrawElements(gl::TRIANGLES, count, gl::UNSIGNED_INT, std::ptr::null()); } else { self.DrawArrays(gl::TRIANGLES, 0, mesh.vertex_count); } } } } impl Deref for GlRenderer { type Target = opengl::Gl; fn deref(&self) -> &Self::Target { &self.gl } }