commit 0755b4f807c5ea1a8363ea94fc9c9ad4fd74cd8d Author: Tom Gowan Date: Fri Apr 26 13:00:38 2019 +1000 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f88dba --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..39bea5e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "shade_runner" +version = "0.1.0" +authors = ["Tom Gowan "] +edition = "2018" + +[dependencies] +notify = "4" +shaderc = "0.3" +spirv-reflect = "0.2" +vulkano = "0.11" + +[dev-dependencies] +vulkano = "0.11" +color-backtrace = "0.1" +difference = "2" + +[patch.crates-io] +vulkano = { git = "https://github.com/mitchmindtree/vulkano", branch = "nannou_patches" } diff --git a/examples/compile.rs b/examples/compile.rs new file mode 100644 index 0000000..46be1da --- /dev/null +++ b/examples/compile.rs @@ -0,0 +1,13 @@ +use shade_runner as sr; +use std::path::PathBuf; + +fn main() { + let project_root = std::env::current_dir().expect("failed to get root directory"); + let mut vert_path = project_root.clone(); + vert_path.push(PathBuf::from("examples/shaders/vert.glsl")); + let mut frag_path = project_root.clone(); + frag_path.push(PathBuf::from("examples/shaders/frag.glsl")); + let shader = sr::load(vert_path, frag_path).expect("Failed to compile"); + let vulkano_entry = sr::parse(&shader); + dbg!(vulkano_entry); +} diff --git a/examples/shaders/frag.glsl b/examples/shaders/frag.glsl new file mode 100644 index 0000000..76eaeac --- /dev/null +++ b/examples/shaders/frag.glsl @@ -0,0 +1,7 @@ +#version 450 + +layout(location = 0) out vec4 f_color; + +void main() { + f_color = vec4(0.0, 0.5, 1.0, 1.0); +} diff --git a/examples/shaders/vert.glsl b/examples/shaders/vert.glsl new file mode 100644 index 0000000..874d199 --- /dev/null +++ b/examples/shaders/vert.glsl @@ -0,0 +1,10 @@ +#version 450 + +layout(location = 0) in vec2 position; + +void main() { + vec2 p = position; + p.x += 0.2; + gl_Position = vec4(p, 0.0, 1.0); +} + diff --git a/src/compiler.rs b/src/compiler.rs new file mode 100644 index 0000000..60bdf56 --- /dev/null +++ b/src/compiler.rs @@ -0,0 +1,26 @@ +use std::fs::File; +use std::path::Path; +use shaderc::ShaderKind; +use std::io::Read; + +pub fn compile(path: T, shader_kind: ShaderKind) -> shaderc::Result> +where + T: AsRef, +{ + // TODO Probably shouldn't create this every time. + let mut compiler = shaderc::Compiler::new().expect("failed to create compiler"); + let mut f = File::open(&path).expect("failed to open shader src"); + let mut src = String::new(); + f.read_to_string(&mut src).expect("failed to read src"); + let mut options = shaderc::CompileOptions::new().unwrap(); + options.add_macro_definition("EP", Some("main")); + let result = compiler.compile_into_spirv( + src.as_str(), + shader_kind, + path.as_ref().to_str().expect("failed to make path string"), + "main", + None, + )?; + let data = result.as_binary(); + Ok(data.to_owned()) +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..b8aa966 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,4 @@ +#[derive(Debug)] +pub enum Error { + Compile(shaderc::Error), +} diff --git a/src/layouts.rs b/src/layouts.rs new file mode 100644 index 0000000..a48f90d --- /dev/null +++ b/src/layouts.rs @@ -0,0 +1,125 @@ +use crate::vk; +use vk::pipeline::shader::*; +use vk::descriptor::descriptor::*; +use vk::descriptor::pipeline_layout::*; +use crate::reflection::LayoutData; + +#[derive(Debug, Clone)] +pub struct Entry { + pub frag_input: FragInput, + pub frag_output: FragOutput, + pub frag_layout: FragLayout, + pub vert_input: VertInput, + pub vert_output: VertOutput, + pub vert_layout: VertLayout, +} + +#[derive(Debug, Clone)] +pub struct FragInput { + pub inputs: Vec, +} + +unsafe impl ShaderInterfaceDef for FragInput { + type Iter = FragInputIter; + + fn elements(&self) -> FragInputIter { + self.inputs.clone().into_iter() + } +} + +pub type FragInputIter = std::vec::IntoIter; + +#[derive(Debug, Clone)] +pub struct FragOutput { + pub outputs: Vec, +} + +unsafe impl ShaderInterfaceDef for FragOutput { + type Iter = FragOutputIter; + + fn elements(&self) -> FragOutputIter { + self.outputs.clone().into_iter() + } +} + +pub type FragOutputIter = std::vec::IntoIter; + +// Layout same as with vertex shader. +#[derive(Debug, Clone)] +pub struct FragLayout { + pub stages: ShaderStages, + pub layout_data: LayoutData, +} +unsafe impl PipelineLayoutDesc for FragLayout { + fn num_sets(&self) -> usize { + self.layout_data.num_sets + } + fn num_bindings_in_set(&self, set: usize) -> Option { + self.layout_data.num_bindings.get(&set).map(|&i| i) + } + fn descriptor(&self, _set: usize, _binding: usize) -> Option { + None + } + fn num_push_constants_ranges(&self) -> usize { + 0 + } + fn push_constants_range(&self, _num: usize) -> Option { + None + } +} + +#[derive(Debug, Clone)] +pub struct VertInput { + pub inputs: Vec, +} + +unsafe impl ShaderInterfaceDef for VertInput { + type Iter = VertInputIter; + + fn elements(&self) -> VertInputIter { + self.inputs.clone().into_iter() + } +} + +pub type VertInputIter = std::vec::IntoIter; + +#[derive(Debug, Clone)] +pub struct VertOutput { + pub outputs: Vec, +} + +unsafe impl ShaderInterfaceDef for VertOutput { + type Iter = VertOutputIter; + + fn elements(&self) -> VertOutputIter { + self.outputs.clone().into_iter() + } +} + +pub type VertOutputIter = std::vec::IntoIter; + +// This structure describes layout of this stage. +#[derive(Debug, Copy, Clone)] +pub struct VertLayout(pub ShaderStages); +unsafe impl PipelineLayoutDesc for VertLayout { + // Number of descriptor sets it takes. + fn num_sets(&self) -> usize { + 0 + } + // Number of entries (bindings) in each set. + fn num_bindings_in_set(&self, _set: usize) -> Option { + None + } + // Descriptor descriptions. + fn descriptor(&self, _set: usize, _binding: usize) -> Option { + None + } + // Number of push constants ranges (think: number of push constants). + fn num_push_constants_ranges(&self) -> usize { + 0 + } + // Each push constant range in memory. + fn push_constants_range(&self, _num: usize) -> Option { + None + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f24bf7a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,32 @@ +mod compiler; +mod error; +mod reflection; +mod srvk; +mod layouts; + +pub use layouts::*; +pub use reflection::LayoutData; + +use spirv_reflect as sr; +use vulkano as vk; +use std::path::Path; +use error::Error; +use shaderc::ShaderKind; + +pub struct CompiledShaders { + pub vertex: Vec, + pub fragment: Vec, +} + +pub fn load(vertex: T, fragment: T) -> Result +where + T: AsRef, +{ + let vertex = compiler::compile(vertex, ShaderKind::Vertex).map_err(|e| Error::Compile(e))?; + let fragment = compiler::compile(fragment, ShaderKind::Fragment).map_err(|e| Error::Compile(e))?; + Ok(CompiledShaders{ vertex, fragment }) +} + +pub fn parse(code: &CompiledShaders) -> Entry { + reflection::create_entry(code) +} diff --git a/src/reflection.rs b/src/reflection.rs new file mode 100644 index 0000000..3de6284 --- /dev/null +++ b/src/reflection.rs @@ -0,0 +1,90 @@ +use crate::sr; +use crate::srvk::SpirvTy; +use std::borrow::Cow; +use crate::vk::pipeline::shader::ShaderInterfaceDefEntry; +use crate::vk::descriptor::descriptor::ShaderStages; +use std::collections::HashMap; +use crate::CompiledShaders; +use crate::layouts::*; + +pub struct ShaderInterfaces { + pub inputs: Vec, + pub outputs: Vec, +} + +#[derive(Debug, Clone, Default)] +pub struct LayoutData { + pub num_sets: usize, + pub num_bindings: HashMap, +} + +pub fn create_entry(shaders: &CompiledShaders) -> Entry { + let vertex_interfaces = create_interfaces(&shaders.vertex); + let fragment_interfaces = create_interfaces(&shaders.fragment); + let frag_input = FragInput{ inputs: fragment_interfaces.inputs }; + let frag_output = FragOutput{ outputs: fragment_interfaces.outputs }; + let frag_layout = FragLayout { + stages: ShaderStages { + fragment: true, + ..ShaderStages::none() + }, + layout_data: Default::default(), + }; + let vert_input = VertInput{ inputs: vertex_interfaces.inputs }; + let vert_output = VertOutput{ outputs: vertex_interfaces.outputs }; + let vert_layout = VertLayout(ShaderStages { + vertex: true, + ..ShaderStages::none() + }); + Entry { + frag_input, + frag_output, + vert_input, + vert_output, + frag_layout, + vert_layout, + } + +} + +fn create_interfaces(data: &[u32]) -> ShaderInterfaces { + sr::ShaderModule::load_u32_data(data) + .map(|m| { + let inputs = m + .enumerate_input_variables(None) + .map(|inputs| { + inputs + .iter() + .filter(|i| { + !i.decoration_flags + .contains(sr::types::ReflectDecorationFlags::BUILT_IN) + }) + .map(|i| ShaderInterfaceDefEntry { + location: i.location..(i.location + 1), + format: SpirvTy::from(i.format).inner(), + name: Some(Cow::from(i.name.clone())), + }) + .collect::>() + }) + .expect("Failed to pass inputs"); + let outputs = m + .enumerate_output_variables(None) + .map(|outputs| { + outputs + .iter() + .filter(|i| { + !i.decoration_flags + .contains(sr::types::ReflectDecorationFlags::BUILT_IN) + }) + .map(|i| ShaderInterfaceDefEntry { + location: i.location..(i.location + 1), + format: SpirvTy::from(i.format).inner(), + name: Some(Cow::from(i.name.clone())), + }) + .collect::>() + }) + .expect("Failed to pass outputs"); + ShaderInterfaces { inputs, outputs } + }) + .expect("failed to load module") +} diff --git a/src/srvk.rs b/src/srvk.rs new file mode 100644 index 0000000..ef4ea89 --- /dev/null +++ b/src/srvk.rs @@ -0,0 +1,100 @@ +use crate::sr; +use crate::vk; +use vk::descriptor::descriptor::*; +use vk::pipeline::shader::ShaderInterfaceDefEntry; +use vk::format::Format; + +pub struct SpirvTy { + inner: T, +} + +pub struct DescriptorDescInfo { + descriptor_type: sr::types::ReflectDescriptorType, + image: sr::types::ReflectImageTraits, +} + +impl SpirvTy { + pub fn inner(self) -> T { + self.inner + } +} + +impl From for SpirvTy { + fn from(d: DescriptorDescInfo) -> Self { + use sr::types::ReflectDescriptorType as SR; + use DescriptorDescTy as VK; + let t = match d.descriptor_type { + SR::Undefined => unreachable!(), + SR::Sampler => VK::Sampler, + SR::CombinedImageSampler => VK::CombinedImageSampler(SpirvTy::from(d.image).inner()), + SR::SampledImage => unreachable!(), + SR::StorageImage => unreachable!(), + SR::UniformTexelBuffer => unreachable!(), + SR::StorageTexelBuffer => unreachable!(), + SR::UniformBuffer => unreachable!(), + SR::StorageBuffer => unreachable!(), + SR::UniformBufferDynamic => unreachable!(), + SR::StorageBufferDynamic => unreachable!(), + SR::InputAttachment => unreachable!(), + SR::AccelerationStructureNV => unreachable!(), + }; + SpirvTy { + inner: t, + } + } +} + +impl From for SpirvTy { + fn from(d: sr::types::ReflectImageTraits) -> Self { + let conv_array_layers = |a, d|{ + if a != 0 { + DescriptorImageDescArray::Arrayed{max_layers: Some(d)} + } else { + DescriptorImageDescArray::NonArrayed + } + }; + let t = DescriptorImageDesc { + sampled: d.sampled != 0, + dimensions: SpirvTy::from(d.dim).inner(), + format: Some(SpirvTy::from(d.image_format).inner()), + multisampled: d.ms != 0, + array_layers: conv_array_layers(d.arrayed, d.depth), + }; + SpirvTy{inner: t} + } +} + +impl From for SpirvTy { + fn from(d: sr::types::variable::ReflectDimension) -> Self { + unimplemented!() + } +} + +impl From for SpirvTy { + fn from(d: sr::types::image::ReflectImageFormat) -> Self { + unimplemented!() + } +} + +impl From for SpirvTy { + fn from(f: sr::types::ReflectFormat) -> Self { + use sr::types::ReflectFormat::*; + use Format::*; + let t = match f { + Undefined => unreachable!(), + R32_UINT => R32Uint, + R32_SINT => R32Sint, + R32_SFLOAT => R32Sfloat, + R32G32_UINT => R32G32Uint, + R32G32_SINT => R32G32Sint, + R32G32_SFLOAT => R32G32Sfloat, + R32G32B32_UINT => R32G32B32Uint, + R32G32B32_SINT => R32G32B32Sint, + R32G32B32_SFLOAT => R32G32B32Sfloat, + R32G32B32A32_UINT => R32G32B32A32Uint, + R32G32B32A32_SINT => R32G32B32A32Sint, + R32G32B32A32_SFLOAT => R32G32B32A32Sfloat, + }; + SpirvTy { inner: t } + } +} diff --git a/tests/shaders/frag1.glsl b/tests/shaders/frag1.glsl new file mode 100644 index 0000000..76eaeac --- /dev/null +++ b/tests/shaders/frag1.glsl @@ -0,0 +1,7 @@ +#version 450 + +layout(location = 0) out vec4 f_color; + +void main() { + f_color = vec4(0.0, 0.5, 1.0, 1.0); +} diff --git a/tests/shaders/frag2.glsl b/tests/shaders/frag2.glsl new file mode 100644 index 0000000..56071ba --- /dev/null +++ b/tests/shaders/frag2.glsl @@ -0,0 +1,13 @@ +#version 450 + +layout(location = 0) in vec4 cool; +layout(location = 1) in vec2 yep; +layout(location = 2) in float monkey; +layout(location = 0) out vec4 f_color; + +void main() { + vec4 t = cool; + t.yw += yep; + t.x -= monkey; + f_color = t; +} diff --git a/tests/shaders/vert1.glsl b/tests/shaders/vert1.glsl new file mode 100644 index 0000000..874d199 --- /dev/null +++ b/tests/shaders/vert1.glsl @@ -0,0 +1,10 @@ +#version 450 + +layout(location = 0) in vec2 position; + +void main() { + vec2 p = position; + p.x += 0.2; + gl_Position = vec4(p, 0.0, 1.0); +} + diff --git a/tests/shaders/vert2.glsl b/tests/shaders/vert2.glsl new file mode 100644 index 0000000..7ca071d --- /dev/null +++ b/tests/shaders/vert2.glsl @@ -0,0 +1,14 @@ +#version 450 + +layout(location = 0) in vec2 position; +layout(location = 0) out vec4 cool; +layout(location = 1) out vec2 yep; +layout(location = 2) out float monkey; + +void main() { + cool = vec4(0.0, 0.5, 1.0, 1.0); + yep = position; + monkey = 0.9; + gl_Position = vec4(yep, 0.0, 1.0); +} + diff --git a/tests/tests.rs b/tests/tests.rs new file mode 100644 index 0000000..4b96f38 --- /dev/null +++ b/tests/tests.rs @@ -0,0 +1,178 @@ +use color_backtrace; +use difference::{Changeset, Difference}; +use shade_runner::*; +use std::borrow::Cow; +use std::collections::HashMap; +use std::path::{Path, PathBuf}; +use vulkano::descriptor::descriptor::ShaderStages; +use vulkano::format::*; +use vulkano::pipeline::shader::ShaderInterfaceDefEntry; + +fn setup() { + color_backtrace::install(); +} + +fn difference(e: &str, t: &str) -> String { + let diffs = Changeset::new(&e, &t, ""); + diffs + .diffs + .iter() + .filter(|d| match d { + Difference::Add(_) => true, + Difference::Rem(_) => true, + _ => false, + }) + .map(|d| match d { + Difference::Add(a) => format!("add: {}", a), + Difference::Rem(a) => format!("remove: {}", a), + _ => "".to_string(), + }) + .collect::>() + .join("\n") +} + +fn parse(vertex: T, fragment: T) -> shade_runner::Entry +where + T: AsRef, +{ + let project_root = std::env::current_dir().expect("failed to get root directory"); + let mut path = project_root.clone(); + path.push(PathBuf::from("tests/shaders/")); + let mut vertex_path = path.clone(); + vertex_path.push(vertex); + let mut fragment_path = path.clone(); + fragment_path.push(fragment); + let shader = shade_runner::load(vertex_path, fragment_path).expect("Failed to compile"); + shade_runner::parse(&shader) +} + +#[test] +fn test_shade1() { + setup(); + let target = Entry { + frag_input: FragInput { inputs: Vec::new() }, + frag_output: FragOutput { + outputs: vec![ShaderInterfaceDefEntry { + location: 0..1, + format: Format::R32G32B32A32Sfloat, + name: Some(Cow::Borrowed("f_color")), + }], + }, + frag_layout: FragLayout { + stages: ShaderStages { + fragment: true, + ..ShaderStages::none() + }, + layout_data: LayoutData { + num_sets: 0, + num_bindings: HashMap::new(), + }, + }, + vert_input: VertInput { + inputs: vec![ShaderInterfaceDefEntry { + location: 0..1, + format: Format::R32G32Sfloat, + name: Some(Cow::Borrowed("position")), + }], + }, + vert_output: VertOutput { + outputs: Vec::new(), + }, + vert_layout: VertLayout(ShaderStages { + vertex: true, + ..ShaderStages::none() + }), + }; + let entry = parse("vert1.glsl", "frag1.glsl"); + let entry = format!("{:?}", entry); + let target = format!("{:?}", target); + assert_eq!( + &entry, + &target, + "\n\nDifference: {}", + difference(&entry, &target) + ); +} + +#[test] +fn test_shade2() { + setup(); + let target = Entry { + frag_input: FragInput { + inputs: vec![ + ShaderInterfaceDefEntry { + location: 0..1, + format: Format::R32G32B32A32Sfloat, + name: Some(Cow::Borrowed("cool")), + }, + ShaderInterfaceDefEntry { + location: 1..2, + format: Format::R32G32Sfloat, + name: Some(Cow::Borrowed("yep")), + }, + ShaderInterfaceDefEntry { + location: 2..3, + format: Format::R32Sfloat, + name: Some(Cow::Borrowed("monkey")), + }, + ], + }, + frag_output: FragOutput { + outputs: vec![ShaderInterfaceDefEntry { + location: 0..1, + format: Format::R32G32B32A32Sfloat, + name: Some(Cow::Borrowed("f_color")), + }], + }, + frag_layout: FragLayout { + stages: ShaderStages { + fragment: true, + ..ShaderStages::none() + }, + layout_data: LayoutData { + num_sets: 0, + num_bindings: HashMap::new(), + }, + }, + vert_input: VertInput { + inputs: vec![ShaderInterfaceDefEntry { + location: 0..1, + format: Format::R32G32Sfloat, + name: Some(Cow::Borrowed("position")), + }], + }, + vert_output: VertOutput { + outputs: vec![ + ShaderInterfaceDefEntry { + location: 0..1, + format: Format::R32G32B32A32Sfloat, + name: Some(Cow::Borrowed("cool")), + }, + ShaderInterfaceDefEntry { + location: 1..2, + format: Format::R32G32Sfloat, + name: Some(Cow::Borrowed("yep")), + }, + ShaderInterfaceDefEntry { + location: 2..3, + format: Format::R32Sfloat, + name: Some(Cow::Borrowed("monkey")), + }, + ], + }, + vert_layout: VertLayout(ShaderStages { + vertex: true, + ..ShaderStages::none() + }), + }; + let entry = parse("vert2.glsl", "frag2.glsl"); + let entry = format!("{:?}", entry); + let target = format!("{:?}", target); + assert_eq!( + &entry, + &target, + "\n\nDifference: {}", + difference(&entry, &target) + ); +} +