]> git.nega.tv - josh/narcissus/commitdiff
shark-shaders: Migrate basic shader to slang
authorJoshua Simmons <josh@nega.tv>
Sat, 4 Oct 2025 21:00:01 +0000 (23:00 +0200)
committerJoshua Simmons <josh@nega.tv>
Sun, 12 Oct 2025 09:54:24 +0000 (11:54 +0200)
title/shark-shaders/build.rs
title/shark-shaders/shaders/basic.frag [deleted file]
title/shark-shaders/shaders/basic.slang [new file with mode: 0644]
title/shark-shaders/shaders/basic.vert [deleted file]
title/shark-shaders/shaders/bindings_compute.slang [new file with mode: 0644]
title/shark-shaders/shaders/bindings_graphics.h [deleted file]
title/shark-shaders/shaders/bindings_graphics.slang [new file with mode: 0644]
title/shark-shaders/shaders/bindings_samplers.slang [new file with mode: 0644]
title/shark-shaders/src/pipelines.rs

index 077cb66fdd6b6e2e4ba30b1c5b904194585fd81a..5c0b65c06dec1f7215f83a5403be5c1aa460c5ff 100644 (file)
@@ -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::<Vec<_>>();
+
     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 (file)
index 23b4b03..0000000
+++ /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 (file)
index 0000000..084cfa7
--- /dev/null
@@ -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 (file)
index a763036..0000000
+++ /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 (file)
index 0000000..05c3ef7
--- /dev/null
@@ -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 (file)
index b9fb83c..0000000
+++ /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 (file)
index 0000000..807d2a5
--- /dev/null
@@ -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 (file)
index 0000000..99216cd
--- /dev/null
@@ -0,0 +1,10 @@
+module bindings_samplers;
+
+public enum Sampler
+{
+    Bilinear,
+    BilinearUnnormalized
+}
+
+[[vk::binding(0, 0)]]
+public SamplerState samplers[2];
index 408f57040d0ba9532c2e08b5c786f3a291ba0bcc..48dfcc0f0d73ca02aeb78b9f285d53a8e7fe13af 100644 (file)
@@ -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 {