ee1a63bd
build(renderer): wire vulkan-zig + glslc + embed SPIR-V
a73x 2026-04-08 08:52
- Update vulkan dep to zig-0.15-compat branch (was master, which targets
Zig nightly and uses std.process.Init / std.Io APIs absent in 0.15.2)
- Add glslc build step that compiles shaders/cell.{vert,frag} to SPIR-V
- Collect renderer.zig + both SPV blobs into a WriteFiles directory so
that @embedFile("cell.vert.spv") resolves relative to renderer.zig
- Add renderer module with vulkan import; wire into exe and test step
- All 12 tests pass including the SPIR-V magic-number check
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
diff --git a/build.zig b/build.zig index 7ed1acd..06dde99 100644 --- a/build.zig +++ b/build.zig @@ -144,4 +144,50 @@ pub fn build(b: *std.Build) void { .root_module = font_test_mod, }); test_step.dependOn(&b.addRunArtifact(font_tests).step); // vulkan-zig — generate Vulkan bindings from vk.xml const vulkan_headers_dep = b.dependency("vulkan_headers", .{}); const vulkan_zig_dep = b.dependency("vulkan", .{ .registry = vulkan_headers_dep.path("registry/vk.xml"), }); const vulkan_module = vulkan_zig_dep.module("vulkan-zig"); // Compile cell shaders to SPIR-V via glslc const glslc_vert = b.addSystemCommand(&.{ "glslc", "--target-env=vulkan1.2" }); glslc_vert.addFileArg(b.path("shaders/cell.vert")); glslc_vert.addArg("-o"); const cell_vert_spv = glslc_vert.addOutputFileArg("cell.vert.spv"); const glslc_frag = b.addSystemCommand(&.{ "glslc", "--target-env=vulkan1.2" }); glslc_frag.addFileArg(b.path("shaders/cell.frag")); glslc_frag.addArg("-o"); const cell_frag_spv = glslc_frag.addOutputFileArg("cell.frag.spv"); // Collect renderer.zig + both SPV blobs into one WriteFiles directory so // that @embedFile("cell.vert.spv") resolves correctly relative to renderer.zig. const renderer_dir = b.addWriteFiles(); const renderer_zig_path = renderer_dir.addCopyFile(b.path("src/renderer.zig"), "renderer.zig"); _ = renderer_dir.addCopyFile(cell_vert_spv, "cell.vert.spv"); _ = renderer_dir.addCopyFile(cell_frag_spv, "cell.frag.spv"); // renderer module const renderer_mod = b.createModule(.{ .root_source_file = renderer_zig_path, .target = target, .optimize = optimize, }); renderer_mod.addImport("vulkan", vulkan_module); exe_mod.addImport("renderer", renderer_mod); // Test renderer.zig const renderer_test_mod = b.createModule(.{ .root_source_file = renderer_zig_path, .target = target, .optimize = optimize, }); renderer_test_mod.addImport("vulkan", vulkan_module); const renderer_tests = b.addTest(.{ .root_module = renderer_test_mod, }); test_step.dependOn(&b.addRunArtifact(renderer_tests).step); } diff --git a/build.zig.zon b/build.zig.zon index d8cf78e..1752ccb 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -15,8 +15,8 @@ .hash = "wayland-0.6.0-dev-lQa1koD8AQDA1Ez_XdLOqA8QPvwKyxsAanpCIsZEb3OS", }, .vulkan = .{ .url = "git+https://github.com/Snektron/vulkan-zig#b3086a867a47bb10e66a987a38115a43056b60f7", .hash = "vulkan-0.0.0-r7Ytx99oAwDmJu0XtvukW_bSYnAv1PpbPO9ZFwsjcPPO", .url = "git+https://github.com/Snektron/vulkan-zig?ref=zig-0.15-compat#3ada9e2989bab70090a55f0f6fac19ea90d06357", .hash = "vulkan-0.0.0-r7Ytx6FIAwD3QyrrJhvObO0YSZ6WEkMGSCPKbhM__HFd", }, .vulkan_headers = .{ .url = "git+https://github.com/KhronosGroup/Vulkan-Headers#afe9eb980aa928a66d1c9c06f38c55dd59868720", diff --git a/shaders/cell.frag b/shaders/cell.frag new file mode 100644 index 0000000..7de861c --- /dev/null +++ b/shaders/cell.frag @@ -0,0 +1,14 @@ #version 450 layout(binding = 0) uniform sampler2D glyph_atlas; layout(location = 0) in vec2 in_uv; layout(location = 1) in vec4 in_fg; layout(location = 2) in vec4 in_bg; layout(location = 0) out vec4 out_color; void main() { float alpha = texture(glyph_atlas, in_uv).r; out_color = mix(in_bg, in_fg, alpha); } diff --git a/shaders/cell.vert b/shaders/cell.vert new file mode 100644 index 0000000..e82987b --- /dev/null +++ b/shaders/cell.vert @@ -0,0 +1,27 @@ #version 450 layout(push_constant) uniform PushConstants { vec2 viewport_size; vec2 cell_size; } pc; layout(location = 0) in vec2 in_unit_pos; layout(location = 1) in vec2 in_cell_pos; layout(location = 2) in vec4 in_uv_rect; layout(location = 3) in vec4 in_fg_color; layout(location = 4) in vec4 in_bg_color; layout(location = 0) out vec2 out_uv; layout(location = 1) out vec4 out_fg; layout(location = 2) out vec4 out_bg; void main() { vec2 pixel_pos = (in_cell_pos + in_unit_pos) * pc.cell_size; vec2 ndc = (pixel_pos / pc.viewport_size) * 2.0 - 1.0; gl_Position = vec4(ndc, 0.0, 1.0); out_uv = mix(in_uv_rect.xy, in_uv_rect.zw, in_unit_pos); out_fg = in_fg_color; out_bg = in_bg_color; } diff --git a/src/renderer.zig b/src/renderer.zig new file mode 100644 index 0000000..5942c5c --- /dev/null +++ b/src/renderer.zig @@ -0,0 +1,21 @@ const std = @import("std"); const vk = @import("vulkan"); pub const cell_vert_spv: []const u8 = @embedFile("cell.vert.spv"); pub const cell_frag_spv: []const u8 = @embedFile("cell.frag.spv"); test "vulkan module imports" { _ = vk; } test "shaders are embedded with SPIR-V magic" { try std.testing.expect(cell_vert_spv.len > 0); try std.testing.expect(cell_frag_spv.len > 0); // SPIR-V magic number (0x07230203), little-endian first 4 bytes const magic: u32 = std.mem.readInt(u32, cell_vert_spv[0..4], .little); try std.testing.expectEqual(@as(u32, 0x07230203), magic); const magic2: u32 = std.mem.readInt(u32, cell_frag_spv[0..4], .little); try std.testing.expectEqual(@as(u32, 0x07230203), magic2); }