diff --git a/librashader-reflect/src/error.rs b/librashader-reflect/src/error.rs index 11357c4..638dfb3 100644 --- a/librashader-reflect/src/error.rs +++ b/librashader-reflect/src/error.rs @@ -1,4 +1,5 @@ use thiserror::Error; +use crate::reflect::semantics::MemberOffset; #[derive(Error, Debug)] pub enum ShaderCompileError { @@ -23,7 +24,8 @@ pub enum SemanticsErrorKind { InvalidBinding(u32), InvalidResourceType, InvalidRange(u32), - UnknownSemantics(String) + UnknownSemantics(String), + InvalidTypeForSemantic(String) } #[derive(Error, Debug)] @@ -41,7 +43,12 @@ pub enum ShaderReflectError { #[error("vertx and fragment shader must have same binding")] MismatchedUniformBuffer { vertex: u32, fragment: u32 }, #[error("filter chain is non causal")] - NonCausalFilterChain { pass: u32, target: u32 } + NonCausalFilterChain { pass: u32, target: u32 }, + #[error("mismatched offset")] + MismatchedOffset { semantic: String, vertex: MemberOffset, fragment: MemberOffset }, + #[error("mismatched component")] + MismatchedComponent { semantic: String, vertex: u32, fragment: u32 }, + } impl From> for ShaderCompileError { diff --git a/librashader-reflect/src/lib.rs b/librashader-reflect/src/lib.rs index e62a13a..c27070e 100644 --- a/librashader-reflect/src/lib.rs +++ b/librashader-reflect/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(let_else)] + mod error; mod front; mod reflect; diff --git a/librashader-reflect/src/reflect/cross.rs b/librashader-reflect/src/reflect/cross.rs index ce1672f..f77339b 100644 --- a/librashader-reflect/src/reflect/cross.rs +++ b/librashader-reflect/src/reflect/cross.rs @@ -1,10 +1,11 @@ use crate::error::{ShaderReflectError, SemanticsErrorKind}; use crate::front::shaderc::GlslangCompilation; -use crate::reflect::semantics::{BindingStage, UboReflection, MAX_BINDINGS_COUNT, ShaderReflection, PushReflection, MAX_PUSH_BUFFER_SIZE, VariableSemantics, TextureSemantics}; -use crate::reflect::{ReflectOptions, ReflectShader, UniformSemantic}; +use crate::reflect::semantics::{BindingStage, UboReflection, MAX_BINDINGS_COUNT, ShaderReflection, PushReflection, MAX_PUSH_BUFFER_SIZE, VariableSemantics, TextureSemantics, ValidateTypeSemantics, MemberOffset, VariableMeta, TypeInfo, TextureMeta}; +use crate::reflect::{ReflectMeta, ReflectOptions, ReflectShader, UniformSemantic}; use spirv_cross::spirv::{Ast, Decoration, Module, Resource, ShaderResources, Type}; use std::fmt::Debug; +use rustc_hash::FxHashMap; use spirv_cross::{ErrorCode, hlsl}; use spirv_cross::hlsl::{CompilerOptions, ShaderModel}; @@ -16,6 +17,56 @@ where fragment: Ast, } +impl ValidateTypeSemantics for VariableSemantics { + fn validate_type(&self, ty: &Type) -> Option { + let (&Type::Float { ref array, vecsize, columns } | &Type::Int { ref array, vecsize, columns } | &Type::UInt { ref array, vecsize, columns }) = ty else { + return None + }; + + if !array.is_empty() { + return None + } + + let valid = match self { + VariableSemantics::MVP => matches!(ty, &Type::Float { .. }) && vecsize == 4 && columns == 4, + VariableSemantics::FrameCount => matches!(ty, &Type::UInt { .. }) && vecsize == 1 && columns == 1, + VariableSemantics::FrameDirection => matches!(ty, &Type::Int { .. }) && vecsize == 1 && columns == 1, + VariableSemantics::FloatParameter => matches!(ty, &Type::Float { .. }) && vecsize == 1 && columns == 1, + _ => matches!(ty, Type::Float { .. }) && vecsize == 4 && columns == 1 + }; + + if valid { + Some(TypeInfo { + size: vecsize, + columns + }) + } else { + None + } + } +} + +impl ValidateTypeSemantics for TextureSemantics { + fn validate_type(&self, ty: &Type) -> Option { + let &Type::Float { ref array, vecsize, columns } = ty else { + return None + }; + + if !array.is_empty() { + return None + } + + if vecsize == 4 && columns == 1 { + Some(TypeInfo { + size: vecsize, + columns + }) + } else { + None + } + } +} + impl TryFrom for CrossReflect { type Error = ShaderReflectError; @@ -176,7 +227,8 @@ impl CrossReflect Ok(size) } - fn add_active_buffer_range(ast: &Ast, resource: &Resource, options: &ReflectOptions, blame: SemanticErrorBlame) -> Result<(), ShaderReflectError> { + fn add_active_buffer_range(ast: &Ast, resource: &Resource, options: &ReflectOptions, meta: &mut ReflectMeta, offset_type: impl Fn(usize) -> MemberOffset, blame: SemanticErrorBlame) -> Result<(), ShaderReflectError> { + let ranges = ast.get_active_buffer_ranges(resource.id)?; for range in ranges { let name = ast.get_member_name(resource.base_type_id, range.index)?; @@ -194,21 +246,68 @@ impl CrossReflect match options.uniform_semantics.get(&name) { None => return Err(blame.error(SemanticsErrorKind::UnknownSemantics(name))), Some(UniformSemantic::Variable(parameter)) => { - match ¶meter.semantics { - VariableSemantics::FloatParameter => {} - semantics => { + let Some(typeinfo) = parameter.semantics.validate_type(&range_type) else { + return Err(blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name))) + }; + match ¶meter.semantics { + VariableSemantics::FloatParameter => { + let offset = offset_type(range.offset); + if let Some(meta) = meta.parameter_meta.get(¶meter.index) { + if offset != meta.offset { + return Err(ShaderReflectError::MismatchedOffset { semantic: name, vertex: meta.offset, fragment: offset }) + } + if meta.components != typeinfo.size { + return Err(ShaderReflectError::MismatchedComponent { semantic: name, vertex: meta.components, fragment: typeinfo.size }) + } + } else { + meta.parameter_meta.insert(parameter.index, VariableMeta { + offset, + components: typeinfo.size + }); + } + } + semantics => { + let offset = offset_type(range.offset); + if let Some(meta) = meta.variable_meta.get(semantics) { + if offset != meta.offset { + return Err(ShaderReflectError::MismatchedOffset { semantic: name, vertex: meta.offset, fragment: offset }) + } + if meta.components != typeinfo.size * typeinfo.columns { + return Err(ShaderReflectError::MismatchedComponent { semantic: name, vertex: meta.components, fragment: typeinfo.size }) + } + } else { + meta.parameter_meta.insert(parameter.index, VariableMeta { + offset, + components: typeinfo.size * typeinfo.columns + }); + } } } }, Some(UniformSemantic::Texture(texture)) => { + let Some(_typeinfo) = texture.semantics.validate_type(&range_type) else { + return Err(blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name))) + }; + if let TextureSemantics::PassOutput = texture.semantics { if texture.index >= options.pass_number { return Err(ShaderReflectError::NonCausalFilterChain { pass: options.pass_number, target: texture.index }) } } - // todo: validaate type + let offset = offset_type(range.offset); + if let Some(meta) = meta.texture_meta.get(&texture) { + if offset != meta.offset { + return Err(ShaderReflectError::MismatchedOffset { semantic: name, vertex: meta.offset, fragment: offset }) + } + } else { + meta.texture_meta.insert(texture.clone(), TextureMeta { + offset, + stage_mask: BindingStage::empty(), + texture: false + }); + } } } } @@ -308,9 +407,29 @@ where let push_constant = self.reflect_push_constant_buffer(vertex_push, fragment_push)?; + let mut meta = ReflectMeta::default(); + + if let Some(ubo) = vertex_ubo { + Self::add_active_buffer_range(&self.vertex, ubo, options, &mut meta, MemberOffset::Ubo, SemanticErrorBlame::Vertex)?; + } + + if let Some(ubo) = fragment_ubo { + Self::add_active_buffer_range(&self.fragment, ubo, options, &mut meta, MemberOffset::Ubo, SemanticErrorBlame::Vertex)?; + } + + if let Some(push) = vertex_push { + Self::add_active_buffer_range(&self.vertex, push, options, &mut meta, MemberOffset::PushConstant, SemanticErrorBlame::Vertex)?; + } + + if let Some(push) = fragment_push { + Self::add_active_buffer_range(&self.fragment, push, options, &mut meta, MemberOffset::PushConstant, SemanticErrorBlame::Vertex)?; + } + + // todo: slang_reflection: 556 Ok(ShaderReflection { ubo, - push_constant + push_constant, + meta }) } } @@ -320,12 +439,20 @@ mod test { use crate::reflect::cross::CrossReflect; use rspirv::binary::Disassemble; use spirv_cross::{glsl, hlsl}; + use crate::reflect::{ReflectOptions, ReflectShader}; #[test] pub fn test_into() { let result = librashader_preprocess::load_shader_source("../test/basic.slang").unwrap(); + let spirv = crate::front::shaderc::compile_spirv(&result).unwrap(); let mut reflect = CrossReflect::::try_from(spirv).unwrap(); + let huh = reflect.reflect(&ReflectOptions { + pass_number: 0, + uniform_semantics: Default::default(), + non_uniform_semantics: Default::default() + }).unwrap(); + eprintln!("{:?}", huh); eprintln!("{:#}", reflect.fragment.compile().unwrap()) // let mut loader = rspirv::dr::Loader::new(); // rspirv::binary::parse_words(spirv.fragment.as_binary(), &mut loader).unwrap(); diff --git a/librashader-reflect/src/reflect/mod.rs b/librashader-reflect/src/reflect/mod.rs index cb45683..5e073ee 100644 --- a/librashader-reflect/src/reflect/mod.rs +++ b/librashader-reflect/src/reflect/mod.rs @@ -1,6 +1,6 @@ use rustc_hash::FxHashMap; use crate::error::ShaderReflectError; -use crate::reflect::semantics::{SemanticMap, ShaderReflection, VariableSemantics, TextureSemantics}; +use crate::reflect::semantics::{SemanticMap, ShaderReflection, VariableSemantics, TextureSemantics, VariableMeta, TextureMeta}; mod cross; mod naga; @@ -11,15 +11,24 @@ pub trait ReflectShader { fn reflect(&self, options: &ReflectOptions) -> Result; } +#[derive(Debug)] pub enum UniformSemantic { Variable(SemanticMap), Texture(SemanticMap) } +#[derive(Debug)] pub struct ReflectOptions { pub pass_number: u32, pub uniform_semantics: FxHashMap, - pub non_uniform_semantics: FxHashMap> + pub non_uniform_semantics: FxHashMap>, +} + +#[derive(Debug, Default)] +pub struct ReflectMeta { + pub parameter_meta: FxHashMap, + pub variable_meta: FxHashMap, + pub texture_meta: FxHashMap, TextureMeta> } pub fn builtin_uniform_semantics() -> FxHashMap { diff --git a/librashader-reflect/src/reflect/semantics.rs b/librashader-reflect/src/reflect/semantics.rs index 7d885aa..b3f015e 100644 --- a/librashader-reflect/src/reflect/semantics.rs +++ b/librashader-reflect/src/reflect/semantics.rs @@ -1,10 +1,12 @@ use crate::error::ShaderReflectError; use bitflags::bitflags; +use crate::reflect::ReflectMeta; pub const BASE_SEMANTICS_COUNT: usize = 5; pub const MAX_BINDINGS_COUNT: u32 = 16; pub const MAX_PUSH_BUFFER_SIZE: u32 = 128; +#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone, Hash)] #[repr(i32)] pub enum VariableSemantics { // mat4, MVP @@ -21,6 +23,7 @@ pub enum VariableSemantics { FloatParameter = 5, } +#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone, Hash)] #[repr(i32)] pub enum TextureSemantics { Original = 0, @@ -31,6 +34,15 @@ pub enum TextureSemantics { User = 5, } +pub struct TypeInfo { + pub size: u32, + pub columns: u32, +} +pub trait ValidateTypeSemantics { + fn validate_type(&self, ty: &T) -> Option; +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct SemanticMap { pub(crate) semantics: T, pub(crate) index: u32 @@ -50,18 +62,43 @@ impl BindingStage { } } +#[derive(Debug)] pub struct UboReflection { pub binding: u32, pub size: u32, pub stage_mask: BindingStage, } +#[derive(Debug)] pub struct PushReflection { pub size: u32, pub stage_mask: BindingStage, } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum MemberOffset { + Ubo(usize), + PushConstant(usize) +} + +#[derive(Debug)] +pub struct VariableMeta { + // this might bite us in the back because retroarch keeps separate UBO/push offsets.. eh + pub offset: MemberOffset, + pub components: u32 +} + +#[derive(Debug)] +pub struct TextureMeta { + // this might bite us in the back because retroarch keeps separate UBO/push offsets.. + pub offset: MemberOffset, + pub stage_mask: BindingStage, + pub texture: bool +} + +#[derive(Debug)] pub struct ShaderReflection { pub ubo: Option, pub push_constant: Option, + pub meta: ReflectMeta }