diff options
| -rw-r--r-- | engine/res/default_shader.slang | 3 | ||||
| -rw-r--r-- | engine/src/mesh.rs | 83 | ||||
| -rw-r--r-- | engine/src/mesh/cube.rs | 14 | ||||
| -rw-r--r-- | engine/src/reflection.rs | 31 | ||||
| -rw-r--r-- | engine/src/renderer/opengl.rs | 85 | ||||
| -rw-r--r-- | engine/src/renderer/opengl/graphics_mesh.rs | 141 | ||||
| -rw-r--r-- | engine/src/renderer/opengl/vertex.rs | 65 | ||||
| -rw-r--r-- | engine/src/shader.rs | 378 |
8 files changed, 602 insertions, 198 deletions
diff --git a/engine/res/default_shader.slang b/engine/res/default_shader.slang index dd6a6a9..db318c8 100644 --- a/engine/res/default_shader.slang +++ b/engine/res/default_shader.slang @@ -237,8 +237,7 @@ struct Fragment [shader("vertex")] VertexStageOutput vertex_main( - Vertex vertex, - // uniform ConstantBuffer<Model3D> model_3d + Vertex vertex: VERTEX, ) { VertexStageOutput stage_output; diff --git a/engine/src/mesh.rs b/engine/src/mesh.rs index fb977af..f26c9c1 100644 --- a/engine/src/mesh.rs +++ b/engine/src/mesh.rs @@ -1,5 +1,8 @@ +use engine_macros::Reflection; +use zerocopy::{Immutable, IntoBytes}; + use crate::builder; -use crate::vector::{Vec2, Vec3}; +use crate::vector::Vec3; pub mod cube; @@ -44,21 +47,25 @@ impl Mesh /// Finds the vertex positions that are furthest in every 3D direction. Keep in mind /// that this can be quite time-expensive if the mesh has many vertices. - pub fn find_furthest_vertex_positions(&self) -> DirectionPositions<'_> + pub fn find_furthest_vertex_positions(&self) -> DirectionPositions { - let mut point_iter = self.vertices().iter().map(|vertex| &vertex.pos).into_iter(); + let mut point_iter = self + .vertices() + .iter() + .map(|vertex| Vec3::from(vertex.pos)) + .into_iter(); let first_point = point_iter.next().unwrap(); point_iter .fold( FurthestPosAcc { - up: FurthestPos::new(&first_point, &Vec3::UP), - down: FurthestPos::new(&first_point, &Vec3::DOWN), - left: FurthestPos::new(&first_point, &Vec3::LEFT), - right: FurthestPos::new(&first_point, &Vec3::RIGHT), - back: FurthestPos::new(&first_point, &Vec3::BACK), - front: FurthestPos::new(&first_point, &Vec3::FRONT), + up: FurthestPos::new(first_point, &Vec3::UP), + down: FurthestPos::new(first_point, &Vec3::DOWN), + left: FurthestPos::new(first_point, &Vec3::LEFT), + right: FurthestPos::new(first_point, &Vec3::RIGHT), + back: FurthestPos::new(first_point, &Vec3::BACK), + front: FurthestPos::new(first_point, &Vec3::FRONT), }, |mut furthest_pos_acc, pos| { furthest_pos_acc.up.update_if_further(pos); @@ -77,13 +84,18 @@ impl Mesh builder! { #[builder(name = VertexBuilder, derives = (Debug, Default, Clone))] -#[derive(Debug, Clone, Default, PartialEq)] +#[derive(Debug, Clone, Default, PartialEq, IntoBytes, Immutable, Reflection)] #[non_exhaustive] pub struct Vertex { - pub pos: Vec3<f32>, - pub texture_coords: Vec2<f32>, - pub normal: Vec3<f32>, + #[builder(skip_generate_fn)] + pub pos: [f32; 3], + + #[builder(skip_generate_fn)] + pub texture_coords: [f32; 2], + + #[builder(skip_generate_fn)] + pub normal: [f32; 3], } } @@ -96,18 +108,39 @@ impl Vertex } } +impl VertexBuilder +{ + pub fn pos(mut self, pos: impl Into<[f32; 3]>) -> Self + { + self.pos = pos.into(); + self + } + + pub fn texture_coords(mut self, texture_coords: impl Into<[f32; 2]>) -> Self + { + self.texture_coords = texture_coords.into(); + self + } + + pub fn normal(mut self, normal: impl Into<[f32; 3]>) -> Self + { + self.normal = normal.into(); + self + } +} + #[derive(Debug, Clone)] -pub struct DirectionPositions<'mesh> +pub struct DirectionPositions { - pub up: &'mesh Vec3<f32>, - pub down: &'mesh Vec3<f32>, - pub left: &'mesh Vec3<f32>, - pub right: &'mesh Vec3<f32>, - pub back: &'mesh Vec3<f32>, - pub front: &'mesh Vec3<f32>, + pub up: Vec3<f32>, + pub down: Vec3<f32>, + pub left: Vec3<f32>, + pub right: Vec3<f32>, + pub back: Vec3<f32>, + pub front: Vec3<f32>, } -impl<'mesh> From<FurthestPosAcc<'mesh>> for DirectionPositions<'mesh> +impl<'mesh> From<FurthestPosAcc<'mesh>> for DirectionPositions { fn from(acc: FurthestPosAcc<'mesh>) -> Self { @@ -136,14 +169,14 @@ struct FurthestPosAcc<'mesh> #[derive(Debug)] struct FurthestPos<'mesh> { - pos: &'mesh Vec3<f32>, + pos: Vec3<f32>, dot_prod: f32, direction: &'mesh Vec3<f32>, } impl<'mesh> FurthestPos<'mesh> { - fn new(pos: &'mesh Vec3<f32>, direction: &'mesh Vec3<f32>) -> Self + fn new(pos: Vec3<f32>, direction: &'mesh Vec3<f32>) -> Self { Self { pos, @@ -152,9 +185,9 @@ impl<'mesh> FurthestPos<'mesh> } } - fn update_if_further(&mut self, point: &'mesh Vec3<f32>) + fn update_if_further(&mut self, point: Vec3<f32>) { - let point_dot_prod = self.direction.dot(point); + let point_dot_prod = self.direction.dot(&point); if point_dot_prod > self.dot_prod { self.pos = point; diff --git a/engine/src/mesh/cube.rs b/engine/src/mesh/cube.rs index e91cf0e..fe45a4a 100644 --- a/engine/src/mesh/cube.rs +++ b/engine/src/mesh/cube.rs @@ -1,7 +1,7 @@ +use crate::builder; use crate::data_types::dimens::Dimens3; use crate::math::calc_triangle_surface_normal; use crate::mesh::{Mesh, Vertex}; -use crate::builder; use crate::vector::{Vec2, Vec3}; builder! { @@ -216,14 +216,14 @@ impl FaceVertices { match face_location { FaceLocation::RightUp => { - self.vertices[0].texture_coords = Vec2 { x: 1.0, y: 1.0 }; - self.vertices[1].texture_coords = Vec2 { x: 0.0, y: 1.0 }; - self.vertices[2].texture_coords = Vec2 { x: 1.0, y: 0.0 }; + self.vertices[0].texture_coords = Vec2 { x: 1.0, y: 1.0 }.into(); + self.vertices[1].texture_coords = Vec2 { x: 0.0, y: 1.0 }.into(); + self.vertices[2].texture_coords = Vec2 { x: 1.0, y: 0.0 }.into(); } FaceLocation::LeftDown => { - self.vertices[0].texture_coords = Vec2 { x: 0.0, y: 1.0 }; - self.vertices[1].texture_coords = Vec2 { x: 0.0, y: 0.0 }; - self.vertices[2].texture_coords = Vec2 { x: 1.0, y: 0.0 }; + self.vertices[0].texture_coords = Vec2 { x: 0.0, y: 1.0 }.into(); + self.vertices[1].texture_coords = Vec2 { x: 0.0, y: 0.0 }.into(); + self.vertices[2].texture_coords = Vec2 { x: 1.0, y: 0.0 }.into(); } }; diff --git a/engine/src/reflection.rs b/engine/src/reflection.rs index 5bd2aef..3ce424e 100644 --- a/engine/src/reflection.rs +++ b/engine/src/reflection.rs @@ -1,5 +1,5 @@ use std::alloc::Layout; -use std::any::TypeId; +use std::any::{TypeId, type_name}; pub use engine_macros::Reflection; @@ -14,14 +14,25 @@ pub trait With: 'static fn get_reflection(&self) -> &'static Reflection; } -#[derive(Debug, Clone)] +#[derive(Debug)] #[non_exhaustive] pub enum Reflection { Struct(Struct), Array(Array), Slice(Slice), - Literal, + Literal(Literal), +} + +impl Reflection +{ + pub const fn as_struct(&self) -> Option<&Struct> + { + match self { + Self::Struct(struct_reflection) => Some(struct_reflection), + _ => None, + } + } } #[derive(Debug, Clone)] @@ -55,12 +66,24 @@ pub struct Slice pub item_reflection: &'static Reflection, } +#[derive(Debug)] +pub struct Literal +{ + pub layout: Layout, + pub type_id: TypeId, + pub type_name: fn() -> &'static str, +} + macro_rules! impl_with_for_literals { ($($literal: ty),*) => { $( impl With for $literal { - const REFLECTION: &Reflection = &Reflection::Literal; + const REFLECTION: &Reflection = &Reflection::Literal(Literal { + layout: Layout::new::<$literal>(), + type_id: TypeId::of::<$literal>(), + type_name: || type_name::<$literal>() + }); fn reflection() -> &'static Reflection where diff --git a/engine/src/renderer/opengl.rs b/engine/src/renderer/opengl.rs index d31835a..2dd5467 100644 --- a/engine/src/renderer/opengl.rs +++ b/engine/src/renderer/opengl.rs @@ -91,6 +91,7 @@ use crate::renderer::{ }; use crate::shader::cursor::BindingValue as ShaderBindingValue; use crate::shader::{ + Context as ShaderContext, Error as ShaderError, Program as ShaderProgram, Stage as ShaderStage, @@ -111,7 +112,6 @@ use crate::windowing::window::{ mod glutin_compat; mod graphics_mesh; -mod vertex; declare_entity!( pub POST_RENDER_PHASE, @@ -137,7 +137,11 @@ struct GraphicsContext #[derive(Debug)] enum GraphicsContextObject { - Mesh(GraphicsMesh), + Mesh + { + mesh: GraphicsMesh, + compatible_shader_program_obj_id: RendererObjectId, + }, } #[derive(Debug, Default)] @@ -265,7 +269,10 @@ fn handle_model_removed( }; #[allow(irrefutable_let_patterns)] - let GraphicsContextObject::Mesh(mut graphics_mesh) = removed_graphics_ctx_obj + let GraphicsContextObject::Mesh { + mesh: mut graphics_mesh, + compatible_shader_program_obj_id: _, + } = removed_graphics_ctx_obj else { tracing::error!( model_entity_id=%model_ent_id, @@ -665,6 +672,7 @@ fn handle_commands( &mut RendererCommandQueue, )>, assets: Single<Assets>, + shader_context: Single<ShaderContext>, ) { for (mut graphics_ctx, mut renderer_object_store, mut command_queue) in @@ -680,7 +688,8 @@ fn handle_commands( let mut opt_curr_gl_ctx: Option<CurrentContextWithFns> = None; - let mut activated_gl_shader_program: Option<GlShaderProgram> = None; + let mut activated_gl_shader_program: Option<(RendererObjectId, GlShaderProgram)> = + None; for command in command_queue.drain() { let tracing_span = tracing::info_span!("handle_cmd"); @@ -794,7 +803,8 @@ fn handle_commands( gl_shader_program.activate(&curr_gl_ctx); - activated_gl_shader_program = Some(gl_shader_program); + activated_gl_shader_program = + Some((shader_program_obj_id, gl_shader_program)); } RendererCommand::SetShaderBinding(binding_location, binding_value) => { let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { @@ -918,18 +928,45 @@ fn handle_commands( continue; } - let key = *next_graphics_ctx_object_key; + let Some((RendererObjectId::Asset(curr_shader_program_asset_id), _)) = + &activated_gl_shader_program + else { + tracing::error!("No shader program is activated"); + continue; + }; - let graphics_mesh = match GraphicsMesh::new(&curr_gl_ctx, &mesh) { - Ok(graphics_mesh) => graphics_mesh, - Err(err) => { - tracing::error!("Failed to create mesh: {err}"); - continue; - } + let curr_shader_program_metadata = shader_context + .get_program_metadata(curr_shader_program_asset_id) + .expect("Not possible"); + + let Some(vertex_subset) = &curr_shader_program_metadata.vertex_subset + else { + tracing::error!( + "Current shader program does not have a vertex subset" + ); + continue; }; - graphics_ctx_objects - .insert(key, GraphicsContextObject::Mesh(graphics_mesh)); + let key = *next_graphics_ctx_object_key; + + let graphics_mesh = + match GraphicsMesh::new(&curr_gl_ctx, &mesh, &vertex_subset) { + Ok(graphics_mesh) => graphics_mesh, + Err(err) => { + tracing::error!("Failed to create mesh: {err}"); + continue; + } + }; + + graphics_ctx_objects.insert( + key, + GraphicsContextObject::Mesh { + mesh: graphics_mesh, + compatible_shader_program_obj_id: RendererObjectId::Asset( + *curr_shader_program_asset_id, + ), + }, + ); renderer_object_store.insert( mesh_object_id, @@ -970,8 +1007,10 @@ fn handle_commands( }; #[allow(irrefutable_let_patterns)] - let GraphicsContextObject::Mesh(graphics_mesh) = - mesh_graphics_ctx_obj + let GraphicsContextObject::Mesh { + mesh: graphics_mesh, + compatible_shader_program_obj_id, + } = mesh_graphics_ctx_obj else { tracing::error!( object_id=?mesh_object_id, @@ -981,6 +1020,20 @@ fn handle_commands( continue; }; + if Some(compatible_shader_program_obj_id) + != activated_gl_shader_program.as_ref().map( + |(activated_gl_shader_program_obj_id, _)| { + activated_gl_shader_program_obj_id + }, + ) + { + tracing::error!(concat!( + "Activated shader program is not the ", + "compatible shader program of the mesh" + )); + continue; + } + if let Err(err) = draw_mesh(&curr_gl_ctx, graphics_mesh) { tracing::error!("Failed to draw mesh: {err}"); }; diff --git a/engine/src/renderer/opengl/graphics_mesh.rs b/engine/src/renderer/opengl/graphics_mesh.rs index cb1c7fc..5d081c7 100644 --- a/engine/src/renderer/opengl/graphics_mesh.rs +++ b/engine/src/renderer/opengl/graphics_mesh.rs @@ -1,23 +1,25 @@ +use std::any::TypeId; + use opengl_bindings::CurrentContextWithFns as GlCurrentContextWithFns; 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 zerocopy::IntoBytes; use crate::mesh::Mesh; -use crate::renderer::opengl::vertex::{ - AttributeComponentType as VertexAttributeComponentType, - Vertex as RendererVertex, -}; +use crate::reflection::Reflection; +use crate::shader::VertexSubset as ShaderVertexSubset; #[derive(Debug)] pub struct GraphicsMesh { /// Vertex and index buffer has to live as long as the vertex array - vertex_buffer: GlBuffer<RendererVertex>, + vertex_buffer: GlBuffer<u8>, pub index_buffer: Option<GlBuffer<u32>>, pub element_cnt: u32, pub vertex_arr: GlVertexArray, @@ -29,66 +31,120 @@ impl GraphicsMesh pub fn new( current_context: &GlCurrentContextWithFns<'_>, mesh: &Mesh, + vertex_subset: &ShaderVertexSubset, ) -> Result<Self, Error> { - tracing::info!( - "Creating vertex array, vertex buffer{}", - if mesh.indices().is_some() { - " and index buffer" - } else { - "" - } - ); - let vertex_arr = GlVertexArray::new(current_context); let vertex_buffer = GlBuffer::new(current_context); vertex_buffer - .store_mapped( + .init( current_context, - mesh.vertices(), + vertex_subset.layout.size() * mesh.vertices().len(), GlBufferUsage::Static, - |vertex| RendererVertex { - pos: vertex.pos.into(), - texture_coords: vertex.texture_coords.into(), - normal: vertex.normal.into(), - }, ) - .map_err(Error::StoreVerticesFailed)?; + .map_err(Error::InitVertexBufferFailed)?; + + for (vertex_index, vertex) in mesh.vertices().iter().enumerate() { + let vertex_bytes = vertex.as_bytes(); + + for vertex_subset_field in vertex_subset.fields.iter().flatten() { + let vertex_buffer_offset = (vertex_subset.layout.size() * vertex_index) + + vertex_subset_field.offset; + + let vertex_field_start_offset = + vertex_subset_field.reflection.byte_offset; + + let vertex_field_size = vertex_subset_field.reflection.layout.size(); + + vertex_buffer + .store_at_byte_offset( + current_context, + vertex_buffer_offset, + &vertex_bytes[vertex_field_start_offset + ..vertex_field_start_offset + vertex_field_size], + ) + .map_err(Error::StoreVerticesFailed)?; + } + } - let _ = vertex_arr.bind_vertex_buffer( + let vertex_buf_binding_index = 0; + + if let Err(err) = vertex_arr.bind_vertex_buffer( current_context, - 0, + vertex_buf_binding_index, &vertex_buffer, GlVertexArrayVertexBufferSpec { offset: 0, - vertex_size: size_of::<RendererVertex>(), + vertex_size: vertex_subset.layout.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 + ); + } + } + } - let mut offset = 0u32; + for vertex_subset_field in vertex_subset.fields.iter().flatten() { + let attrib_index: u32 = + vertex_subset_field.varying_input_offset.try_into().unwrap(); - for attrib in RendererVertex::attrs() { - vertex_arr.enable_attrib(current_context, attrib.index); + vertex_arr.enable_attrib(current_context, attrib_index); vertex_arr.set_attrib_format( current_context, - attrib.index, - GlVertexArrayAttributeFormat { - data_type: match attrib.component_type { - VertexAttributeComponentType::Float => { - GlVertexArrayDataType::Float + attrib_index, + match vertex_subset_field.reflection.reflection { + Reflection::Literal(_) => { + if vertex_subset_field.reflection.type_id != TypeId::of::<f32>() { + panic!("Unsupported vertex field data type"); + } + + GlVertexArrayAttributeFormat { + data_type: GlVertexArrayDataType::Float, + count: 1, + normalized: false, + offset: vertex_subset_field.offset.try_into().unwrap(), + } + } + Reflection::Array(array_vertex_field) => { + let Reflection::Literal(array_vertex_field_item) = + array_vertex_field.item_reflection + else { + panic!("Unsupported array item type in vertex field"); + }; + + if array_vertex_field_item.type_id != TypeId::of::<f32>() { + panic!("Unsupported array item type in vertex field"); } - }, - count: attrib.component_cnt as u8, - normalized: false, - offset, + + GlVertexArrayAttributeFormat { + data_type: GlVertexArrayDataType::Float, + count: array_vertex_field.length.try_into().unwrap(), + normalized: false, + offset: vertex_subset_field.offset.try_into().unwrap(), + } + } + _ => panic!("Unsupported vertex field data type"), }, ); - vertex_arr.set_attrib_vertex_buf_binding(current_context, attrib.index, 0); - - offset += attrib.component_size * attrib.component_cnt as u32; + vertex_arr.set_attrib_vertex_buf_binding( + current_context, + attrib_index, + vertex_buf_binding_index, + ); } if let Some(indices) = mesh.indices() { @@ -137,6 +193,9 @@ impl GraphicsMesh #[derive(Debug, thiserror::Error)] pub enum Error { + #[error("Failed to initialize vertex buffer")] + InitVertexBufferFailed(#[source] opengl_bindings::buffer::Error), + #[error("Failed to store vertices in vertex buffer")] StoreVerticesFailed(#[source] opengl_bindings::buffer::Error), diff --git a/engine/src/renderer/opengl/vertex.rs b/engine/src/renderer/opengl/vertex.rs deleted file mode 100644 index 5a1593e..0000000 --- a/engine/src/renderer/opengl/vertex.rs +++ /dev/null @@ -1,65 +0,0 @@ -use safer_ffi::derive_ReprC; - -#[derive(Debug, Clone)] -#[derive_ReprC] -#[repr(C)] -pub struct Vertex -{ - pub pos: opengl_bindings::data_types::Vec3<f32>, - pub texture_coords: opengl_bindings::data_types::Vec2<f32>, - pub normal: opengl_bindings::data_types::Vec3<f32>, -} - -impl Vertex -{ - pub fn attrs() -> &'static [Attribute] - { - #[allow(clippy::cast_possible_truncation)] - &[ - Attribute { - index: 0, - component_type: AttributeComponentType::Float, - component_cnt: AttributeComponentCnt::Three, - component_size: size_of::<f32>() as u32, - }, - Attribute { - index: 1, - component_type: AttributeComponentType::Float, - component_cnt: AttributeComponentCnt::Two, - component_size: size_of::<f32>() as u32, - }, - Attribute { - index: 2, - component_type: AttributeComponentType::Float, - component_cnt: AttributeComponentCnt::Three, - component_size: size_of::<f32>() as u32, - }, - ] - } -} - -#[derive(Debug)] -pub struct Attribute -{ - pub index: u32, - pub component_type: AttributeComponentType, - pub component_cnt: AttributeComponentCnt, - pub component_size: u32, -} - -#[derive(Debug)] -pub enum AttributeComponentType -{ - Float, -} - -#[derive(Debug, Clone, Copy)] -#[repr(u32)] -#[allow(dead_code)] -pub enum AttributeComponentCnt -{ - One = 1, - Two = 2, - Three = 3, - Four = 4, -} diff --git a/engine/src/shader.rs b/engine/src/shader.rs index 65872f1..ae0be1d 100644 --- a/engine/src/shader.rs +++ b/engine/src/shader.rs @@ -1,3 +1,4 @@ +use std::alloc::Layout; use std::any::type_name; use std::borrow::Cow; use std::collections::HashMap; @@ -5,7 +6,7 @@ use std::fmt::Debug; use std::path::Path; use std::str::Utf8Error; -use bitflags::bitflags; +use bitflags::{bitflags, bitflags_match}; use ecs::pair::{ChildOf, Pair}; use ecs::phase::{Phase, START as START_PHASE}; use ecs::sole::Single; @@ -31,6 +32,12 @@ use crate::asset::{ Submitter as AssetSubmitter, }; use crate::builder; +use crate::mesh::Vertex; +use crate::reflection::{ + Struct as StructReflection, + StructField as StructFieldReflection, + With, +}; use crate::renderer::PRE_RENDER_PHASE; use crate::shader::default::{ ASSET_LABEL, @@ -40,6 +47,10 @@ use crate::shader::default::{ pub mod cursor; pub mod default; +/// The vertex parameter of a vertex entrypoint function in a shader should have this +/// semantic name. +pub const VERTEX_PARAM_SEMANTIC_NAME: &str = "VERTEX"; + #[derive(Debug, Clone, Component)] pub struct Shader { @@ -57,7 +68,7 @@ pub struct ModuleSource } bitflags! { - #[derive(Debug, Clone, Copy, Default)] + #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub struct EntrypointFlags: usize { const FRAGMENT = 1 << 0; @@ -65,6 +76,7 @@ bitflags! { } } +#[derive(Clone)] pub struct Module { inner: SlangModule, @@ -94,9 +106,24 @@ pub struct EntryPoint impl EntryPoint { - pub fn function_name(&self) -> Option<&str> + pub fn function(&self) -> FunctionReflection<'_> + { + FunctionReflection { + inner: self.inner.function_reflection(), + } + } +} + +pub struct FunctionReflection<'a> +{ + inner: &'a shader_slang::reflection::Function, +} + +impl<'a> FunctionReflection<'a> +{ + pub fn name(&self) -> Option<&str> { - self.inner.function_reflection().name() + self.inner.name() } } @@ -245,7 +272,7 @@ pub struct VariableLayout<'a> impl<'a> VariableLayout<'a> { - pub fn name(&self) -> Option<&str> + pub fn name(&self) -> Option<&'a str> { self.inner.name() } @@ -263,6 +290,19 @@ impl<'a> VariableLayout<'a> // self.inner.binding_index() } + pub fn varying_input_offset(&self) -> Option<usize> + { + if !self + .inner + .categories() + .any(|category| category == SlangParameterCategory::VaryingInput) + { + return None; + } + + Some(self.inner.offset(SlangParameterCategory::VaryingInput)) + } + pub fn binding_space(&self) -> u32 { self.inner.binding_space() @@ -297,6 +337,11 @@ pub struct TypeLayout<'a> impl<'a> TypeLayout<'a> { + pub fn kind(&self) -> TypeKind + { + TypeKind::from_slang_type_kind(self.inner.kind()) + } + pub fn get_field_by_name(&self, name: &str) -> Option<VariableLayout<'a>> { let index = self.inner.find_field_index_by_name(name); @@ -312,6 +357,11 @@ impl<'a> TypeLayout<'a> Some(VariableLayout { inner: field }) } + pub fn parameter_category(&self) -> ParameterCategory + { + ParameterCategory::from_slang_parameter_category(self.inner.parameter_category()) + } + pub fn binding_range_descriptor_set_index(&self, index: i64) -> i64 { self.inner.binding_range_descriptor_set_index(index) @@ -479,6 +529,80 @@ impl TypeKind } } +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum ParameterCategory +{ + None, + Mixed, + ConstantBuffer, + ShaderResource, + UnorderedAccess, + VaryingInput, + VaryingOutput, + SamplerState, + Uniform, + DescriptorTableSlot, + SpecializationConstant, + PushConstantBuffer, + RegisterSpace, + Generic, + RayPayload, + HitAttributes, + CallablePayload, + ShaderRecord, + ExistentialTypeParam, + ExistentialObjectParam, + SubElementRegisterSpace, + Subpass, + MetalArgumentBufferElement, + MetalAttribute, + MetalPayload, + Count, +} + +impl ParameterCategory +{ + fn from_slang_parameter_category(parameter_category: SlangParameterCategory) -> Self + { + match parameter_category { + SlangParameterCategory::None => Self::None, + SlangParameterCategory::Mixed => Self::Mixed, + SlangParameterCategory::ConstantBuffer => Self::ConstantBuffer, + SlangParameterCategory::ShaderResource => Self::ShaderResource, + SlangParameterCategory::UnorderedAccess => Self::UnorderedAccess, + SlangParameterCategory::VaryingInput => Self::VaryingInput, + SlangParameterCategory::VaryingOutput => Self::VaryingOutput, + SlangParameterCategory::SamplerState => Self::SamplerState, + SlangParameterCategory::Uniform => Self::Uniform, + SlangParameterCategory::DescriptorTableSlot => Self::DescriptorTableSlot, + SlangParameterCategory::SpecializationConstant => { + Self::SpecializationConstant + } + SlangParameterCategory::PushConstantBuffer => Self::PushConstantBuffer, + SlangParameterCategory::RegisterSpace => Self::RegisterSpace, + SlangParameterCategory::Generic => Self::Generic, + SlangParameterCategory::RayPayload => Self::RayPayload, + SlangParameterCategory::HitAttributes => Self::HitAttributes, + SlangParameterCategory::CallablePayload => Self::CallablePayload, + SlangParameterCategory::ShaderRecord => Self::ShaderRecord, + SlangParameterCategory::ExistentialTypeParam => Self::ExistentialTypeParam, + SlangParameterCategory::ExistentialObjectParam => { + Self::ExistentialObjectParam + } + SlangParameterCategory::SubElementRegisterSpace => { + Self::SubElementRegisterSpace + } + SlangParameterCategory::Subpass => Self::Subpass, + SlangParameterCategory::MetalArgumentBufferElement => { + Self::MetalArgumentBufferElement + } + SlangParameterCategory::MetalAttribute => Self::MetalAttribute, + SlangParameterCategory::MetalPayload => Self::MetalPayload, + SlangParameterCategory::Count => Self::Count, + } + } +} + pub struct Blob { inner: SlangBlob, @@ -562,7 +686,7 @@ pub struct Context _global_session: SlangGlobalSession, session: SlangSession, modules: HashMap<AssetId, Module>, - programs: HashMap<AssetId, Program>, + programs: HashMap<AssetId, (Program, ProgramMetadata)>, } impl Context @@ -574,20 +698,27 @@ impl Context pub fn get_program(&self, asset_id: &AssetId) -> Option<&Program> { - self.programs.get(asset_id) + self.programs.get(asset_id).map(|(program, _)| program) + } + + pub fn get_program_metadata(&self, asset_id: &AssetId) -> Option<&ProgramMetadata> + { + self.programs + .get(asset_id) + .map(|(_, program_metadata)| program_metadata) } pub fn compose_into_program( &self, - modules: &[&Module], - entry_points: &[&EntryPoint], + modules: impl IntoIterator<Item = Module>, + entry_points: impl IntoIterator<Item = EntryPoint>, ) -> Result<Program, Error> { let components = modules - .iter() + .into_iter() .map(|module| SlangComponentType::from(module.inner.clone())) - .chain(entry_points.iter().map(|entry_point| { + .chain(entry_points.into_iter().map(|entry_point| { SlangComponentType::from(entry_point.inner.clone()) })) .collect::<Vec<_>>(); @@ -598,6 +729,138 @@ impl Context } } +pub struct ProgramMetadata +{ + pub vertex_subset: Option<VertexSubset>, +} + +#[derive(Debug)] +pub struct VertexSubset +{ + pub layout: Layout, + pub fields: [Option<VertexSubsetField>; const { + Vertex::REFLECTION.as_struct().unwrap().fields.len() + }], +} + +impl VertexSubset +{ + pub fn new( + vs_entrypoint: &EntryPointReflection<'_>, + ) -> Result<Self, VertexSubsetError> + { + const VERTEX_REFLECTION: &StructReflection = + const { Vertex::REFLECTION.as_struct().unwrap() }; + + if vs_entrypoint.stage() != Stage::Vertex { + return Err(VertexSubsetError::EntrypointNotInVertexStage); + } + + let vs_entrypoint_vertex_param = vs_entrypoint + .parameters() + .find(|param| param.semantic_name() == Some(VERTEX_PARAM_SEMANTIC_NAME)) + .ok_or(VertexSubsetError::EntrypointMissingVertexParam)?; + + let vs_entrypoint_vertex_param = vs_entrypoint_vertex_param + .type_layout() + .expect("Not possible"); + + if vs_entrypoint_vertex_param.parameter_category() + != ParameterCategory::VaryingInput + { + return Err(VertexSubsetError::EntryPointVertexParamNotVaryingInput); + } + + if vs_entrypoint_vertex_param.kind() != TypeKind::Struct { + return Err(VertexSubsetError::EntrypointVertexTypeNotStruct); + } + + if let Some(unknown_vertex_field_name) = vs_entrypoint_vertex_param + .fields() + .find_map(|vertex_param_field| { + let vertex_param_field_name = + vertex_param_field.name().expect("Not possible"); + + if VERTEX_REFLECTION + .fields + .iter() + .all(|vertex_field| vertex_field.name != vertex_param_field_name) + { + return Some(vertex_param_field_name); + } + + None + }) + { + return Err(VertexSubsetError::EntrypointVertexTypeHasUnknownField { + field_name: unknown_vertex_field_name.to_string(), + }); + } + + let mut layout = Layout::new::<()>(); + + let mut fields = [const { None }; const { VERTEX_REFLECTION.fields.len() }]; + + for vertex_field in const { VERTEX_REFLECTION.fields } { + let Some(vertex_field_var_layout) = + vs_entrypoint_vertex_param.get_field_by_name(vertex_field.name) + else { + continue; + }; + + let (new_layout, vertex_field_offset) = + layout.extend(vertex_field.layout).expect("Not possible"); + + layout = new_layout; + + fields[vertex_field.index] = Some(VertexSubsetField { + offset: vertex_field_offset, + reflection: vertex_field, + varying_input_offset: vertex_field_var_layout + .varying_input_offset() + .expect("Not possible"), + }); + } + + layout = layout.pad_to_align(); + + Ok(Self { layout, fields }) + } +} + +#[derive(Debug)] +pub struct VertexSubsetField +{ + pub offset: usize, + pub reflection: &'static StructFieldReflection, + pub varying_input_offset: usize, +} + +#[derive(Debug, thiserror::Error)] +pub enum VertexSubsetError +{ + #[error("Entrypoint is not in vertex stage")] + EntrypointNotInVertexStage, + + #[error( + "Entrypoint does not have a vertex parameter (parameter with semantic name {})", + VERTEX_PARAM_SEMANTIC_NAME + )] + EntrypointMissingVertexParam, + + #[error("Entrypoint vertex parameter is not a varying input")] + EntryPointVertexParamNotVaryingInput, + + #[error("Entrypoint vertex type is not a struct")] + EntrypointVertexTypeNotStruct, + + #[error("Entrypoint vertex type has unknown field {field_name}")] + EntrypointVertexTypeHasUnknownField + { + field_name: String + }, +} + #[derive(Debug, thiserror::Error)] #[error(transparent)] pub struct Error(#[from] shader_slang::Error); @@ -757,37 +1020,48 @@ fn load_modules(mut context: Single<Context>, assets: Single<Assets>) } }; + context.modules.insert(*asset_id, module.clone()); + if !module_source.link_entrypoints.is_empty() { assert!(context.programs.get(asset_id).is_none()); - let Some(vertex_shader_entry_point) = module.get_entry_point("vertex_main") - else { - tracing::error!( - "Shader module does not contain a vertex shader entry point" - ); - continue; - }; - - let Some(fragment_shader_entry_point) = - module.get_entry_point("fragment_main") - else { - tracing::error!( - "Shader module don't contain a fragment_main entry point" - ); - continue; - }; - - let shader_program = match context.compose_into_program( - &[&module], - &[&vertex_shader_entry_point, &fragment_shader_entry_point], - ) { - Ok(shader_program) => shader_program, - Err(err) => { - tracing::error!("Failed to compose shader into program: {err}"); + let entry_points = match module_source + .link_entrypoints + .iter() + .filter_map(|entrypoint_flag| { + let entrypoint_name = bitflags_match!(entrypoint_flag, { + EntrypointFlags::VERTEX => Some("vertex_main"), + EntrypointFlags::FRAGMENT => Some("fragment_main"), + _ => None + })?; + + let Some(entry_point) = module.get_entry_point(entrypoint_name) + else { + return Some(Err(EntrypointNotFoundError { entrypoint_name })); + }; + + Some(Ok(entry_point)) + }) + .collect::<Result<Vec<_>, EntrypointNotFoundError>>() + { + Ok(entry_points) => entry_points, + Err(EntrypointNotFoundError { entrypoint_name }) => { + tracing::error!( + "Shader module does not have a '{entrypoint_name}' entry point" + ); continue; } }; + let shader_program = + match context.compose_into_program([module], entry_points) { + Ok(shader_program) => shader_program, + Err(err) => { + tracing::error!("Failed to compose shader into program: {err}"); + continue; + } + }; + let linked_shader_program = match shader_program.link() { Ok(linked_shader_program) => linked_shader_program, Err(err) => { @@ -796,10 +1070,32 @@ fn load_modules(mut context: Single<Context>, assets: Single<Assets>) } }; - context.programs.insert(*asset_id, linked_shader_program); - } + let vertex_subset = if module_source + .link_entrypoints + .contains(EntrypointFlags::VERTEX) + { + VertexSubset::new( + &shader_program + .reflection(0) + .expect("Not possible") + .get_entry_point_by_name("vertex_main") + .expect("Not possible"), + ) + .inspect_err(|err| { + tracing::error!( + "Failed to create vertex subset for shader {asset_label:?}: {err}" + ); + }) + .ok() + } else { + None + }; - context.modules.insert(*asset_id, module); + context.programs.insert( + *asset_id, + (linked_shader_program, ProgramMetadata { vertex_subset }), + ); + } } } @@ -816,3 +1112,9 @@ fn load_module( Ok(Module { inner: module }) } + +#[derive(Debug)] +struct EntrypointNotFoundError +{ + entrypoint_name: &'static str, +} |
