tirbofish/dropbear · commit
d925ed02394cafa0480621bd90eaae28884025b2
refactor: changed from wgsl to slang (except for mipmap, and one is still coming along) Unverified
@@ -12,7 +12,7 @@ readme.workspace = true dropbear_future-queue = { path = "../dropbear_future-queue" } dropbear-traits = { path = "../dropbear-traits" } dropbear-macro = { path = "../dropbear-macro" } -slank = { path = "../slank", features = ["download-slang"] } +slank = { path = "../slank", features = ["download-slang", "use-wgpu"] } anyhow.workspace = true app_dirs2.workspace = true @@ -54,3 +54,6 @@ rfd.workspace = true version = "0.25" default-features = false features = ["png", "jpeg"] + +[build-dependencies] +slank = { path = "../slank", features = ["download-slang"] } @@ -0,0 +1,19 @@ +use slank::{SlangShaderBuilder, SlangTarget}; + +fn main() { + // to copy paste: + // let shader = Shader::from_slang(graphics.clone(), &slank::compiled::CompiledSlangShader::from_bytes("light cube", include_slang!("light_cube"))); + + SlangShaderBuilder::new("light_cube") + .add_source_path("src/pipelines/shaders/light.slang").unwrap() + .compile_to_out_dir(SlangTarget::SpirV).unwrap(); + + SlangShaderBuilder::new("blit_shader") + .add_source_path("src/pipelines/shaders/blit.slang").unwrap() + .compile_to_out_dir(SlangTarget::SpirV).unwrap(); + + // unable to do mipmap.slang because it required texture_storage_2d, one write and one read. + // just wasn't possible with slang :( + + +} @@ -1,4 +1,6 @@ -use crate::texture::Texture; +use slank::{include_slang, utils::WgpuUtils}; + +use crate::{texture::Texture}; pub struct MipMapper { blit_mipmap: wgpu::RenderPipeline, @@ -10,10 +12,10 @@ pub struct MipMapper { impl MipMapper { pub fn new(device: &wgpu::Device) -> Self { - let blit_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("mipmap blit shader"), - source: wgpu::ShaderSource::Wgsl(include_str!("pipelines/shaders/blit.wgsl").into()), - }); + let blit_shader = device.create_shader_module(slank::compiled::CompiledSlangShader::from_bytes( + "mipmap blit_shader", + include_slang!("blit_shader") + ).create_wgpu_shader()); // Keep this SRGB so we can render directly into the SRGB textures we create for materials. let blit_format = Texture::TEXTURE_FORMAT; @@ -88,10 +90,12 @@ impl MipMapper { bind_group_layouts: &[&storage_texture_layout], push_constant_ranges: &[], }); + let compute_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("mipmap compute shader"), source: wgpu::ShaderSource::Wgsl(include_str!("pipelines/shaders/mipmap.wgsl").into()), }); + let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: Some("Mipmapper"), layout: Some(&pipeline_layout), @@ -1,6 +1,7 @@ use std::sync::Arc; use std::mem::size_of; use glam::DMat4; +use slank::include_slang; use wgpu::{BufferAddress, VertexAttribute, VertexFormat}; use crate::buffer::{StorageBuffer, UniformBuffer}; use crate::entity::{EntityTransform, Transform}; @@ -26,11 +27,7 @@ pub struct LightCubePipeline { impl DropbearShaderPipeline for LightCubePipeline { fn new(graphics: Arc<SharedGraphicsContext>) -> Self { - let shader = Shader::new( - graphics.clone(), - include_str!("shaders/light.wgsl"), - Some("light cube shader"), - ); + let shader = Shader::from_slang(graphics.clone(), &slank::compiled::CompiledSlangShader::from_bytes("light cube", include_slang!("light_cube"))); let pipeline_layout = graphics.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("light cube pipeline layout"), @@ -0,0 +1,35 @@ +#include "dropbear.slang" + +struct VertexOutput { + float4 clip_position : SV_Position; + + [[vk_location(0)]] + float2 uv; +}; + +[[vk_binding(0, 0)]] +Texture2D tex; + +[[vk_binding(1, 0)]] +SamplerState tex_sampler; + +[shader("vertex")] +VertexOutput vs_main( + uint in_vertex_index: SV_VulkanVertexID +) { + VertexOutput output; + + float x = float((in_vertex_index << 1u) & 2u); + float y = float(in_vertex_index & 2u); + + output.clip_position = float4(x * 2.0 - 1.0, y * 2.0 - 1.0, 0.0, 1.0); + output.uv = float2(x, 1.0 - y); + + return output; +} + +[shader("fragment")] +float4 fs_main(VertexOutput input) : SV_Target0 { + float4 s = tex.Sample(tex_sampler, input.uv); + return s; +} @@ -13,7 +13,7 @@ fn vs_main( @builtin(vertex_index) in_vertex_index: u32, ) -> VertexOutput { var out: VertexOutput; - // Create fullscreen triangle + let x = f32((in_vertex_index << 1u) & 2u); let y = f32(in_vertex_index & 2u); out.clip_position = vec4<f32>(x * 2.0 - 1.0, y * 2.0 - 1.0, 0.0, 1.0); @@ -0,0 +1,32 @@ +/// A standard light descriptor struct. +struct Light { + float4 position; + float4 direction; // x, y, z, outer_cutoff_angle + float4 color; // r, g, b, light_type (0, 1, 2) + + float constant; + float lin; + float quadratic; + + float cutoff; +}; + +struct CameraUniform { + float4 view_pos; + float4x4 view_proj; +}; + +/// Converts a wgpu matrix (in the form of 4 float4) into a matrix usable for Slang. +float4x4 vs_input_matrix( + float4 mat1, + float4 mat2, + float4 mat3, + float4 mat4 +) { + return transpose(float4x4( + mat1, + mat2, + mat3, + mat4 + )); +} @@ -1,48 +1,42 @@ -struct Light { - float4 position; - float4 direction; // x, y, z, outer_cutoff_angle - float4 color; // r, g, b, light_type (0, 1, 2) - - float constant; - float lin; - float quadratic; - - float cutoff; -}; - - -struct CameraUniform { - float4 view_pos; - float4x4 view_proj; -}; +#include "dropbear.slang" [[vk::binding(0, 0)]] ConstantBuffer<CameraUniform> camera; -[[vk::binding(1, 0)]] +[[vk::binding(0, 1)]] ConstantBuffer<Light> light; struct VertexInput { + [[vk::location(0)]] float3 position : POSITION; + + [[vk::location(5)]] float4 model_matrix_0 : TEXCOORD5; + + [[vk::location(6)]] float4 model_matrix_1 : TEXCOORD6; + + [[vk::location(7)]] float4 model_matrix_2 : TEXCOORD7; + + [[vk::location(8)]] float4 model_matrix_3 : TEXCOORD8; }; struct VertexOutput { float4 clip_position : SV_Position; + + [[vk::location(0)]] float3 color : COLOR0; }; - [shader("vertex")] VertexOutput vs_main(VertexInput input) { - float4x4 model_matrix = float4x4( + float4x4 model_matrix = vs_input_matrix( input.model_matrix_0, input.model_matrix_1, input.model_matrix_2, - input.model_matrix_3 + input.model_matrix_3, ); VertexOutput output; @@ -1,62 +0,0 @@ -// Shader for rendering the white cube for lighting (or diff depending on colour) - -struct Light { - position: vec4<f32>, - direction: vec4<f32>, // x, y, z, outer_cutoff_angle - color: vec4<f32>, // r, g, b, light_type (0, 1, 2) - constant: f32, - lin: f32, - quadratic: f32, - cutoff: f32, -} - -struct CameraUniform { - view_pos: vec4<f32>, - view_proj: mat4x4<f32>, -}; - -@group(0) @binding(0) -var<uniform> camera: CameraUniform; - -@group(1) @binding(0) -var<uniform> light: Light; - -struct InstanceInput { - @location(5) model_matrix_0: vec4<f32>, - @location(6) model_matrix_1: vec4<f32>, - @location(7) model_matrix_2: vec4<f32>, - @location(8) model_matrix_3: vec4<f32>, -} - -struct VertexInput { - @location(0) position: vec3<f32>, -}; - -struct VertexOutput { - @builtin(position) clip_position: vec4<f32>, - @location(0) color: vec3<f32>, -}; - -@vertex -fn vs_main( - model: VertexInput, - instance: InstanceInput, -) -> VertexOutput { - let model_matrix = mat4x4<f32>( - instance.model_matrix_0, - instance.model_matrix_1, - instance.model_matrix_2, - instance.model_matrix_3, - ); - var out: VertexOutput; - out.clip_position = camera.view_proj * model_matrix * vec4<f32>(model.position, 1.0); - out.color = light.color.xyz; - return out; -} - -// Fragment shaders - -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> { - return vec4<f32>(in.color, 1.0); -} @@ -3,6 +3,7 @@ use std::ops::Deref; use crate::graphics::SharedGraphicsContext; use std::sync::Arc; +use slank::{compiled::CompiledSlangShader, utils::WgpuUtils}; use wgpu::ShaderModule; /// A nice little struct that stored basic information about a WGPU shaders. @@ -47,6 +48,8 @@ impl Shader { log::debug!("Created new shaders under the label: {:?}", label); + slank::compiled::CompiledSlangShader::from_bytes("light cube", slank::include_slang!("light_cube")); + Self { label: match label { Some(label) => label.into(), @@ -56,4 +59,18 @@ impl Shader { content: shader_file_contents.to_string(), } } + + pub fn from_slang(graphics: Arc<SharedGraphicsContext>, shader: &CompiledSlangShader) -> Self { + let module = graphics + .device + .create_shader_module(shader.create_wgpu_shader()); + + log::debug!("Created new shaders [slang] under the label: {}", shader.label()); + + Self { + label: shader.label().clone(), + module, + content: String::from_utf8(shader.source.clone()).unwrap_or_default(), + } + } } @@ -7,7 +7,7 @@ fn main() -> anyhow::Result<()> { let path = find_or_download_slangc()?; println!("cargo:rustc-env=SLANG_DIR={}", path.display()); - println!("cargo:warning=Found slangc at: {}", path.display()); + // println!("cargo:warning=Found slangc at: {}", path.display()); Ok(()) } @@ -127,7 +127,7 @@ fn check_cached_download() -> Option<PathBuf> { fn download_slang() -> anyhow::Result<PathBuf> { use std::fs; - println!("cargo:warning=Downloading slangc compiler..."); + // println!("cargo:warning=Downloading slangc compiler..."); let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); @@ -147,10 +147,10 @@ fn download_slang() -> anyhow::Result<PathBuf> { let archive_path = cache_dir.join(&archive_name); if !archive_path.exists() { - println!("cargo:warning=Downloading from: {}", download_url); + // println!("cargo:warning=Downloading from: {}", download_url); download_file(&download_url, &archive_path)?; } else { - println!("cargo:warning=Using cached download: {}", archive_path.display()); + // println!("cargo:warning=Using cached download: {}", archive_path.display()); } extract_archive(&archive_path, &cache_dir)?; @@ -1,6 +1,7 @@ use std::path::Path; pub struct CompiledSlangShader { + pub(crate) label: String, args: String, pub source: Vec<u8>, } @@ -8,10 +9,11 @@ pub struct CompiledSlangShader { impl CompiledSlangShader { /// Internal: Creates a new instance of [CompiledSlangShader] from the compiled args /// of [crate::SlangShaderBuilder] - pub(crate) fn new(args: String, source: Vec<u8>) -> Self { + pub(crate) fn new(label: String, args: String, source: Vec<u8>) -> Self { Self { args, - source + source, + label, } } @@ -19,6 +21,22 @@ impl CompiledSlangShader { pub fn args(&self) -> String { self.args.clone() } + + /// Creates a [CompiledSlangShader] from raw bytes. + /// + /// This is useful when loading shaders compiled at build time using the + /// `include_slang!` macro. + pub fn from_bytes(label: &str, source: &[u8]) -> Self { + Self { + label: label.to_string(), + args: String::new(), + source: source.to_vec(), + } + } + + pub fn label(&self) -> String { + self.label.clone() + } /// Writes the output to a file. pub fn output(&self, path: impl AsRef<Path>) -> std::io::Result<()> { @@ -1,9 +1,27 @@ -//! slank - slangc for rust +//! slank - slangc for rust. +//! +//! Compiles slang code during build and stores the shaders locally (or in the crate with [`include_slang`]) //! //! Check out [`SlangShaderBuilder`] to get started. -pub mod compiled; +/// Fetches the slang file (located in {OUT_DIR}/{label}.spv) (assuming it is compiled as .spv) +/// and includes the bytes of the file. +#[macro_export] +macro_rules! include_slang { + ($label:expr) => { + include_bytes!(concat!(env!("OUT_DIR"), "/", $label, ".spv")) + }; +} + +/// Fetches the path of the shader (with the same label) and returns it to you. +#[macro_export] +macro_rules! include_slang_path { + ($label:expr) => { + concat!(env!("OUT_DIR"), "/", $label, ".spv") + }; +} +pub mod compiled; pub mod utils; use std::path::{Path, PathBuf}; @@ -28,27 +46,46 @@ pub struct EntryPoint { /// /// This is the entry point of the library. /// # Usage -/// ```rust +/// +/// Add `slank` to your `[build-dependencies]`. +/// +/// In your `build.rs`: +/// ```rust,no_run /// use slank::{ShaderStage, SlangShaderBuilder, SlangTarget}; +/// use std::path::Path; /// -/// SlangShaderBuilder::new() -/// .add_source_str(include_str!("../test/light.slang")) -/// .entry_with_stage("vs_main", ShaderStage::Vertex) -/// .entry_with_stage("fs_main", ShaderStage::Fragment) -/// .build(SlangTarget::SpirV).unwrap(); +/// fn main() { +/// let out_dir = std::env::var("OUT_DIR").unwrap(); +/// let dest_path = Path::new(&out_dir).join("shader.spv"); /// +/// SlangShaderBuilder::new("shader_label") +/// .add_source_path("src/shader.slang").unwrap() +/// .entry_with_stage("vs_main", ShaderStage::Vertex) +/// .entry_with_stage("fs_main", ShaderStage::Fragment) +/// .build(SlangTarget::SpirV).unwrap() +/// .output(&dest_path).unwrap(); +/// +/// println!("cargo:rerun-if-changed=src/shader.slang"); +/// } +/// ``` +/// +/// Then in your main code: +/// ```rust,ignore +/// let shader_bytes = include_bytes!(concat!(env!("OUT_DIR"), "/shader.spv")); /// ``` pub struct SlangShaderBuilder { + label: String, sources: Vec<SourceFile>, entries: Vec<EntryPoint>, } impl SlangShaderBuilder { /// Creates a new instance of a [SlangShaderBuilder]. - pub fn new() -> Self { + pub fn new(label: &str) -> Self { Self { sources: Vec::new(), entries: Vec::new(), + label: label.to_string(), } } @@ -175,6 +212,15 @@ impl SlangShaderBuilder { self } + pub fn compile_to_out_dir(self, target: SlangTarget) -> anyhow::Result<()> { + let label = self.label.clone(); + let compiled = self.build(target)?; + let out_dir = std::env::var("OUT_DIR") + .map_err(|_| anyhow::anyhow!("OUT_DIR not set. Are you running this from build.rs?"))?; + let dest = Path::new(&out_dir).join(format!("{}.spv", label)); + compiled.output(&dest).map_err(Into::into) + } + pub fn build(self, target: SlangTarget) -> anyhow::Result<CompiledSlangShader> { let slang_dir = PathBuf::from(env!("SLANG_DIR")); let slangc_path = slang_dir.join("bin").join(if cfg!(windows) { "slangc.exe" } else { "slangc" }); @@ -234,20 +280,20 @@ impl SlangShaderBuilder { let output = cmd.output()?; if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr).to_string(); + let stderr = String::from_utf8_lossy(&output.stderr).to_string(); return Err(anyhow::anyhow!("Compilation error: {}", stderr)); } let binary_output = std::fs::read(&output_path)?; - Ok(CompiledSlangShader::new(args_record, binary_output)) + Ok(CompiledSlangShader::new(self.label, args_record, binary_output)) } } impl Default for SlangShaderBuilder { fn default() -> Self { - Self::new() + Self::new("no named shader") } } @@ -302,6 +348,7 @@ pub enum SlangTarget { // GLSL/Vulkan/SPIR-V Glsl, + /// Most optimal for WGPU and Vulkan/ SpirV, SpirVAssembly, @@ -467,13 +514,20 @@ mod tests { use super::*; #[test] - fn test_from_str() { - let shader = SlangShaderBuilder::new() - .add_source_str(include_str!("../../dropbear-engine/src/pipelines/shaders/light.slang")) + fn test_compile() { + let result = SlangShaderBuilder::new("light") + .add_source_str(include_str!("../test/light.slang")) .entry_with_stage("vs_main", ShaderStage::Vertex) .entry_with_stage("fs_main", ShaderStage::Fragment) - .build(SlangTarget::SpirV).unwrap(); + .build(SlangTarget::SpirV); + assert!(result.is_ok()); + } - shader.output("/home/tirbofish/shader.spv").unwrap(); + #[test] + fn test_from_bytes() { + let bytes = vec![1, 2, 3, 4]; + let shader = CompiledSlangShader::from_bytes("idk", &bytes); + assert_eq!(shader.source, bytes); + assert_eq!(shader.label(), "unknown"); } } @@ -2,14 +2,14 @@ #[cfg(feature = "use-wgpu")] pub trait WgpuUtils { /// Creates a new [`wgpu::ShaderModuleDescriptor`] for you to create your own shaders with. - fn create_wgpu_shader(&self, label: Option<&str>) -> wgpu::ShaderModuleDescriptor; + fn create_wgpu_shader(&self) -> wgpu::ShaderModuleDescriptor<'_>; } #[cfg(feature = "use-wgpu")] impl WgpuUtils for crate::CompiledSlangShader { - fn create_wgpu_shader(&self, label: Option<&str>) -> wgpu::ShaderModuleDescriptor { + fn create_wgpu_shader(&self) -> wgpu::ShaderModuleDescriptor<'_> { wgpu::ShaderModuleDescriptor { - label, + label: Some(self.label.as_str()), source: wgpu::util::make_spirv(self.source.as_ref()), } } @@ -10,7 +10,6 @@ struct Light { float cutoff; }; - struct CameraUniform { float4 view_pos; float4x4 view_proj;