diff --git a/Cargo.lock b/Cargo.lock index b0013ad..50caf8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,6 +41,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "cc" version = "1.0.73" @@ -71,6 +77,15 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -140,6 +155,7 @@ dependencies = [ "librashader", "librashader-preprocess", "naga", + "rspirv", "shaderc", "spirv_cross", "thiserror", @@ -266,6 +282,17 @@ dependencies = [ "xmlparser", ] +[[package]] +name = "rspirv" +version = "0.11.0+1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1503993b59ca9ae4127365c3293517576d7ce56be9f3d8abb1625c85ddc583ba" +dependencies = [ + "fxhash", + "num-traits", + "spirv", +] + [[package]] name = "rustc-hash" version = "1.1.0" diff --git a/librashader-reflect/Cargo.toml b/librashader-reflect/Cargo.toml index bf05e3a..53eda27 100644 --- a/librashader-reflect/Cargo.toml +++ b/librashader-reflect/Cargo.toml @@ -14,3 +14,4 @@ thiserror = "1.0.37" [dev-dependencies] librashader-preprocess = { path = "../librashader-preprocess", default-features = false } +rspirv = "0.11.0+1.5.4" diff --git a/librashader-reflect/src/error.rs b/librashader-reflect/src/error.rs index 5360c73..d604c0c 100644 --- a/librashader-reflect/src/error.rs +++ b/librashader-reflect/src/error.rs @@ -12,12 +12,32 @@ pub enum ShaderCompileError { ShaderCInitError, } +#[derive(Debug)] +pub enum SemanticsErrorKind { + InvalidUniformBufferSize(usize), + InvalidPushBufferSize(usize), + InvalidLocation(u32), + InvalidDescriptorSet(u32), + InvalidInputCount(usize), + InvalidOutputCount(usize), + InvalidBinding(u32), + InvalidResourceType, +} + #[derive(Error, Debug)] pub enum ShaderReflectError { #[error("shader")] NagaCompileError(#[from] naga::front::spv::Error), #[error("spirv")] SpirvCrossError(#[from] spirv_cross::ErrorCode), + #[error("error when verifying vertex semantics")] + VertexSemanticError(SemanticsErrorKind), + #[error("error when verifying texture semantics")] + FragmentSemanticError(SemanticsErrorKind), + #[error("vertx and fragment shader must have same binding")] + MismatchedUniformBuffer { vertex: Option, fragment: Option }, + #[error("binding exceeded max")] + InvalidBinding(u32) } impl From> for ShaderCompileError { diff --git a/librashader-reflect/src/front/shaderc.rs b/librashader-reflect/src/front/shaderc.rs index ed8e354..84a993d 100644 --- a/librashader-reflect/src/front/shaderc.rs +++ b/librashader-reflect/src/front/shaderc.rs @@ -101,7 +101,7 @@ fn get_shaderc_options() -> Result, ShaderCompileError> pub fn compile_spirv(source: &ShaderSource) -> Result { let compiler = shaderc::Compiler::new().ok_or(ShaderCompileError::ShaderCInitError)?; - let name = source.name.as_deref().unwrap_or("shader.glsl"); + let name = source.name.as_deref().unwrap_or("shader.slang"); let options = get_shaderc_options()?; let vertex = compiler.compile_into_spirv( diff --git a/librashader-reflect/src/reflect/cross.rs b/librashader-reflect/src/reflect/cross.rs new file mode 100644 index 0000000..87b8d6f --- /dev/null +++ b/librashader-reflect/src/reflect/cross.rs @@ -0,0 +1,205 @@ +use crate::error::{ShaderReflectError, SemanticsErrorKind}; +use crate::front::shaderc::GlslangCompilation; +use crate::reflect::semantics::{MAX_BINDING_NUM, MAX_BINDINGS_COUNT, ShaderReflection}; +use crate::reflect::ReflectShader; +use spirv_cross::spirv::{Ast, Decoration, Module, ShaderResources}; +use std::fmt::Debug; +use spirv_cross::ErrorCode; + +pub struct CrossReflect +where + T: spirv_cross::spirv::Target, +{ + vertex: Ast, + fragment: Ast, +} + +impl TryFrom for CrossReflect +where + T: spirv_cross::spirv::Target, + Ast: spirv_cross::spirv::Compile, + Ast: spirv_cross::spirv::Parse, +{ + type Error = ShaderReflectError; + + fn try_from(value: GlslangCompilation) -> Result { + let vertex_module = Module::from_words(value.vertex.as_binary()); + let fragment_module = Module::from_words(value.fragment.as_binary()); + + let vertex = Ast::parse(&vertex_module)?; + let fragment = Ast::parse(&fragment_module)?; + + Ok(CrossReflect { vertex, fragment }) + } +} +impl CrossReflect + where + T: spirv_cross::spirv::Target, + Ast: spirv_cross::spirv::Compile, + Ast: spirv_cross::spirv::Parse, +{ + fn validate(&self, vertex_res: &ShaderResources, fragment_res: &ShaderResources) -> Result<(), ShaderReflectError> { + if !vertex_res.sampled_images.is_empty() + || !vertex_res.storage_buffers.is_empty() + || !vertex_res.subpass_inputs.is_empty() + || !vertex_res.storage_images.is_empty() + || !vertex_res.atomic_counters.is_empty() + { + return Err(ShaderReflectError::VertexSemanticError( + SemanticsErrorKind::InvalidResourceType, + )); + } + + if !fragment_res.storage_buffers.is_empty() + || !fragment_res.subpass_inputs.is_empty() + || !fragment_res.storage_images.is_empty() + || !fragment_res.atomic_counters.is_empty() + { + return Err(ShaderReflectError::FragmentSemanticError( + SemanticsErrorKind::InvalidResourceType, + )); + } + + let vert_inputs = vertex_res.stage_inputs.len(); + if vert_inputs != 2 { + return Err(ShaderReflectError::VertexSemanticError( + SemanticsErrorKind::InvalidInputCount(vert_inputs), + )); + } + + let frag_outputs = fragment_res.stage_outputs.len(); + if frag_outputs != 1 { + return Err(ShaderReflectError::FragmentSemanticError( + SemanticsErrorKind::InvalidOutputCount(frag_outputs), + )); + } + + let fragment_location = self.fragment.get_decoration(fragment_res.stage_outputs[0].id, Decoration::Location)?; + if fragment_location != 0 { + return Err(ShaderReflectError::FragmentSemanticError( + SemanticsErrorKind::InvalidLocation(fragment_location), + )); + } + + let mut vert_mask = vertex_res.stage_inputs.iter() + .try_fold(0, |mask, input| { + Ok::(mask | 1 << self.vertex.get_decoration(input.id, Decoration::Location)?) + })?; + if vert_mask != 0x3 { + return Err(ShaderReflectError::VertexSemanticError( + SemanticsErrorKind::InvalidLocation(vert_mask), + )); + } + + if vertex_res.uniform_buffers.len() > 1 { + return Err(ShaderReflectError::VertexSemanticError( + SemanticsErrorKind::InvalidUniformBufferSize(vertex_res.uniform_buffers.len()), + )); + } + + if vertex_res.push_constant_buffers.len() > 1 { + return Err(ShaderReflectError::VertexSemanticError( + SemanticsErrorKind::InvalidUniformBufferSize(vertex_res.push_constant_buffers.len()), + )); + } + + if fragment_res.uniform_buffers.len() > 1 { + return Err(ShaderReflectError::FragmentSemanticError( + SemanticsErrorKind::InvalidUniformBufferSize(fragment_res.uniform_buffers.len()), + )); + } + + if fragment_res.push_constant_buffers.len() > 1 { + return Err(ShaderReflectError::FragmentSemanticError( + SemanticsErrorKind::InvalidUniformBufferSize(fragment_res.push_constant_buffers.len()), + )); + } + Ok(()) + } +} + +impl ReflectShader for CrossReflect +where + T: spirv_cross::spirv::Target, + Ast: spirv_cross::spirv::Compile, + Ast: spirv_cross::spirv::Parse, +{ + fn reflect(&self) -> Result { + let vertex_res = self.vertex.get_shader_resources()?; + let fragment_res = self.fragment.get_shader_resources()?; + self.validate(&vertex_res, &fragment_res)?; + + let vertex_ubo = vertex_res.uniform_buffers.first().map(|f| f.id); + let fragment_ubo = fragment_res.uniform_buffers.first().map(|f| f.id); + + let vertex_push = vertex_res.push_constant_buffers.first().map(|f| f.id); + let fragment_push = fragment_res.push_constant_buffers.first().map(|f| f.id); + + if let Some(ubo) = vertex_ubo { + let desc_set = self.vertex.get_decoration(ubo, Decoration::DescriptorSet)?; + if desc_set != 0 { + return Err(ShaderReflectError::VertexSemanticError(SemanticsErrorKind::InvalidDescriptorSet(desc_set))) + } + } + + if let Some(ubo) = fragment_ubo { + let desc_set = self.fragment.get_decoration(ubo, Decoration::DescriptorSet)?; + if desc_set != 0 { + return Err(ShaderReflectError::FragmentSemanticError(SemanticsErrorKind::InvalidDescriptorSet(desc_set))) + } + } + + let vertex_ubo_binding = vertex_ubo.map(|s| self.vertex.get_decoration(s, Decoration::Binding)) + .map_or(Ok(None), |v| v.map(Some))?; + + let fragment_ubo_binding = vertex_ubo.map(|s| self.fragment.get_decoration(s, Decoration::Binding)) + .map_or(Ok(None), |v| v.map(Some))?; + + match (vertex_ubo_binding, fragment_ubo_binding) { + (Some(vertex), Some(fragment)) => { + if vertex != fragment { + return Err(ShaderReflectError::MismatchedUniformBuffer { + vertex: vertex_ubo_binding, + fragment: fragment_ubo_binding + }) + } + + if vertex >= MAX_BINDINGS_COUNT { + return Err(ShaderReflectError::InvalidBinding(vertex)) + } + } + (Some(vertex), None) => { + if vertex >= MAX_BINDINGS_COUNT { + return Err(ShaderReflectError::VertexSemanticError(SemanticsErrorKind::InvalidBinding(vertex))); + } + } + (None, Some(fragment)) => { + if fragment >= MAX_BINDINGS_COUNT { + return Err(ShaderReflectError::FragmentSemanticError(SemanticsErrorKind::InvalidBinding(vertex))); + } + } + (None, None) => {} + } + + // todo: slang_reflection:490 + todo!() + } +} + +#[cfg(test)] +mod test { + use crate::reflect::cross::CrossReflect; + use rspirv::binary::Disassemble; + use spirv_cross::{glsl, hlsl}; + + #[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 mut loader = rspirv::dr::Loader::new(); + // rspirv::binary::parse_words(spirv.fragment.as_binary(), &mut loader).unwrap(); + // let module = loader.module(); + // println!("{:#}", module.disassemble()); + } +} diff --git a/librashader-reflect/src/reflect/mod.rs b/librashader-reflect/src/reflect/mod.rs index d6cfd91..1c1e7f1 100644 --- a/librashader-reflect/src/reflect/mod.rs +++ b/librashader-reflect/src/reflect/mod.rs @@ -1,2 +1,10 @@ +use crate::error::ShaderReflectError; +use crate::reflect::semantics::ShaderReflection; + +mod cross; mod naga; -mod spirv_cross; +pub mod semantics; + +pub trait ReflectShader { + fn reflect(&self) -> Result; +} diff --git a/librashader-reflect/src/reflect/naga.rs b/librashader-reflect/src/reflect/naga.rs index c891228..9209686 100644 --- a/librashader-reflect/src/reflect/naga.rs +++ b/librashader-reflect/src/reflect/naga.rs @@ -26,8 +26,12 @@ impl TryFrom for NagaReflect { fn try_from(value: GlslangCompilation) -> Result { let ops = Options::default(); - let vertex = naga::front::spv::parse_u8_slice(value.vertex.as_binary_u8(), &ops)?; - let fragment = naga::front::spv::parse_u8_slice(value.fragment.as_binary_u8(), &ops)?; + let vertex = + naga::front::spv::Parser::new(value.vertex.as_binary().to_vec().into_iter(), &ops) + .parse()?; + let fragment = + naga::front::spv::Parser::new(value.fragment.as_binary().to_vec().into_iter(), &ops) + .parse()?; Ok(NagaReflect { vertex, fragment }) } } @@ -36,15 +40,11 @@ impl TryFrom for NagaReflect { mod test { use crate::reflect::naga::NagaReflect; use naga::front::spv::Options; + use rspirv::binary::Disassemble; #[test] pub fn test_into() { - let result = librashader_preprocess::load_shader_source( - "../test/slang-shaders/blurs/shaders/royale/blur3x3-last-pass.slang", - ) - .unwrap(); + let result = librashader_preprocess::load_shader_source("../test/basic.slang").unwrap(); let spirv = crate::front::shaderc::compile_spirv(&result).unwrap(); - - println!("{:?}", NagaReflect::try_from(spirv)) } } diff --git a/librashader-reflect/src/reflect/semantics.rs b/librashader-reflect/src/reflect/semantics.rs new file mode 100644 index 0000000..572a11f --- /dev/null +++ b/librashader-reflect/src/reflect/semantics.rs @@ -0,0 +1,41 @@ +use crate::error::ShaderReflectError; + +pub const BASE_SEMANTICS_COUNT: usize = 5; +pub const MAX_BINDINGS_COUNT: u32 = 16; + +#[repr(i32)] +pub enum VariableSemantics { + // mat4, MVP + MVP = 0, + // vec4, viewport size of current pass + Output = 1, + // vec4, viewport size of final pass + FinalViewport = 2, + // uint, frame count with modulo + FrameCount = 3, + // int, frame direction + FrameDirection = 4, + // float, user defined parameter, array + FloatParameter = 5, +} + +#[repr(i32)] +pub enum TextureSemantics { + Original = 0, + Source = 1, + OriginalHistory = 2, + PassOutput = 3, + PassFeedback = 4, + User = 5, +} + +pub struct BufferReflection { + pub binding: Option, + pub size: usize, + pub stage_mask: u32, +} + +pub struct ShaderReflection { + pub ubo: Option, + pub push_constant: Option, +} diff --git a/librashader-reflect/src/reflect/spirv_cross.rs b/librashader-reflect/src/reflect/spirv_cross.rs deleted file mode 100644 index 405ef77..0000000 --- a/librashader-reflect/src/reflect/spirv_cross.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::error::ShaderReflectError; -use crate::front::shaderc::GlslangCompilation; -use spirv_cross::spirv::{Ast, Module}; -use std::fmt::Debug; - -pub struct SpirvCrossReflect -where - T: spirv_cross::spirv::Target, -{ - vertex: Ast, - fragment: Ast, -} - -impl TryFrom for SpirvCrossReflect -where - T: spirv_cross::spirv::Target, - Ast: spirv_cross::spirv::Compile, - Ast: spirv_cross::spirv::Parse, -{ - type Error = ShaderReflectError; - - fn try_from(value: GlslangCompilation) -> Result { - let vertex_module = Module::from_words(value.vertex.as_binary()); - let fragment_module = Module::from_words(value.fragment.as_binary()); - - let vertex = Ast::parse(&vertex_module)?; - let fragment = Ast::parse(&fragment_module)?; - Ok(SpirvCrossReflect { vertex, fragment }) - } -} - -#[cfg(test)] -mod test { - use crate::reflect::spirv_cross::SpirvCrossReflect; - use spirv_cross::glsl; - - #[test] - pub fn test_into() { - let result = librashader_preprocess::load_shader_source( - "../test/slang-shaders/blurs/shaders/royale/blur3x3-last-pass.slang", - ) - .unwrap(); - let spirv = crate::front::shaderc::compile_spirv(&result).unwrap(); - SpirvCrossReflect::::try_from(spirv).unwrap(); - } -} diff --git a/test/basic.slang b/test/basic.slang new file mode 100644 index 0000000..61a1505 --- /dev/null +++ b/test/basic.slang @@ -0,0 +1,32 @@ +#version 450 +// 450 or 310 es are recommended + +layout(set = 0, binding = 0, std140) uniform UBO +{ + mat4 MVP; + vec4 SourceSize; // Not used here, but doesn't hurt + float ColorMod; +}; + +#pragma name StockShader +#pragma format R8G8B8A8_UNORM +#pragma parameter ColorMod "Color intensity" 1.0 0.1 2.0 0.1 + +#pragma stage vertex +layout(location = 0) in vec4 Position; +layout(location = 1) in vec2 TexCoord; +layout(location = 0) out vec2 vTexCoord; +void main() +{ + gl_Position = MVP * Position; + vTexCoord = TexCoord; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(binding = 1) uniform sampler2D Source; +void main() +{ + FragColor = texture(Source, vTexCoord) * ColorMod; +} \ No newline at end of file