From c6a5aaf5c4a020eaefe24943ecb6cf9d4eb26836 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 15:57:22 -0300 Subject: Post chain: Shadertoy passes compiled by glslc; Crt, Ripple and Glitch rewritten as Shadertoy files Stage 9 of docs/render-pipeline.md. src/gui/Post.zig runs the chain over the finished frame: each pass reads the one before, ping-pong textures between, the last writes the window; an empty chain is the direct path. The prefix (shaders/post/prefix.glsl) is ghostty's uniform block, name for name and offset for offset (tested against a copy of its Uniforms, the selection colours by their GLSL names), at SDL GPU's bindings, y down. A user's file (Shader ) is compiled by glslc with compileGlsl's own flags on a thread of its own, taken in at the next loop step; errors name the file's own lines; a failed compile keeps the last good pipeline; glslc missing is said once. The bundled passes compile with the build, through the same prefix. Crt, Ripple and Glitch are Shadertoy files, rewritten: no barrel (input is identity, tested), everything in linear light and dithered, keyed to iTime. scene_effects, crt.zig and crt.frag.glsl are gone; the core keeps no clock for the chain. ShaderAnimation off|on|always drives redraw level A: the chain alone over the retained frame (34 us CPU a redraw, measured). Shared files touched: pardes.zig (nextWake loses the scene line, disableSceneEffects empties the chain, two tests), builtins.zig (one switch arm), ninep/ctl.zig (two switch arms), macos.zig (flags from the chain), gui.zig. Not touched: Messages.zig, mouse.zig, detached/*, host_io, tty. --- build.zig | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) (limited to 'build.zig') diff --git a/build.zig b/build.zig index 803f773d..c1117593 100644 --- a/build.zig +++ b/build.zig @@ -50,7 +50,11 @@ const gui_shaders = [_][]const u8{ "decor.frag", "overlay.vert", "overlay.frag", "image.vert", "image.frag", - "crt.vert", "crt.frag", + "post.vert", + // The bundled post passes: shaders/post/.glsl behind the Shadertoy + // prefix (shaderSource). + "post-crt.frag", "post-ripple.frag", + "post-glitch.frag", }; pub fn build(b: *std.Build) void { @@ -161,7 +165,7 @@ pub fn build(b: *std.Build) void { for (gui_shaders) |name| { update_shaders.addCopyFileToSource(compileGlsl(b, name), b.fmt("shaders/prebuilt/{s}.spv", .{name})); update_shaders.addCopyFileToSource( - b.path(b.fmt("shaders/{s}.glsl", .{name})), + shaderSource(b, name), b.fmt("shaders/prebuilt/{s}.glsl", .{name}), ); } @@ -691,7 +695,7 @@ pub fn build(b: *std.Build) void { mod.addOptions("pardes_isolation", if (isolated_mod == mod) iso_on else iso_off); if (shell == .gui) for (gui_shaders) |name| mod.addAnonymousImport( b.fmt("effect-source-{s}.glsl", .{name}), - .{ .root_source_file = b.path(b.fmt("{s}/{s}.glsl", .{ gui_effect_source_dir, name })) }, + .{ .root_source_file = if (prebuilt_shaders) b.path(b.fmt("{s}/{s}.glsl", .{ gui_effect_source_dir, name })) else shaderSource(b, name) }, ); if (shell == .macos) mod.addAnonymousImport("effect-source-crt.ci.metal", .{ .root_source_file = b.path("shaders/crt.ci.metal"), @@ -756,6 +760,8 @@ pub fn build(b: *std.Build) void { gm.addAnonymousImport("AdwaitaMono-Regular.ttf", .{ .root_source_file = b.path("assets/AdwaitaMono-Regular.ttf"), }); + // Put before a user's Shadertoy file when the gui compiles it. + gm.addAnonymousImport("post-prefix.glsl", .{ .root_source_file = b.path("shaders/post/prefix.glsl") }); for (gui_shaders) |name| gm.addAnonymousImport(b.fmt("{s}.spv", .{name}), .{ .root_source_file = if (prebuilt_shaders) b.path(b.fmt("shaders/prebuilt/{s}.spv", .{name})) @@ -1444,7 +1450,6 @@ pub fn build(b: *std.Build) void { .{ .path = "src/esp32p4/input_rescue.zig", .libc = false }, .{ .path = "src/fonts.zig", .libc = true }, .{ .path = "src/c_heap.zig", .libc = false }, - .{ .path = "src/gui/crt.zig", .libc = false }, }) |entry| { const tests = b.addTest(.{ .root_module = b.createModule(.{ @@ -1626,10 +1631,23 @@ fn reindexed(b: *std.Build, archive: std.Build.LazyPath, index: usize) std.Build // glslc shaders/.glsl -fshader-stage= -o .spv, // returned as a LazyPath for @embedFile. The stage is the name's own suffix. +/// The GLSL a shader is compiled from: shaders/.glsl, or for a bundled +/// post pass (post-.frag) shaders/post/.glsl behind the Shadertoy +/// prefix, the one the gui puts before a user's file at run time. +fn shaderSource(b: *std.Build, name: []const u8) std.Build.LazyPath { + if (!std.mem.startsWith(u8, name, "post-")) return b.path(b.fmt("shaders/{s}.glsl", .{name})); + const joined = b.addSystemCommand(&.{"cat"}); + joined.addFileArg(b.path("shaders/post/prefix.glsl")); + joined.addFileArg(b.path(b.fmt("shaders/post/{s}.glsl", .{name["post-".len .. name.len - ".frag".len]}))); + return joined.captureStdOut(.{}); +} + +/// The one glslc invocation every shader is compiled with, at build time here +/// and at run time for a user's post pass (src/gui/Post.zig). fn compileGlsl(b: *std.Build, name: []const u8) std.Build.LazyPath { const stage = if (std.mem.endsWith(u8, name, ".vert")) "vertex" else "fragment"; const cmd = b.addSystemCommand(&.{ "glslc", b.fmt("-fshader-stage={s}", .{stage}) }); - cmd.addFileArg(b.path(b.fmt("shaders/{s}.glsl", .{name}))); + cmd.addFileArg(shaderSource(b, name)); cmd.addArg("-o"); return cmd.addOutputFileArg(b.fmt("{s}.spv", .{name})); } -- cgit v1.3