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",
_ => "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 }| {
)
.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,
.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(),
);
}
- 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());
+++ /dev/null
-#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);
-}
--- /dev/null
+#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;
+}
+++ /dev/null
-#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);
-}
--- /dev/null
+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;
+++ /dev/null
-#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
--- /dev/null
+module bindings_graphics;
+
+[[vk::binding(1, 0)]]
+public Texture2D albedo;
--- /dev/null
+module bindings_samplers;
+
+public enum Sampler
+{
+ Bilinear,
+ BilinearUnnormalized
+}
+
+[[vk::binding(0, 0)]]
+public SamplerState samplers[2];
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 {