renderer/vulkan: probe shader Vulkan-compatibility (diagnostic)
Adds `probeGhosttyShaders` to the smoke test: tries to compile each
of the renderer's nine real GLSL shaders (with the same #include
preprocessing the OpenGL backend uses) through `shaders.Module.init`
as Vulkan-targeted SPIR-V. Reports per-shader success or the first
glslang error message.
Results on the current shader source:
✓ bg_color.f.glsl (UBO-only, no textures)
✓ cell_bg.f.glsl (UBO-only, no textures)
✗ full_screen.v.glsl → gl_VertexID undeclared
✗ cell_text.v.glsl → gl_VertexID undeclared
✗ cell_text.f.glsl → location missing on in/out
✗ image.v.glsl → gl_VertexID undeclared
✗ image.f.glsl → location missing on in/out
✗ bg_image.v.glsl → location missing on in/out
✗ bg_image.f.glsl → location missing on in/out
Two distinct incompatibilities surfaced:
1. `gl_VertexID` (OpenGL) vs `gl_VertexIndex` (Vulkan SPIR-V):
every vertex shader uses the OpenGL name.
2. SPIR-V requires explicit `layout(location = N)` on every
shader-stage in/out variable. OpenGL GLSL auto-assigns
locations; the renderer's shaders rely on that.
The binding namespace conflict (UBO at binding=1 colliding with
`atlas_color` at binding=1 in `cell_text.f.glsl`) is the next
hurdle behind these two, but it's hidden by them today.
Implications for the "use glslang auto-bind/auto-map" path:
glslang's auto-bind features are C++-only (not exposed through the
C `glslang_input_t` struct we use), AND they only help with
bindings — they don't synthesize `gl_VertexIndex` or auto-place
`layout(location = N)` qualifiers. Making the existing GLSL
Vulkan-compatible requires source modification spanning all 9
shaders.
Co-Authored-By: claude-flow <ruv@ruv.net>
pull/12846/head
parent
0070b90370
commit
f51433c770
|
|
@ -383,6 +383,13 @@ test "smoke" {
|
|||
// it + a sampler, render a quad sampling from it, save as PPM.
|
||||
try renderTexturedToFile(&device, "/tmp/ghastty-vulkan-smoke-textured.ppm");
|
||||
|
||||
// ---- 7. Try compiling the real Ghostty shaders ---------------
|
||||
// Tests whether the existing OpenGL GLSL sources compile cleanly
|
||||
// through glslang to Vulkan SPIR-V, or whether they hit binding
|
||||
// namespace conflicts (Vulkan shares one namespace per descriptor
|
||||
// set; OpenGL has separate ones per resource type).
|
||||
try probeGhosttyShaders(&device);
|
||||
|
||||
std.debug.print("\n All Vulkan smoke checks passed.\n", .{});
|
||||
std.debug.print(
|
||||
" Visual (gradient): /tmp/ghastty-vulkan-smoke.ppm\n",
|
||||
|
|
@ -1156,6 +1163,91 @@ fn renderTexturedToFile(device: *const Device, path: []const u8) !void {
|
|||
std.debug.print(" Textured: wrote {}x{} PPM to {s}\n", .{ out_w, out_h, path });
|
||||
}
|
||||
|
||||
/// Compile each of the renderer's actual GLSL shaders (with the
|
||||
/// existing `#include` preprocessor splicing in `common.glsl`) and
|
||||
/// report which ones glslang accepts as Vulkan-targeted SPIR-V. The
|
||||
/// expected failure mode is a binding namespace collision on the
|
||||
/// shaders that combine the Globals UBO with texture samplers.
|
||||
fn probeGhosttyShaders(device: *const Device) !void {
|
||||
// The full source files post-include-preprocessing. Computed at
|
||||
// comptime via the same `processIncludes` trick as
|
||||
// `opengl/shaders.zig`'s `loadShaderCode`.
|
||||
const common = @embedFile("../shaders/glsl/common.glsl");
|
||||
inline for (&[_]struct { name: []const u8, src: [:0]const u8, stage: shaders.Stage }{
|
||||
.{
|
||||
.name = "bg_color.f.glsl",
|
||||
.src = comptime spliceCommon(@embedFile("../shaders/glsl/bg_color.f.glsl")),
|
||||
.stage = .fragment,
|
||||
},
|
||||
.{
|
||||
.name = "cell_bg.f.glsl",
|
||||
.src = comptime spliceCommon(@embedFile("../shaders/glsl/cell_bg.f.glsl")),
|
||||
.stage = .fragment,
|
||||
},
|
||||
.{
|
||||
.name = "full_screen.v.glsl",
|
||||
.src = comptime spliceCommon(@embedFile("../shaders/glsl/full_screen.v.glsl")),
|
||||
.stage = .vertex,
|
||||
},
|
||||
.{
|
||||
.name = "cell_text.v.glsl",
|
||||
.src = comptime spliceCommon(@embedFile("../shaders/glsl/cell_text.v.glsl")),
|
||||
.stage = .vertex,
|
||||
},
|
||||
.{
|
||||
.name = "cell_text.f.glsl",
|
||||
.src = comptime spliceCommon(@embedFile("../shaders/glsl/cell_text.f.glsl")),
|
||||
.stage = .fragment,
|
||||
},
|
||||
.{
|
||||
.name = "image.v.glsl",
|
||||
.src = comptime spliceCommon(@embedFile("../shaders/glsl/image.v.glsl")),
|
||||
.stage = .vertex,
|
||||
},
|
||||
.{
|
||||
.name = "image.f.glsl",
|
||||
.src = comptime spliceCommon(@embedFile("../shaders/glsl/image.f.glsl")),
|
||||
.stage = .fragment,
|
||||
},
|
||||
.{
|
||||
.name = "bg_image.v.glsl",
|
||||
.src = comptime spliceCommon(@embedFile("../shaders/glsl/bg_image.v.glsl")),
|
||||
.stage = .vertex,
|
||||
},
|
||||
.{
|
||||
.name = "bg_image.f.glsl",
|
||||
.src = comptime spliceCommon(@embedFile("../shaders/glsl/bg_image.f.glsl")),
|
||||
.stage = .fragment,
|
||||
},
|
||||
}) |entry| {
|
||||
if (shaders.Module.init(device, entry.src, entry.stage)) |mod| {
|
||||
defer mod.deinit();
|
||||
std.debug.print(" Shader compile ✓ {s}\n", .{entry.name});
|
||||
} else |err| {
|
||||
std.debug.print(" Shader compile ✗ {s} → {}\n", .{ entry.name, err });
|
||||
}
|
||||
}
|
||||
|
||||
_ = common;
|
||||
}
|
||||
|
||||
/// Tiny comptime preprocessor: replace `#include "common.glsl"` with
|
||||
/// the contents of `common.glsl`. The real Ghostty shaders all use
|
||||
/// exactly that one include, so this is a sufficient stub.
|
||||
fn spliceCommon(comptime contents: [:0]const u8) [:0]const u8 {
|
||||
const needle = "#include \"common.glsl\"";
|
||||
if (std.mem.indexOf(u8, contents, needle)) |idx| {
|
||||
const common = @embedFile("../shaders/glsl/common.glsl");
|
||||
return std.fmt.comptimePrint("{s}{s}{s}", .{
|
||||
contents[0..idx],
|
||||
common,
|
||||
contents[idx + needle.len ..],
|
||||
});
|
||||
} else {
|
||||
return contents;
|
||||
}
|
||||
}
|
||||
|
||||
fn imageBarrier(
|
||||
device: *const Device,
|
||||
cb: vk.VkCommandBuffer,
|
||||
|
|
|
|||
Loading…
Reference in New Issue