use std::ffi::{c_void, CString}; use std::process::abort; use cstr::cstr; use glfw::WindowSize; use crate::camera::Camera; use crate::color::Color; use crate::lighting::{LightSettings, LightSource}; use crate::object::Object; use crate::opengl::buffer::{ ArrayKind as ArrayBufferKind, Buffer, ElementArrayKind as ElementArrayBufferKind, Usage as BufferUsage, }; use crate::opengl::currently_bound::CurrentlyBound; #[cfg(feature = "debug")] use crate::opengl::debug::{MessageSeverity, MessageSource, MessageType}; use crate::opengl::shader::Program as ShaderProgram; use crate::opengl::vertex_array::{PrimitiveKind, VertexArray}; use crate::opengl::{clear_buffers, enable, BufferClearMask, Capability}; use crate::projection::new_perspective; use crate::vector::{Vec2, Vec3}; use crate::vertex::Vertex; #[derive(Debug)] pub struct Renderer { camera: Camera, } impl Renderer { pub fn new(window: &glfw::Window) -> Result { gl::load_with(|symbol| { let proc_name = unsafe { CString::from_vec_unchecked(symbol.into()) }; match window.get_proc_address(proc_name.as_c_str()) { Ok(addr) => addr as *const c_void, Err(err) => { println!( "FATAL ERROR: Failed to get adress of OpenGL function {}. {}", symbol, err ); abort(); } } }); #[cfg(feature = "debug")] Self::initialize_debug(); let window_size = window.size().map_err(Error::GetWindowSizeFailed)?; Self::set_viewport(&Vec2 { x: 0, y: 0 }, &window_size); enable(Capability::DepthTest); let camera = Camera::new(); Ok(Self { camera }) } pub fn render<'obj>( &self, objects: impl IntoIterator, light_source: Option<&LightSource>, light_settings: &LightSettings, window_size: &WindowSize, ) { clear_buffers(BufferClearMask::COLOR | BufferClearMask::DEPTH); for obj in objects { obj.renderable() .shader_program .activate(|shader_program_curr_bound| { apply_transformation_matrices( obj, &self.camera, window_size, &shader_program_curr_bound, ); apply_light( obj, light_source, &self.camera, &shader_program_curr_bound, light_settings, ); if let Some(texture) = obj.texture() { texture.inner().bind(|_| { Self::draw_object(obj); }); } else { Self::draw_object(obj); } }); } } pub fn set_viewport(position: &Vec2, size: &WindowSize) { crate::opengl::set_viewport(position, size); } #[must_use] pub fn camera(&self) -> &Camera { &self.camera } pub fn camera_mut(&mut self) -> &mut Camera { &mut self.camera } #[cfg(feature = "debug")] fn initialize_debug() { use crate::opengl::debug::{ enable_debug_output, set_debug_message_callback, set_debug_message_control, MessageIdsAction, }; enable_debug_output(); set_debug_message_callback(opengl_debug_message_cb); set_debug_message_control(None, None, None, &[], MessageIdsAction::Disable); } fn draw_object(obj: &Object) { obj.renderable().vertex_arr.bind(|vert_arr_curr_bound| { if let Some(index_info) = &obj.renderable().index_info { VertexArray::draw_elements( &vert_arr_curr_bound, PrimitiveKind::Triangles, 0, index_info.cnt, ); } else { VertexArray::draw_arrays( &vert_arr_curr_bound, PrimitiveKind::Triangles, 0, 3, ); } }); } } #[derive(Debug)] pub struct Renderable { shader_program: ShaderProgram, vertex_arr: VertexArray, /// Vertex and index buffer has to live as long as the vertex array _vertex_buffer: Buffer, index_info: Option, } impl Renderable { pub fn new( shader_program: ShaderProgram, vertices: &[Vertex], indices: Option<&[u32]>, ) -> Self { let vertex_arr = VertexArray::new(); let vertex_buffer = Buffer::new(); let mut index_info = None; vertex_arr.bind(|vert_arr_curr_bound| { vertex_buffer.bind(|vert_buf_curr_bound| { Buffer::store(&vert_buf_curr_bound, vertices, BufferUsage::Static); VertexArray::configure_attrs(&vert_arr_curr_bound, &vert_buf_curr_bound); }); if let Some(indices) = indices { let new_index_buffer = Buffer::new(); new_index_buffer.bind(|index_buf_curr_bound| { Buffer::store(&index_buf_curr_bound, indices, BufferUsage::Static); }); index_info = Some(IndexInfo { _buffer: new_index_buffer, cnt: indices.len().try_into().unwrap(), }); } }); Self { shader_program, vertex_arr, _vertex_buffer: vertex_buffer, index_info, } } } /// Renderer error. #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Failed to get window size")] GetWindowSizeFailed(#[source] glfw::Error), } fn apply_transformation_matrices( object: &Object, camera: &Camera, window_size: &WindowSize, shader_program_curr_bound: &CurrentlyBound, ) { object.renderable().shader_program.set_uniform_matrix_4fv( shader_program_curr_bound, cstr!("model"), &object.transform().as_matrix(), ); let view = camera.as_matrix(); object.renderable().shader_program.set_uniform_matrix_4fv( shader_program_curr_bound, cstr!("view"), &view, ); #[allow(clippy::cast_precision_loss)] let projection = new_perspective( 80.0f32.to_radians(), window_size.width as f32 / window_size.height as f32, 100.0, 0.1, ); object.renderable().shader_program.set_uniform_matrix_4fv( shader_program_curr_bound, cstr!("projection"), &projection, ); } fn apply_light( obj: &Object, light_source: Option<&LightSource>, camera: &Camera, shader_program_curr_bound: &CurrentlyBound, light_settings: &LightSettings, ) { obj.renderable().shader_program.set_uniform_vec_3fv( shader_program_curr_bound, cstr!("light_color"), &light_source .map_or(Color::WHITE_F32, |light_source| { light_source.color().clone() }) .into(), ); obj.renderable().shader_program.set_uniform_vec_3fv( shader_program_curr_bound, cstr!("light_pos"), &light_source.map_or_else(Vec3::default, |light_source| { light_source.position().clone() }), ); obj.renderable().shader_program.set_uniform_1fv( shader_program_curr_bound, cstr!("ambient_light_strength"), light_settings.ambient_light_strength, ); obj.renderable().shader_program.set_uniform_1fv( shader_program_curr_bound, cstr!("specular_light_strength"), light_settings.specular_light_strength, ); obj.renderable().shader_program.set_uniform_1uiv( shader_program_curr_bound, cstr!("specular_shininess"), light_settings.specular_shininess, ); obj.renderable().shader_program.set_uniform_vec_3fv( shader_program_curr_bound, cstr!("view_pos"), camera.position(), ); } #[cfg(feature = "debug")] #[tracing::instrument(skip_all)] fn opengl_debug_message_cb( source: MessageSource, ty: MessageType, id: u32, severity: MessageSeverity, message: &str, ) { use std::backtrace::{Backtrace, BacktraceStatus}; use tracing::{event, Level}; macro_rules! create_event { ($level: expr) => { event!($level, ?source, ?ty, id, ?severity, message); }; } match ty { MessageType::Error => { create_event!(Level::ERROR); let backtrace = Backtrace::capture(); if matches!(backtrace.status(), BacktraceStatus::Captured) { event!(Level::TRACE, "{backtrace}"); } } MessageType::Other => { create_event!(Level::INFO); } _ => { create_event!(Level::WARN); } }; } #[derive(Debug)] struct IndexInfo { _buffer: Buffer, cnt: u32, }