summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engine/src/rendering/backend/opengl.rs183
-rw-r--r--engine/src/rendering/shader.rs159
2 files changed, 252 insertions, 90 deletions
diff --git a/engine/src/rendering/backend/opengl.rs b/engine/src/rendering/backend/opengl.rs
index bdaa555..0a58417 100644
--- a/engine/src/rendering/backend/opengl.rs
+++ b/engine/src/rendering/backend/opengl.rs
@@ -62,7 +62,7 @@ use opengl_bindings::misc::{
};
use opengl_bindings::shader::{
Error as GlShaderError,
- Kind as ShaderKind,
+ Kind as GlShaderKind,
Program as GlShaderProgram,
Shader as GlShader,
};
@@ -119,8 +119,15 @@ use crate::rendering::shader::cursor::{
};
use crate::rendering::shader::reflection::Stage as ShaderStage;
use crate::rendering::shader::{
+ CompileTarget as ShaderCompileTarget,
+ CompileTargetFormat as ShaderCompileTargetFormat,
+ Context as ShaderContext,
+ ContextSessionInitOpts as ShaderContextSessionInitOpts,
+ DebugInfoLevel as ShaderDebugInfoLevel,
EntrypointFlags as ShaderEntrypointFlags,
Error as ShaderError,
+ LineDirectiveMode as ShaderLineDirectiveMode,
+ OptimizationLevel as ShaderOptimizationLevel,
Program as ShaderProgram,
VertexDescCreationError as ShaderVertexDescCreationError,
VertexDescription as ShaderVertexDescription,
@@ -521,15 +528,27 @@ impl crate::ecs::extension::Extension for Extension
}
fn init(
- mut shader_context: Single<crate::rendering::shader::Context>,
+ mut shader_context: Single<ShaderContext>,
mut actions: Actions,
) -> Result<(), crate::Error>
{
let shader_context = shader_context.get_mut()?;
- let shader_target_ids = shader_context
- .init_session(&[&crate::rendering::shader::CompileTarget::default()
- .format(crate::rendering::shader::CompileTargetFormat::Glsl)])?;
+ let shader_target_ids = shader_context.init_session(
+ &if cfg!(debug_assertions) {
+ ShaderContextSessionInitOpts::default()
+ .optimization_level(ShaderOptimizationLevel::Default)
+ .debug_info_level(ShaderDebugInfoLevel::Maximal)
+ .line_directive_mode(ShaderLineDirectiveMode::Standard)
+ .no_mangle(true)
+ } else {
+ ShaderContextSessionInitOpts::default()
+ .optimization_level(ShaderOptimizationLevel::High)
+ .debug_info_level(ShaderDebugInfoLevel::None)
+ .line_directive_mode(ShaderLineDirectiveMode::None)
+ },
+ &[&ShaderCompileTarget::default().format(ShaderCompileTargetFormat::Glsl)],
+ )?;
actions.create_sole(BackendProperties {
shader_target_id: shader_target_ids[0],
@@ -921,12 +940,12 @@ fn handle_commands(
}
Command::CreateShaderProgram(object_id, shader_program) => {
if let Err(err) = backend_resources
- .try_create_resource::<CreateShaderError>(
+ .try_create_resource::<GlShaderCreationError>(
object_store,
object_id,
|| {
Ok(BackendResource::Shader {
- program: create_shader_program(
+ program: create_and_init_gl_shader_program(
gl_context,
&shader_program,
backend_properties,
@@ -1842,107 +1861,123 @@ fn cube_map_texture_face_to_gl(face: CubeMapTextureFace) -> GlCubeMapTextureFace
}
}
-fn create_shader_program(
+fn create_and_init_gl_shader_program(
current_context: &MaybeCurrentContextWithFns,
shader_program: &ShaderProgram,
backend_properties: &BackendProperties,
-) -> Result<GlShaderProgram, CreateShaderError>
+) -> Result<GlShaderProgram, GlShaderCreationError>
{
+ let gl_shader_program = GlShaderProgram::new(current_context);
+
let shader_program_reflection = shader_program
.reflection(backend_properties.shader_target_id)
.expect("Not possible");
- let (vs_entry_point_index, vs_entry_point_reflection) = shader_program_reflection
- .entry_points()
- .enumerate()
- .find(|(_, entry_point)| entry_point.stage() == ShaderStage::Vertex)
- .ok_or_else(|| {
- CreateShaderError::NoShaderStageEntrypointFound(ShaderStage::Vertex)
- })?;
+ if shader_program
+ .metadata()
+ .link_entrypoints
+ .contains(ShaderEntrypointFlags::VERTEX)
+ {
+ let Some(vs_entry_point_index) = shader_program_reflection
+ .entry_points()
+ .position(|entry_point| entry_point.stage() == ShaderStage::Vertex)
+ else {
+ unreachable!();
+ };
- let vertex_shader_entry_point_code = shader_program
- .get_entry_point_code(
- vs_entry_point_index.try_into().expect(
- "Vertex shader entry point index does not fit in 32-bit unsigned int",
- ),
- backend_properties.shader_target_id,
- )
- .map_err(|err| CreateShaderError::GetShaderEntryPointCodeFailed {
- err,
- stage: ShaderStage::Vertex,
- entrypoint: vs_entry_point_reflection
- .name()
- .map_or("(none)".into(), |name| name.to_string().into()),
- })?;
+ let gl_vertex_shader = create_and_init_gl_shader(
+ current_context,
+ shader_program,
+ vs_entry_point_index,
+ GlShaderKind::Vertex,
+ backend_properties,
+ )?;
- let (fs_entry_point_index, fs_entry_point_reflection) = shader_program_reflection
- .entry_points()
- .enumerate()
- .find(|(_, entry_point)| entry_point.stage() == ShaderStage::Fragment)
- .ok_or_else(|| {
- CreateShaderError::NoShaderStageEntrypointFound(ShaderStage::Fragment)
- })?;
+ gl_shader_program.attach(current_context, &gl_vertex_shader);
+ }
- let fragment_shader_entry_point_code = shader_program
- .get_entry_point_code(
- fs_entry_point_index.try_into().expect(
- "Fragment shader entry point index does not fit in 32-bit unsigned int",
- ),
- backend_properties.shader_target_id,
- )
- .map_err(|err| CreateShaderError::GetShaderEntryPointCodeFailed {
- err,
- stage: ShaderStage::Fragment,
- entrypoint: fs_entry_point_reflection
- .name()
- .map_or("(none)".into(), |name| name.to_string().into()),
- })?;
+ if shader_program
+ .metadata()
+ .link_entrypoints
+ .contains(ShaderEntrypointFlags::FRAGMENT)
+ {
+ let Some(fs_entry_point_index) = shader_program_reflection
+ .entry_points()
+ .position(|entry_point| entry_point.stage() == ShaderStage::Fragment)
+ else {
+ unreachable!();
+ };
- let vertex_shader = GlShader::new(current_context, ShaderKind::Vertex);
+ let gl_fragment_shader = create_and_init_gl_shader(
+ current_context,
+ shader_program,
+ fs_entry_point_index,
+ GlShaderKind::Fragment,
+ backend_properties,
+ )?;
- vertex_shader.set_source(
- current_context,
- vertex_shader_entry_point_code.as_str().unwrap(),
- )?;
+ gl_shader_program.attach(current_context, &gl_fragment_shader);
+ }
- vertex_shader.compile(current_context)?;
+ gl_shader_program.link(current_context)?;
- let fragment_shader = GlShader::new(current_context, ShaderKind::Fragment);
+ Ok(gl_shader_program)
+}
- fragment_shader.set_source(
- current_context,
- fragment_shader_entry_point_code.as_str().unwrap(),
- )?;
+fn create_and_init_gl_shader(
+ current_context: &MaybeCurrentContextWithFns,
+ shader_program: &ShaderProgram,
+ entry_point_index: usize,
+ 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");
- fragment_shader.compile(current_context)?;
+ let entry_point_code = shader_program
+ .get_entry_point_code(entry_point_index, backend_properties.shader_target_id)
+ .map_err(|err| GlShaderCreationError::GetShaderEntryPointCodeFailed {
+ err,
+ stage: match gl_shader_kind {
+ GlShaderKind::Vertex => ShaderStage::Vertex,
+ GlShaderKind::Fragment => ShaderStage::Fragment,
+ },
+ entry_point: shader_program
+ .reflection(backend_properties.shader_target_id)
+ .ok()
+ .and_then(|program_reflection| {
+ program_reflection
+ .get_entry_point_by_index(entry_point_index)?
+ .name()
+ .map(ToOwned::to_owned)
+ })
+ .map_or("<unknown>".into(), Into::into),
+ })?;
- let gl_shader_program = GlShaderProgram::new(current_context);
+ let gl_shader = GlShader::new(current_context, gl_shader_kind);
- gl_shader_program.attach(current_context, &vertex_shader);
- gl_shader_program.attach(current_context, &fragment_shader);
+ gl_shader.set_source(current_context, entry_point_code.as_str().unwrap())?;
- gl_shader_program.link(current_context)?;
+ gl_shader.compile(current_context)?;
- Ok(gl_shader_program)
+ Ok(gl_shader)
}
#[derive(Debug, thiserror::Error)]
-enum CreateShaderError
+enum GlShaderCreationError
{
#[error(
- "Failed to get code of shader program entry point {entrypoint} of stage {stage:?}"
+ "Failed to get code of shader program entry point {entry_point} of stage {stage:?}"
)]
GetShaderEntryPointCodeFailed
{
#[source]
err: ShaderError,
stage: ShaderStage,
- entrypoint: Cow<'static, str>,
+ entry_point: Cow<'static, str>,
},
- #[error("No entrypoint was found for shader stage {0:?}")]
- NoShaderStageEntrypointFound(ShaderStage),
-
#[error("Failed to create shader vertex description")]
VertexDescCreationFailed(#[from] ShaderVertexDescCreationError),
diff --git a/engine/src/rendering/shader.rs b/engine/src/rendering/shader.rs
index 926302a..3fdc2c1 100644
--- a/engine/src/rendering/shader.rs
+++ b/engine/src/rendering/shader.rs
@@ -10,7 +10,6 @@ use ecs::phase::INIT as INIT_PHASE;
use shader_slang::{
Blob as SlangBlob,
ComponentType as SlangComponentType,
- DebugInfoLevel as SlangDebugInfoLevel,
EntryPoint as SlangEntryPoint,
GlobalSession as SlangGlobalSession,
Module as SlangModule,
@@ -236,7 +235,6 @@ pub struct Context
{
global_session: SlangGlobalSession,
session: Option<SlangSession>,
- modules: hashbrown::HashMap<AssetId, Module>,
programs: hashbrown::HashMap<AssetId, Program>,
}
@@ -245,6 +243,7 @@ impl Context
#[allow(clippy::missing_errors_doc)]
pub fn init_session(
&mut self,
+ opts: &ContextSessionInitOpts,
compile_targets: &[&CompileTarget],
) -> Result<Vec<CompileTargetId>, ContextSessionInitError>
{
@@ -257,10 +256,11 @@ impl Context
}
let session_options = shader_slang::CompilerOptions::default()
- .optimization(shader_slang::OptimizationLevel::None)
- .matrix_layout_row(true)
- .debug_information(SlangDebugInfoLevel::Maximal)
- .no_mangle(true);
+ .matrix_layout_row(true) // Matrices must be row major
+ .optimization(opts.optimization_level.into_slang())
+ .debug_information(opts.debug_info_level.into_slang())
+ .line_directive_mode(opts.line_directive_mode.into_slang())
+ .no_mangle(opts.no_mangle);
let (compile_target_ids, target_descs) = compile_targets
.iter()
@@ -271,7 +271,10 @@ impl Context
(
CompileTargetId { index: index as u32 },
shader_slang::TargetDesc::default()
- .format(compile_target.format.into_slang()),
+ .format(compile_target.format.into_slang())
+ // For some reason, the compiler options need to be specified
+ // for each target as well. Otherwise, they are not used
+ .options(&session_options),
)
})
.collect::<(Vec<_>, Vec<_>)>();
@@ -292,12 +295,6 @@ impl Context
}
#[must_use]
- pub fn get_module(&self, asset_id: &AssetId) -> Option<&Module>
- {
- self.modules.get(asset_id)
- }
-
- #[must_use]
pub fn get_program(&self, asset_id: &AssetId) -> Option<&Program>
{
self.programs.get(asset_id)
@@ -350,6 +347,139 @@ impl Context
}
}
+#[derive(Debug, Default, Clone)]
+#[non_exhaustive]
+pub struct ContextSessionInitOpts
+{
+ pub optimization_level: OptimizationLevel,
+ pub debug_info_level: DebugInfoLevel,
+ pub line_directive_mode: LineDirectiveMode,
+ pub no_mangle: bool,
+}
+
+impl ContextSessionInitOpts
+{
+ pub fn optimization_level(mut self, optimization_level: OptimizationLevel) -> Self
+ {
+ self.optimization_level = optimization_level;
+ self
+ }
+
+ pub fn debug_info_level(mut self, debug_info_level: DebugInfoLevel) -> Self
+ {
+ self.debug_info_level = debug_info_level;
+ self
+ }
+
+ pub fn line_directive_mode(mut self, line_directive_mode: LineDirectiveMode) -> Self
+ {
+ self.line_directive_mode = line_directive_mode;
+ self
+ }
+
+ pub fn no_mangle(mut self, no_mangle: bool) -> Self
+ {
+ self.no_mangle = no_mangle;
+ self
+ }
+}
+
+#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)]
+#[non_exhaustive]
+pub enum OptimizationLevel
+{
+ /// Don't optimize at all.
+ None,
+
+ /// Default optimization level: balance code quality and compilation time.
+ #[default]
+ Default,
+
+ /// Optimize aggressively.
+ High,
+
+ /// Include optimizations that may take a very long time, or may involve severe
+ /// space-vs-speed tradeoffs
+ Maximal,
+}
+
+impl OptimizationLevel
+{
+ fn into_slang(self) -> shader_slang::OptimizationLevel
+ {
+ match self {
+ Self::None => shader_slang::OptimizationLevel::None,
+ Self::Default => shader_slang::OptimizationLevel::Default,
+ Self::High => shader_slang::OptimizationLevel::High,
+ Self::Maximal => shader_slang::OptimizationLevel::Maximal,
+ }
+ }
+}
+
+#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)]
+#[non_exhaustive]
+pub enum DebugInfoLevel
+{
+ /// Don't emit debug information at all.
+ #[default]
+ None,
+
+ /// Emit as little debug information as possible, while still supporting stack
+ /// trackers.
+ Minimal,
+
+ /// Emit whatever is the standard level of debug information for each target.
+ Standard,
+
+ /// Emit as much debug information as possible for each target.
+ Maximal,
+}
+
+impl DebugInfoLevel
+{
+ fn into_slang(self) -> shader_slang::DebugInfoLevel
+ {
+ match self {
+ Self::None => shader_slang::DebugInfoLevel::None,
+ Self::Minimal => shader_slang::DebugInfoLevel::Minimal,
+ Self::Standard => shader_slang::DebugInfoLevel::Standard,
+ Self::Maximal => shader_slang::DebugInfoLevel::Maximal,
+ }
+ }
+}
+
+#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq)]
+#[non_exhaustive]
+pub enum LineDirectiveMode
+{
+ /// Don't emit line directives at all.
+ #[default]
+ None,
+
+ /// Emit standard C-style `#line` directives.
+ Standard,
+
+ /// Emit GLSL-style directives with file *number* instead of name
+ Glsl,
+
+ /// Use a source map to track line mappings (ie no #line will appear in emitting
+ /// source)
+ SourceMap,
+}
+
+impl LineDirectiveMode
+{
+ fn into_slang(self) -> shader_slang::LineDirectiveMode
+ {
+ match self {
+ Self::None => shader_slang::LineDirectiveMode::None,
+ Self::Standard => shader_slang::LineDirectiveMode::Standard,
+ Self::Glsl => shader_slang::LineDirectiveMode::Glsl,
+ Self::SourceMap => shader_slang::LineDirectiveMode::SourceMap,
+ }
+ }
+}
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CompileTargetId
{
@@ -757,7 +887,6 @@ pub(super) fn prepare(collector: &mut crate::ecs::extension::Collector<'_>)
.add_sole(Context {
global_session,
session: None,
- modules: hashbrown::HashMap::new(),
programs: hashbrown::HashMap::new(),
})
.ok();
@@ -816,8 +945,6 @@ fn load_modules(
}
};
- context.modules.insert(*asset_id, module.clone());
-
if !module_source.link_entrypoints.is_empty() {
assert!(!context.programs.contains_key(asset_id));