summaryrefslogtreecommitdiff
path: root/engine/src/rendering
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2026-11-05 10:12:09 +0100
committerHampusM <hampus@hampusmat.com>2026-11-05 10:12:09 +0100
commite8472cc24f5eafd8a9a08b64c52eb49d01ac4031 (patch)
treef3445f52e562ed50d55ddfa24b39f783db2f4df2 /engine/src/rendering
parentc45826d102dc4f47c16c65d9837cc03b59534f23 (diff)
feat(engine): make shader entry point names not hardcoded
Diffstat (limited to 'engine/src/rendering')
-rw-r--r--engine/src/rendering/backend/opengl.rs44
-rw-r--r--engine/src/rendering/main_pipeline.rs4
-rw-r--r--engine/src/rendering/shader.rs236
-rw-r--r--engine/src/rendering/shader/reflection.rs4
4 files changed, 174 insertions, 114 deletions
diff --git a/engine/src/rendering/backend/opengl.rs b/engine/src/rendering/backend/opengl.rs
index 0a58417..aad9b6c 100644
--- a/engine/src/rendering/backend/opengl.rs
+++ b/engine/src/rendering/backend/opengl.rs
@@ -124,7 +124,6 @@ use crate::rendering::shader::{
Context as ShaderContext,
ContextSessionInitOpts as ShaderContextSessionInitOpts,
DebugInfoLevel as ShaderDebugInfoLevel,
- EntrypointFlags as ShaderEntrypointFlags,
Error as ShaderError,
LineDirectiveMode as ShaderLineDirectiveMode,
OptimizationLevel as ShaderOptimizationLevel,
@@ -953,8 +952,8 @@ fn handle_commands(
info: ShaderInfo {
vertex_desc: if shader_program
.metadata()
- .link_entrypoints
- .contains(ShaderEntrypointFlags::VERTEX)
+ .vertex_stage_entry_point_idx
+ .is_some()
{
Some(shader_program.create_vertex_desc(
backend_properties.shader_target_id,
@@ -1869,26 +1868,13 @@ fn create_and_init_gl_shader_program(
{
let gl_shader_program = GlShaderProgram::new(current_context);
- let shader_program_reflection = shader_program
- .reflection(backend_properties.shader_target_id)
- .expect("Not possible");
-
- if shader_program
- .metadata()
- .link_entrypoints
- .contains(ShaderEntrypointFlags::VERTEX)
+ if let Some(vertex_stage_entry_point_idx) =
+ shader_program.metadata().vertex_stage_entry_point_idx
{
- let Some(vs_entry_point_index) = shader_program_reflection
- .entry_points()
- .position(|entry_point| entry_point.stage() == ShaderStage::Vertex)
- else {
- unreachable!();
- };
-
let gl_vertex_shader = create_and_init_gl_shader(
current_context,
shader_program,
- vs_entry_point_index,
+ vertex_stage_entry_point_idx,
GlShaderKind::Vertex,
backend_properties,
)?;
@@ -1896,22 +1882,13 @@ fn create_and_init_gl_shader_program(
gl_shader_program.attach(current_context, &gl_vertex_shader);
}
- if shader_program
- .metadata()
- .link_entrypoints
- .contains(ShaderEntrypointFlags::FRAGMENT)
+ if let Some(fragment_stage_entry_point_idx) =
+ shader_program.metadata().fragment_stage_entry_point_idx
{
- let Some(fs_entry_point_index) = shader_program_reflection
- .entry_points()
- .position(|entry_point| entry_point.stage() == ShaderStage::Fragment)
- else {
- unreachable!();
- };
-
let gl_fragment_shader = create_and_init_gl_shader(
current_context,
shader_program,
- fs_entry_point_index,
+ fragment_stage_entry_point_idx,
GlShaderKind::Fragment,
backend_properties,
)?;
@@ -1927,14 +1904,11 @@ fn create_and_init_gl_shader_program(
fn create_and_init_gl_shader(
current_context: &MaybeCurrentContextWithFns,
shader_program: &ShaderProgram,
- entry_point_index: usize,
+ entry_point_index: u32,
gl_shader_kind: GlShaderKind,
backend_properties: &BackendProperties,
) -> Result<GlShader, GlShaderCreationError>
{
- let entry_point_index = u32::try_from(entry_point_index)
- .expect("Shader entry point index does not fit in 32-bit unsigned int");
-
let entry_point_code = shader_program
.get_entry_point_code(entry_point_index, backend_properties.shader_target_id)
.map_err(|err| GlShaderCreationError::GetShaderEntryPointCodeFailed {
diff --git a/engine/src/rendering/main_pipeline.rs b/engine/src/rendering/main_pipeline.rs
index 754b507..7caf625 100644
--- a/engine/src/rendering/main_pipeline.rs
+++ b/engine/src/rendering/main_pipeline.rs
@@ -401,7 +401,7 @@ fn get_or_create_shaders<'shader_ctx>(
name: "main_3d_shader.slang".into(),
file_path: Path::new("@engine/main_3d_shader").into(),
source: include_str!("../../res/main_3d_shader.slang").into(),
- link_entrypoints: ShaderEntrypointFlags::VERTEX
+ link_entry_points: ShaderEntrypointFlags::VERTEX
| ShaderEntrypointFlags::FRAGMENT,
},
);
@@ -418,7 +418,7 @@ fn get_or_create_shaders<'shader_ctx>(
name: "sky_box_shader.slang".into(),
file_path: Path::new("@engine/sky_box_shader").into(),
source: include_str!("../../res/sky_box_shader.slang").into(),
- link_entrypoints: ShaderEntrypointFlags::VERTEX
+ link_entry_points: ShaderEntrypointFlags::VERTEX
| ShaderEntrypointFlags::FRAGMENT,
},
);
diff --git a/engine/src/rendering/shader.rs b/engine/src/rendering/shader.rs
index 2a2ac7a..05fc212 100644
--- a/engine/src/rendering/shader.rs
+++ b/engine/src/rendering/shader.rs
@@ -5,8 +5,9 @@ use std::path::Path;
use std::str::Utf8Error;
use std::sync::Arc;
-use bitflags::{bitflags, bitflags_match};
+use bitflags::{bitflags, Flags as _};
use ecs::phase::INIT as INIT_PHASE;
+use owning_ref::OwningRef;
use shader_slang::{
Blob as SlangBlob,
ComponentType as SlangComponentType,
@@ -33,6 +34,7 @@ use crate::ecs::phase::Phase;
use crate::ecs::sole::Single;
use crate::ecs::{declare_entity, pair, Sole};
use crate::mesh::vertex_buffer::VertexLabel;
+use crate::rendering::shader::reflection::Stage;
pub mod cursor;
pub mod reflection;
@@ -49,7 +51,7 @@ pub struct ModuleSource
pub name: Cow<'static, str>,
pub file_path: Cow<'static, Path>,
pub source: Cow<'static, str>,
- pub link_entrypoints: EntrypointFlags,
+ pub link_entry_points: EntrypointFlags,
}
impl Asset for ModuleSource
@@ -171,23 +173,23 @@ impl Program
target_id: CompileTargetId,
) -> Result<VertexDescription, VertexDescCreationError>
{
- if !self
- .metadata
- .link_entrypoints
- .contains(EntrypointFlags::VERTEX)
- {
+ let Some(vertex_stage_entry_point_idx) =
+ self.metadata.vertex_stage_entry_point_idx
+ else {
return Err(VertexDescCreationError::NoVertexStageEntrypointInProgram);
- }
+ };
let program_reflection = self
.reflection(target_id)
.map_err(VertexDescCreationError::GetProgramReflectionFailed)?;
- VertexDescription::new(
- &program_reflection
- .get_entry_point_by_name("vertex_main")
- .expect("Not possible"),
- )
+ let Some(vertex_stage_entry_point) =
+ program_reflection.get_entry_point_by_index(vertex_stage_entry_point_idx)
+ else {
+ unreachable!();
+ };
+
+ VertexDescription::new(&vertex_stage_entry_point)
}
}
@@ -301,51 +303,130 @@ impl Context
self.programs.get(asset_id)
}
+ pub fn load_module(
+ &self,
+ module_source: &ModuleSource,
+ ) -> Result<Module, LoadModuleError>
+ {
+ let Some(session) = self.session.as_ref() else {
+ return Err(LoadModuleError::ContextSessionNotInitialized);
+ };
+
+ let module = session
+ .load_module_from_source_string(
+ &module_source.name,
+ &module_source.file_path.to_string_lossy(),
+ &module_source.source,
+ )
+ .map_err(Error)?;
+
+ Ok(Module { inner: module })
+ }
+
#[tracing::instrument(skip_all)]
- pub fn compose_into_program(
+ pub fn compose_program(
&self,
- module: Module,
- link_entrypoints: EntrypointFlags,
+ modules: &[Module],
+ link_entry_points: EntrypointFlags,
+ target_id: CompileTargetId,
) -> Result<Program, ComposeProgramError>
{
let Some(session) = self.session.as_ref() else {
return Err(ComposeProgramError::ContextSessionNotInitialized);
};
- let entry_points = link_entrypoints
+ let all_entry_points = modules
.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(ComposeProgramError::EntrypointNotFoundError {
- entrypoint_name,
- }));
- };
-
- Some(Ok(entry_point))
+ .flat_map(Module::entry_points)
+ .enumerate()
+ .map(|(entry_point_index, entry_point)| {
+ Ok::<_, Error>((
+ entry_point_index,
+ OwningRef::new(SlangObjectRc::<SlangComponentType>::from(
+ entry_point.inner,
+ ))
+ .try_map(|entry_point| {
+ entry_point.layout(i64::from(target_id.index))
+ })?,
+ ))
})
.collect::<Result<Vec<_>, _>>()?;
- let components = entry_points
- .into_iter()
- .map(|entry_point| {
- SlangObjectRc::<SlangComponentType>::from(entry_point.inner)
- })
- .chain([SlangObjectRc::<SlangComponentType>::from(module.inner)])
- .collect::<Vec<_>>();
+ let mut program_metadata = ProgramMetadata::default();
+
+ let mut components = Vec::with_capacity(
+ EntrypointFlags::iter_defined_names().count() + modules.len(),
+ );
+
+ components.extend(modules.iter().map(|module| {
+ SlangObjectRc::<SlangComponentType>::from(module.inner.clone())
+ }));
+
+ if link_entry_points.contains(EntrypointFlags::VERTEX) {
+ let mut entry_point_iter =
+ all_entry_points.iter().filter(|(_, entry_point)| {
+ Stage::from_slang(
+ entry_point.entry_point_by_index(0).unwrap().stage(),
+ ) == Stage::Vertex
+ });
+
+ let (vertex_stage_entry_point_idx, vertex_stage_entry_point) =
+ entry_point_iter
+ .next()
+ .ok_or(ComposeProgramError::NoEntryPointForStage(Stage::Vertex))
+ .cloned()?;
+
+ if entry_point_iter.next().is_some() {
+ return Err(ComposeProgramError::MultipleEntryPointsForStage(
+ Stage::Vertex,
+ ));
+ }
+
+ // This is fine. It is very unlikely there are more than 2^32 entry points
+ program_metadata.vertex_stage_entry_point_idx =
+ Some(u32::try_from(vertex_stage_entry_point_idx).expect(
+ "Shader's vertex stage entry point index would overflow a u32",
+ ));
+
+ components.push(vertex_stage_entry_point.as_owner().clone());
+ }
+
+ if link_entry_points.contains(EntrypointFlags::FRAGMENT) {
+ let mut entry_point_iter =
+ all_entry_points.iter().filter(|(_, entry_point)| {
+ Stage::from_slang(
+ entry_point.entry_point_by_index(0).unwrap().stage(),
+ ) == Stage::Fragment
+ });
+
+ let (fragment_stage_entry_point_idx, fragment_stage_entry_point) =
+ entry_point_iter
+ .next()
+ .ok_or(ComposeProgramError::NoEntryPointForStage(Stage::Fragment))
+ .cloned()?;
+
+ if entry_point_iter.next().is_some() {
+ return Err(ComposeProgramError::MultipleEntryPointsForStage(
+ Stage::Fragment,
+ ));
+ }
+
+ // This is fine. It is very unlikely there are more than 2^32 entry points
+ program_metadata.fragment_stage_entry_point_idx =
+ Some(u32::try_from(fragment_stage_entry_point_idx).expect(
+ "Shader's fragment stage entry point index would overflow a u32",
+ ));
+
+ components.push(fragment_stage_entry_point.as_owner().clone());
+ }
let program = session
.create_composite_component_type(&components)
- .map_err(|err| ComposeProgramError::Other(Error(err)))?;
+ .map_err(Error)?;
Ok(Program {
inner: program,
- metadata: ProgramMetadata { link_entrypoints },
+ metadata: program_metadata,
})
}
}
@@ -524,11 +605,12 @@ impl CompileTargetFormat
}
}
-#[derive(Debug, Clone)]
+#[derive(Debug, Default, Clone)]
#[non_exhaustive]
pub struct ProgramMetadata
{
- pub link_entrypoints: EntrypointFlags,
+ pub vertex_stage_entry_point_idx: Option<u32>,
+ pub fragment_stage_entry_point_idx: Option<u32>,
}
#[derive(Debug, Clone)]
@@ -728,17 +810,14 @@ pub enum ComposeProgramError
#[error("A shader context session has not been initialized")]
ContextSessionNotInitialized,
- #[error("Shader module does not have a '{entrypoint_name}' entry point")]
- EntrypointNotFoundError
- {
- entrypoint_name: &'static str
- },
+ #[error("Multiple entry points found for shader stage {0:?}")]
+ MultipleEntryPointsForStage(Stage),
- #[error("Failed to create vertex description")]
- VertexDescriptionCreationFailed(#[from] VertexDescCreationError),
+ #[error("No entry point was found for shader stage {0:?}")]
+ NoEntryPointForStage(Stage),
#[error(transparent)]
- Other(Error),
+ Other(#[from] Error),
}
#[derive(Debug, thiserror::Error)]
@@ -755,6 +834,16 @@ pub enum ContextSessionInitError
}
#[derive(Debug, thiserror::Error)]
+pub enum LoadModuleError
+{
+ #[error("A shader context session has not been initialized")]
+ ContextSessionNotInitialized,
+
+ #[error(transparent)]
+ Other(#[from] Error),
+}
+
+#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct Error(#[from] shader_slang::Error);
@@ -850,7 +939,7 @@ fn import(
source: std::fs::read_to_string(file_path)
.map_err(ImportError::ReadFileFailed)?
.into(),
- link_entrypoints: settings.map_or_default(|settings| settings.link_entrypoints),
+ link_entry_points: settings.map_or_default(|settings| settings.link_entrypoints),
});
Ok(())
@@ -914,6 +1003,7 @@ fn load_modules(
mut context: Single<Context>,
assets: Single<Assets>,
asset_events: Single<AssetEvents>,
+ rendering_backend_properties: Single<crate::rendering::BackendProperties>,
) -> Result<(), crate::Error>
{
let Ok(context) = context.get_mut() else {
@@ -923,7 +1013,7 @@ fn load_modules(
let assets = assets.get()?;
let asset_events = asset_events.get()?;
- let Some(session) = context.session.as_ref() else {
+ let Ok(rendering_backend_properties) = rendering_backend_properties.get() else {
return Ok(());
};
@@ -938,25 +1028,35 @@ fn load_modules(
unreachable!();
};
- tracing::debug!(asset_label=?asset_label, "Loading shader module");
+ tracing::debug!(%asset_label, "Loading shader module");
- let module = match load_module(session, module_source) {
+ let module = match context.load_module(module_source) {
Ok(module) => module,
Err(err) => {
- tracing::error!("Failed to load shader module: {err}");
+ tracing::error!(%asset_label, "Failed to load shader module: {err}");
continue;
}
};
- if !module_source.link_entrypoints.is_empty() {
- assert!(!context.programs.contains_key(asset_id));
+ if !module_source.link_entry_points.is_empty() {
+ if context.programs.contains_key(asset_id) {
+ tracing::debug!(
+ %asset_label,
+ "Removing old shader program associated with asset"
+ );
- let shader_program = match context
- .compose_into_program(module, module_source.link_entrypoints)
- {
+ context.programs.remove(asset_id);
+ }
+
+ let shader_program = match context.compose_program(
+ &[module],
+ module_source.link_entry_points,
+ rendering_backend_properties.shader_target_id,
+ ) {
Ok(shader_program) => shader_program,
Err(err) => {
tracing::error!(
+ %asset_label,
"Failed to compose shader into program: {:#}",
crate::Error::new(err)
);
@@ -967,7 +1067,7 @@ fn load_modules(
let linked_shader_program = match shader_program.into_linked() {
Ok(linked_shader_program) => linked_shader_program,
Err(err) => {
- tracing::error!("Failed to link shader: {err}");
+ tracing::error!(%asset_label, "Failed to link shader: {err}");
continue;
}
};
@@ -978,17 +1078,3 @@ fn load_modules(
Ok(())
}
-
-fn load_module(
- session: &SlangSession,
- module_source: &ModuleSource,
-) -> Result<Module, Error>
-{
- let module = session.load_module_from_source_string(
- &module_source.name,
- &module_source.file_path.to_string_lossy(),
- &module_source.source,
- )?;
-
- Ok(Module { inner: module })
-}
diff --git a/engine/src/rendering/shader/reflection.rs b/engine/src/rendering/shader/reflection.rs
index 10bff54..cc16ec6 100644
--- a/engine/src/rendering/shader/reflection.rs
+++ b/engine/src/rendering/shader/reflection.rs
@@ -36,7 +36,7 @@ impl<'a> EntryPointReflection<'a>
#[must_use]
pub fn stage(&self) -> Stage
{
- Stage::from_slang_stage(self.inner.stage())
+ Stage::from_slang(self.inner.stage())
}
#[must_use]
@@ -722,7 +722,7 @@ pub enum Stage
impl Stage
{
- fn from_slang_stage(stage: shader_slang::Stage) -> Self
+ pub(super) fn from_slang(stage: shader_slang::Stage) -> Self
{
match stage {
shader_slang::Stage::None => Self::None,