From: Joshua Simmons Date: Sat, 4 Oct 2025 21:00:01 +0000 (+0200) Subject: shark-shaders: Migrate basic shader to slang X-Git-Url: https://git.nega.tv//gitweb.cgi?a=commitdiff_plain;h=f80925d824c31e116856ef6e072b3bcb6c9a4139;p=josh%2Fnarcissus shark-shaders: Migrate basic shader to slang --- diff --git a/title/shark-shaders/build.rs b/title/shark-shaders/build.rs index 077cb66..5c0b65c 100644 --- a/title/shark-shaders/build.rs +++ b/title/shark-shaders/build.rs @@ -3,21 +3,82 @@ use std::process::Command; const SHADER_ROOT: &str = "shaders"; +struct Lexer<'a> { + bytes: &'a [u8], + index: usize, +} + +impl<'a> Lexer<'a> { + fn new(bytes: &'a [u8]) -> Self { + Self { bytes, index: 0 } + } + + fn is_empty(&self) -> bool { + self.index >= self.bytes.len() + } + + fn skip(&mut self, b: u8) -> bool { + if self.bytes.get(self.index).is_some_and(|&x| x == b) { + self.index += 1; + true + } else { + false + } + } + + fn skip_to(&mut self, escape: u8, needle: u8) -> bool { + let mut escape_count = 0; + while let Some(&b) = self.bytes.get(self.index) { + if b == escape { + escape_count += 1; + } else if b == needle { + if escape_count & 1 == 0 { + self.index += 1; + return true; + } + escape_count = 0; + } else { + escape_count = 0; + } + self.index += 1; + } + false + } + + fn read_to(&mut self, escape: u8, needle: u8) -> &[u8] { + let start = self.index; + let mut escape_count = 0; + while let Some(&b) = self.bytes.get(self.index) { + if b == escape { + escape_count += 1; + } else if b == needle { + if escape_count & 1 == 0 { + break; + } + escape_count = 0; + } else { + escape_count = 0; + } + self.index += 1; + } + &self.bytes[start..self.index] + } +} + #[derive(Clone, Copy)] struct Shader { stage: &'static str, name: &'static str, } +#[derive(Clone, Copy)] +struct SlangShader { + name: &'static str, +} + +const SLANG_SHADERS: &[SlangShader] = &[SlangShader { name: "basic" }]; + const SHADERS: &[Shader] = &[ - Shader { - stage: "vert", - name: "basic", - }, - Shader { - stage: "frag", - name: "basic", - }, Shader { stage: "comp", name: "draw_2d_bin_0_clear", @@ -59,6 +120,25 @@ fn main() { _ => "0", }; + let mut slang_commands = SLANG_SHADERS + .iter() + .map(|SlangShader { name }| { + Command::new("slangc") + .arg(format!("{SHADER_ROOT}/{name}.slang")) + .args(["-target", "spirv"]) + .args(["-profile", "spirv_1_6"]) + .args(["-capability", "vk_mem_model"]) + .arg("-fvk-use-scalar-layout") + .arg("-fvk-use-entrypoint-name") + .arg("-matrix-layout-row-major") + .arg(format!("-g{debug}")) + .args(["-depfile", &format!("{out_dir}/{name}.d")]) + .args(["-o", &format!("{out_dir}/{name}.spv")]) + .spawn() + .unwrap() + }) + .collect::>(); + let mut commands = SHADERS .iter() .map(|Shader { stage, name }| { @@ -83,6 +163,15 @@ fn main() { ) .unwrap(); + for SlangShader { name } in SLANG_SHADERS { + writeln!( + file, + "pub const {}_SPV: &[u8] = &SpirvBytes(*include_bytes!(\"{out_dir}/{name}.spv\")).0;", + name.to_ascii_uppercase(), + ) + .unwrap(); + } + for Shader { stage, name } in SHADERS { writeln!( file, @@ -93,7 +182,7 @@ fn main() { .unwrap(); } - for (mut command, shader) in commands.drain(..).zip(SHADERS.iter()) { + for (mut command, shader) in slang_commands.drain(..).zip(SLANG_SHADERS.iter()) { let status = command.wait().unwrap(); assert!( status.success(), @@ -102,70 +191,35 @@ fn main() { ); } - for &Shader { stage, name } in SHADERS { - let depfile = std::fs::read_to_string(format!("{out_dir}/{name}.{stage}.d")).unwrap(); - - struct Lexer<'a> { - bytes: &'a [u8], - index: usize, - } - - impl<'a> Lexer<'a> { - fn new(bytes: &'a [u8]) -> Self { - Self { bytes, index: 0 } - } - - fn is_empty(&self) -> bool { - self.index >= self.bytes.len() - } - - fn skip(&mut self, b: u8) -> bool { - if self.bytes.get(self.index).is_some_and(|&x| x == b) { - self.index += 1; - true - } else { - false - } - } + for (mut command, shader) in commands.drain(..).zip(SHADERS.iter()) { + let status = command.wait().unwrap(); + assert!( + status.success(), + "shader '{}' failed to compile", + shader.name + ); + } - fn skip_to(&mut self, escape: u8, needle: u8) -> bool { - let mut escape_count = 0; - while let Some(&b) = self.bytes.get(self.index) { - if b == escape { - escape_count += 1; - } else if b == needle { - if escape_count & 1 == 0 { - self.index += 1; - return true; - } - escape_count = 0; - } else { - escape_count = 0; - } - self.index += 1; - } - false - } + for &SlangShader { name } in SLANG_SHADERS { + let depfile = std::fs::read_to_string(format!("{out_dir}/{name}.d")).unwrap(); - fn read_to(&mut self, escape: u8, needle: u8) -> &[u8] { - let start = self.index; - let mut escape_count = 0; - while let Some(&b) = self.bytes.get(self.index) { - if b == escape { - escape_count += 1; - } else if b == needle { - if escape_count & 1 == 0 { - break; - } - escape_count = 0; - } else { - escape_count = 0; + for line in depfile.lines() { + let mut lexer = Lexer::new(line.as_bytes()); + if lexer.skip_to(b'\\', b':') { + lexer.skip(b' '); + while !lexer.is_empty() { + let path = lexer.read_to(b'\\', b' '); + if let Ok(path) = std::str::from_utf8(path) { + println!("cargo::rerun-if-changed={path}"); } - self.index += 1; + lexer.skip(b' '); } - &self.bytes[start..self.index] } } + } + + for &Shader { stage, name } in SHADERS { + let depfile = std::fs::read_to_string(format!("{out_dir}/{name}.{stage}.d")).unwrap(); for line in depfile.lines() { let mut lexer = Lexer::new(line.as_bytes()); diff --git a/title/shark-shaders/shaders/basic.frag b/title/shark-shaders/shaders/basic.frag deleted file mode 100644 index 23b4b03..0000000 --- a/title/shark-shaders/shaders/basic.frag +++ /dev/null @@ -1,15 +0,0 @@ -#version 460 - -#extension GL_GOOGLE_include_directive : require - -#include "bindings_graphics.h" - -layout(location = 0) in vec2 tex_coord; -layout(location = 1) in vec3 normal; -layout(location = 0) out vec4 out_color; - -void main() { - const float n_dot_l = max(dot(normal, vec3(0.0, 1.0, 0.0)), 0.1); - const vec3 rgb = texture(sampler2D(albedo, samplers[SAMPLER_BILINEAR]), vec2(tex_coord.x, tex_coord.y)).rgb; - out_color = vec4(rgb * n_dot_l, 1.0); -} diff --git a/title/shark-shaders/shaders/basic.slang b/title/shark-shaders/shaders/basic.slang new file mode 100644 index 0000000..084cfa7 --- /dev/null +++ b/title/shark-shaders/shaders/basic.slang @@ -0,0 +1,60 @@ +#language slang 2026 + +import bindings_samplers; +import bindings_graphics; + +struct Vertex { + float4 position; + float4 normal; + float4 texcoord; +} + +struct VSConstants { + float4x4 clip_from_camera; + Vertex *vertex_buffer; + float4 *transform_buffer; +} + +struct VSOutput { + float4 position : SV_Position; + float2 texcoord; + float3 normal; +} + +[shader("vertex")] +VSOutput vertex(uniform VSConstants constants, + uint instance_id: SV_InstanceID, + uint vertex_id: SV_VertexID) { + let vertex = constants.vertex_buffer[vertex_id]; + let t0 = constants.transform_buffer[instance_id * 3 + 0]; + let t1 = constants.transform_buffer[instance_id * 3 + 1]; + let t2 = constants.transform_buffer[instance_id * 3 + 2]; + + let camera_from_model = float4x4( + t0[0], t0[1], t0[2], t2[1], + t0[3], t1[0], t1[1], t2[2], + t1[2], t1[3], t2[0], t2[3], + 0.0, 0.0, 0.0, 1.0); + + let clip_from_model = mul(constants.clip_from_camera, camera_from_model); + + VSOutput output; + output.position = mul(clip_from_model, float4(vertex.position.xyz, 1.0)); + output.normal = vertex.normal.xyz; + output.texcoord = float2(vertex.texcoord.x, 1.0 - vertex.texcoord.y); + return output; +} + +struct FSOutput { + float4 color : SV_Target0; +} + +[shader("fragment")] +FSOutput fragment(VSOutput input) { + let n_dot_l = max(dot(input.normal, float3(0.0, 1.0, 0.0)), 0.1); + let rgb = albedo.Sample(samplers[Sampler::Bilinear], input.texcoord).rgb; + + FSOutput output; + output.color = float4(rgb * n_dot_l, 1.0); + return output; +} diff --git a/title/shark-shaders/shaders/basic.vert b/title/shark-shaders/shaders/basic.vert deleted file mode 100644 index a763036..0000000 --- a/title/shark-shaders/shaders/basic.vert +++ /dev/null @@ -1,55 +0,0 @@ -#version 460 - -#extension GL_EXT_buffer_reference : require -#extension GL_EXT_buffer_reference2 : require -#extension GL_EXT_scalar_block_layout : require - -struct Vertex { - vec4 position; - vec4 normal; - vec4 texcoord; -}; - -struct Transform { - vec4 transform[3]; -}; - -layout(buffer_reference, std430, buffer_reference_align = 16) readonly buffer VertexRef { - Vertex values[]; -}; - -layout(buffer_reference, std430, buffer_reference_align = 16) readonly buffer TransformRef { - Transform values[]; -}; - -struct BasicConstants { - mat4 clip_from_camera; - VertexRef vertex_buffer; - TransformRef transform_buffer; -}; - -layout(std430, row_major, push_constant) uniform BasicConstantsBlock { - BasicConstants constants; -}; - -layout(location = 0) out vec2 out_texcoord; -layout(location = 1) out vec3 out_normal; - -void main() { - const Transform td = constants.transform_buffer.values[gl_InstanceIndex]; - const Vertex vd = constants.vertex_buffer.values[gl_VertexIndex]; - - const mat4 camera_from_model = mat4( - td.transform[0].x, td.transform[0].w, td.transform[1].z, 0.0, - td.transform[0].y, td.transform[1].x, td.transform[1].w, 0.0, - td.transform[0].z, td.transform[1].y, td.transform[2].x, 0.0, - td.transform[2].y, td.transform[2].z, td.transform[2].w, 1.0 - ); - - const vec4 position_clip = constants.clip_from_camera * camera_from_model * vec4(vd.position.xyz, 1.0); - - gl_Position = position_clip; - - out_normal = vd.normal.xyz; - out_texcoord = vec2(vd.texcoord.x, 1.0 - vd.texcoord.y); -} diff --git a/title/shark-shaders/shaders/bindings_compute.slang b/title/shark-shaders/shaders/bindings_compute.slang new file mode 100644 index 0000000..05c3ef7 --- /dev/null +++ b/title/shark-shaders/shaders/bindings_compute.slang @@ -0,0 +1,14 @@ +module bindings_compute; + +[[vk::binding(1, 0)]] +public Texture3D tony_mc_mapface_lut; +[[vk::binding(2, 0)]] +public Texture2D glyph_atlas; +[[vk::binding(3, 0)]] +public RWTexture2D ui_layer_write; +[[vk::binding(4, 0)]] +public Texture2D ui_layer_read; +[[vk::binding(5, 0)]] +public Texture2D color_layer; +[[vk::binding(6, 0)]] +public RWTexture2D composited_output; diff --git a/title/shark-shaders/shaders/bindings_graphics.h b/title/shark-shaders/shaders/bindings_graphics.h deleted file mode 100644 index b9fb83c..0000000 --- a/title/shark-shaders/shaders/bindings_graphics.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef GRAPHICS_BINDINGS_INCLUDE -#define GRAPHICS_BINDINGS_INCLUDE - -const uint SAMPLER_BILINEAR = 0; -const uint SAMPLER_BILINEAR_UNNORMALIZED = 1; -const uint SAMPLER_COUNT = 2; - -layout (set = 0, binding = 0) uniform sampler samplers[SAMPLER_COUNT]; -layout (set = 0, binding = 1) uniform texture2D albedo; - -#endif diff --git a/title/shark-shaders/shaders/bindings_graphics.slang b/title/shark-shaders/shaders/bindings_graphics.slang new file mode 100644 index 0000000..807d2a5 --- /dev/null +++ b/title/shark-shaders/shaders/bindings_graphics.slang @@ -0,0 +1,4 @@ +module bindings_graphics; + +[[vk::binding(1, 0)]] +public Texture2D albedo; diff --git a/title/shark-shaders/shaders/bindings_samplers.slang b/title/shark-shaders/shaders/bindings_samplers.slang new file mode 100644 index 0000000..99216cd --- /dev/null +++ b/title/shark-shaders/shaders/bindings_samplers.slang @@ -0,0 +1,10 @@ +module bindings_samplers; + +public enum Sampler +{ + Bilinear, + BilinearUnnormalized +} + +[[vk::binding(0, 0)]] +public SamplerState samplers[2]; diff --git a/title/shark-shaders/src/pipelines.rs b/title/shark-shaders/src/pipelines.rs index 408f570..48dfcc0 100644 --- a/title/shark-shaders/src/pipelines.rs +++ b/title/shark-shaders/src/pipelines.rs @@ -321,11 +321,13 @@ impl Pipelines { let basic_pipeline = gpu.create_graphics_pipeline(&GraphicsPipelineDesc { vertex_shader: ShaderDesc { - code: crate::BASIC_VERT_SPV, + code: crate::BASIC_SPV, + entry: c"vertex", ..default() }, fragment_shader: ShaderDesc { - code: crate::BASIC_FRAG_SPV, + code: crate::BASIC_SPV, + entry: c"fragment", ..default() }, layout: PipelineLayout {