summaryrefslogtreecommitdiff
path: root/engine/src
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src')
-rw-r--r--engine/src/mesh.rs83
-rw-r--r--engine/src/mesh/cube.rs14
-rw-r--r--engine/src/reflection.rs31
-rw-r--r--engine/src/renderer/opengl.rs85
-rw-r--r--engine/src/renderer/opengl/graphics_mesh.rs141
-rw-r--r--engine/src/renderer/opengl/vertex.rs65
-rw-r--r--engine/src/shader.rs378
7 files changed, 601 insertions, 196 deletions
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,
+}