diff options
Diffstat (limited to 'src/shader_build.zig')
| -rw-r--r-- | src/shader_build.zig | 34 |
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)); +} |
