use std::hint::cold_path; use opengl_bindings::buffer::{Buffer as GlBuffer, Usage as GlBufferUsage}; use opengl_bindings::vertex_array::{ AttributeFormat as GlVertexArrayAttributeFormat, BindVertexBufferError as GlVertexArrayBindVertexBufferError, DataType as GlVertexArrayDataType, VertexArray as GlVertexArray, VertexBufferSpec as GlVertexArrayVertexBufferSpec, }; use opengl_bindings::MaybeCurrentContextWithFns as GlCurrentContextWithFns; use crate::mesh::vertex_buffer::VertexAttrProperties as MeshVertexAttrProperties; use crate::mesh::{Mesh, VertexAttrType}; use crate::rendering::shader::{ VertexDescription as ShaderVertexDescription, VertexInputSemName, }; use crate::rendering::MeshUsage; const VERTEX_BUF_BINDING_INDEX: u32 = 0; #[derive(Debug)] pub struct GraphicsMesh { /// Vertex and index buffer has to live as long as the vertex array vertex_buffer: GlBuffer, vertex_attr_props: Vec, last_max_vertex_attr_index: u32, pub index_buffer: Option>, pub element_cnt: u32, pub vertex_arr: GlVertexArray, } impl GraphicsMesh { #[tracing::instrument(skip_all)] pub fn new( current_context: &GlCurrentContextWithFns, mesh: &Mesh, mesh_usage: MeshUsage, ) -> Result { let buffer_usage = mesh_usage_to_gl_buffer_usage(mesh_usage); let vertex_arr = GlVertexArray::new(current_context); let vertex_buffer = GlBuffer::new(current_context); vertex_buffer .store(current_context, mesh.vertex_buf().as_bytes(), buffer_usage) .map_err(Error::StoreVerticesFailed)?; if let Err(err) = vertex_arr.bind_vertex_buffer( current_context, VERTEX_BUF_BINDING_INDEX, &vertex_buffer, GlVertexArrayVertexBufferSpec { offset: 0, vertex_size: mesh.vertex_buf().vertex_size(), }, ) { match err { GlVertexArrayBindVertexBufferError::OffsetValueTooLarge { value: _, max_value: _, } => unreachable!(), GlVertexArrayBindVertexBufferError::VertexSizeValueTooLarge { value, max_value, } => { panic!( "Size of vertex ({}) is too large. Must be less than {max_value}", value ); } } } if let Some(indices) = mesh.indices() { let index_buffer = GlBuffer::new(current_context); index_buffer .store(current_context, indices, buffer_usage) .map_err(Error::StoreIndicesFailed)?; vertex_arr.bind_element_buffer(current_context, &index_buffer); return Ok(Self { vertex_buffer: vertex_buffer, vertex_attr_props: mesh.vertex_buf().vertex_attr_props().to_vec(), last_max_vertex_attr_index: 0, index_buffer: Some(index_buffer), element_cnt: indices .len() .try_into() .expect("Mesh index count does not fit into a 32-bit unsigned int"), vertex_arr, }); } Ok(Self { vertex_buffer: vertex_buffer, vertex_attr_props: mesh.vertex_buf().vertex_attr_props().to_vec(), last_max_vertex_attr_index: 0, index_buffer: None, element_cnt: mesh .vertex_buf() .len() .try_into() .expect("Mesh vertex count does not fit into a 32-bit unsigned int"), vertex_arr, }) } pub fn update( &mut self, current_context: &GlCurrentContextWithFns, mesh: &Mesh, mesh_usage: MeshUsage, ) -> Result<(), Error> { let buffer_usage = mesh_usage_to_gl_buffer_usage(mesh_usage); self.vertex_buffer .store(current_context, mesh.vertex_buf().as_bytes(), buffer_usage) .map_err(Error::StoreVerticesFailed)?; if let Some(indices) = mesh.indices() { let index_buffer = self .index_buffer .get_or_insert_with(|| GlBuffer::new(current_context)); index_buffer .store(current_context, indices, buffer_usage) .map_err(Error::StoreIndicesFailed)?; self.vertex_arr .bind_element_buffer(current_context, &index_buffer); self.element_cnt = indices .len() .try_into() .expect("Mesh index count does not fit into a 32-bit unsigned int"); return Ok(()); } self.element_cnt = mesh .vertex_buf() .len() .try_into() .expect("Mesh vertex count does not fit into a 32-bit unsigned int"); Ok(()) } pub fn update_vertex_attrs_for_shader( &mut self, curr_gl_ctx: &GlCurrentContextWithFns, shader_vertex_desc: &ShaderVertexDescription, ) -> Result<(), VertexAttrsUpdatingError> { for index in 0..self.last_max_vertex_attr_index { self.vertex_arr.disable_attrib(curr_gl_ctx, index); } let mut max_vertex_attr_index = 0u32; for vertex_input_desc in &*shader_vertex_desc.inputs { let Some(vertex_attr_props) = self.vertex_attr_props.iter().find(|vertex_attr_props| { vertex_input_desc .semantic_name .matches_vertex_label(&vertex_attr_props.label) }) else { cold_path(); return Err(VertexAttrsUpdatingError::MissingVertexAttr( vertex_input_desc.semantic_name.clone(), )); }; let attrib_index: u32 = vertex_input_desc.index.try_into().unwrap(); self.vertex_arr.enable_attrib(curr_gl_ctx, attrib_index); self.vertex_arr.set_attrib_format( curr_gl_ctx, attrib_index, match &vertex_attr_props.ty { VertexAttrType::Float32 => GlVertexArrayAttributeFormat { data_type: GlVertexArrayDataType::Float, count: 1, normalized: false, offset: vertex_attr_props.byte_offset.try_into().unwrap(), }, VertexAttrType::Float32Array { length } => { GlVertexArrayAttributeFormat { data_type: GlVertexArrayDataType::Float, count: (*length).try_into().unwrap(), normalized: false, offset: vertex_attr_props.byte_offset.try_into().unwrap(), } } }, ); self.vertex_arr.set_attrib_vertex_buf_binding( curr_gl_ctx, attrib_index, VERTEX_BUF_BINDING_INDEX, ); max_vertex_attr_index = max_vertex_attr_index.max(attrib_index); } self.last_max_vertex_attr_index = max_vertex_attr_index; Ok(()) } pub fn destroy(&self, curr_gl_ctx: &GlCurrentContextWithFns) { self.vertex_arr.delete(curr_gl_ctx); self.vertex_buffer.delete(curr_gl_ctx); if let Some(index_buffer) = &self.index_buffer { index_buffer.delete(curr_gl_ctx); } } } #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Failed to store vertices in vertex buffer")] StoreVerticesFailed(#[source] opengl_bindings::buffer::Error), #[error("Failed to store indices in index buffer")] StoreIndicesFailed(#[source] opengl_bindings::buffer::Error), } #[derive(Debug, thiserror::Error)] pub enum VertexAttrsUpdatingError { #[error( "Mesh is missing equivalent vertex attribute for shader's vertex input: {0}" )] MissingVertexAttr(VertexInputSemName), } fn mesh_usage_to_gl_buffer_usage(mesh_usage: MeshUsage) -> GlBufferUsage { match mesh_usage { MeshUsage::Stream => GlBufferUsage::Stream, MeshUsage::Static => GlBufferUsage::Static, MeshUsage::Dynamic => GlBufferUsage::Dynamic, } }