summaryrefslogtreecommitdiff
path: root/src/shader_build.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 13:19:08 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commit4e224b7c7e67fafcbe7359fcf534048e254b701d (patch)
tree33539e34e8cee525792767facebf36356bc28b08 /src/shader_build.zig
parent7ec97214a7a6ee905167b7f94a800a8c1d70b100 (diff)
downloadpardes-4e224b7c7e67fafcbe7359fcf534048e254b701d.tar.gz
pardes-4e224b7c7e67fafcbe7359fcf534048e254b701d.zip
The theme's chrome is worked out once, not per grapheme; still post passes let the GUI rest; a perf gate
somtrmsz's contrast floors made ChromeTheme.fromTheme run its focus-tint and separator searches (pow calls each), and Output's RowDecoration.styleAt called it for every grapheme of every highlighted row: the 50k-line file's render went from 0.11 ms to 13 ms. Pardes.bodyChrome now keeps the theme's chrome, worked out again only when the theme differs; recolorSyntax asks for it once a decorated row, and a plain row (nearly every row of a file) never asks. ReleaseFast, medium fixture, median us, before -> after (main): render 13150 -> 76 (111), key-down 14658 -> 79 (110), wheel 13589 -> 76 (109), open 14891 -> 2095 (2055), edit-char 16703 -> 3006 (1251; the rest of that gap is editing and tree-sitter, not this). Post.animating asked for frames whenever the window had focus and any pass was ready, so a still pass kept the GUI drawing at the display's rate (Bloom: 49% of a core idle, in a hidden test window). A pass now says whether it moves on its own: the CRT (its hum and dither) and a Shadertoy file whose source reads iTime, iFrame or iDate (shader_build.readsTime; the flag rides the wire's post message); Bloom, Vignette and Grain are still. Bloom idle: 49% -> 1.2%, as with no pass. zig build perf-gate: the 50k-line file's gestures, each's fastest sample within 3x of the recorded baseline's (test/perf-baseline-<platform>- <optimize>.json, recorded from this build), run with every unit-test. On the regressed code it fails at 180x for render.
Diffstat (limited to 'src/shader_build.zig')
-rw-r--r--src/shader_build.zig34
1 files changed, 30 insertions, 4 deletions
diff --git a/src/shader_build.zig b/src/shader_build.zig
index 8aa7eb5e..c2610fd7 100644
--- a/src/shader_build.zig
+++ b/src/shader_build.zig
@@ -32,8 +32,24 @@ pub const Pass = struct {
path: []const u8 = &.{},
spirv: []const u8 = &.{},
revision: u32 = 0,
+ /// It moves on its own (reads the clock or the frame count), so the
+ /// chain redraws while idle; a still pass draws only with a new frame.
+ animated: bool = false,
};
+/// The bundled passes that move on their own: the CRT's hum and dither.
+pub fn sceneAnimated(scene: Scene) bool {
+ return scene == .crt;
+}
+
+/// Whether a Shadertoy source moves on its own: it reads the time
+/// (iTime, iTimeDelta and the times of a cursor move or a focus change),
+/// the frame count or the date. A mention in a comment counts too.
+pub fn readsTime(source: []const u8) bool {
+ for ([_][]const u8{ "iTime", "iFrame", "iDate" }) |name| if (std.mem.indexOf(u8, source, name) != null) return true;
+ return false;
+}
+
/// Who to wake when a compile is done: the host's loop.
pub const Wake = struct {
ctx: ?*anyopaque = null,
@@ -47,6 +63,7 @@ const File = struct {
source: ?u64 = null,
spirv: []u8 = &.{},
revision: u32 = 0,
+ animated: bool = false,
/// Wants a compile: new to the chain, or its directory changed.
stale: bool = true,
};
@@ -56,7 +73,7 @@ const Result = union(enum) {
unchanged,
missing,
failed: struct { text: []u8, source: u64 },
- spirv: struct { bytes: []u8, source: u64 },
+ spirv: struct { bytes: []u8, source: u64, animated: bool },
};
/// One compile of the stale files, on its own thread.
@@ -148,8 +165,8 @@ pub fn recheck(b: *Build) void {
/// The chain as passes, in its order.
pub fn view(b: *const Build, chain: *const Chain, out: *[max]Pass) []const Pass {
for (chain.list(), 0..) |entry, i| {
- out[i] = if (entry.scene) |scene| .{ .scene = scene, .level = entry.level } else for (b.files[0..b.len]) |file| {
- if (std.mem.eql(u8, file.path, entry.path.get())) break .{ .path = file.path, .spirv = file.spirv, .revision = file.revision };
+ out[i] = if (entry.scene) |scene| .{ .scene = scene, .level = entry.level, .animated = sceneAnimated(scene) } else for (b.files[0..b.len]) |file| {
+ if (std.mem.eql(u8, file.path, entry.path.get())) break .{ .path = file.path, .spirv = file.spirv, .revision = file.revision, .animated = file.animated };
} else .{ .path = entry.path.get() };
}
return out[0..chain.len];
@@ -211,6 +228,7 @@ fn collect(b: *Build, gpa: std.mem.Allocator, core: *pardes.Pardes, job: *Job) v
gpa.free(f.spirv);
f.spirv = r.bytes;
f.source = r.source;
+ f.animated = r.animated;
f.revision +%= 1;
b.revision +%= 1;
},
@@ -291,7 +309,7 @@ fn compileOne(gpa: std.mem.Allocator, io: std.Io, path: []const u8, known: ?u64)
.exited => |code| code == 0,
else => false,
};
- if (clean) return .{ .spirv = .{ .bytes = reader.toOwnedSlice(0) catch return .none, .source = source } };
+ if (clean) return .{ .spirv = .{ .bytes = reader.toOwnedSlice(0) catch return .none, .source = source, .animated = readsTime(body) } };
const said = reader.toOwnedSlice(1) catch return .none;
// glslc calls its input <stdin>; the file is what a person knows.
defer gpa.free(said);
@@ -400,3 +418,11 @@ test "a save that changes a file compiles it again, a failed one keeps the last
const second = b.view(&core.settings.post, &out)[0];
try std.testing.expect(second.revision != first.revision and b.revision != at);
}
+
+test "a pass animates when it reads the clock or the frame count, and only the CRT of the bundled ones" {
+ try std.testing.expect(readsTime("o = vec4(sin(iTime));"));
+ try std.testing.expect(readsTime("float f = float(iFrame % 64);"));
+ try std.testing.expect(readsTime("x = iTimeCursorChange;"));
+ try std.testing.expect(!readsTime("o = texture(iChannel0, p / iResolution.xy);"));
+ for (std.enums.values(Scene)) |scene| try std.testing.expectEqual(scene == .crt, sceneAnimated(scene));
+}