renderer: move custom shader uniforms out of frame state

These should not be independent per-frame, that makes the time
calculations all sorts of jank.

Also moves the uniforms struct layout in to `shadertoy.zig` and cleans
up the handling in general somewhat.
pull/7648/head
Qwerasd 2025-06-21 23:07:18 -06:00
parent 5bfdb1b9cf
commit c7a7474be0
4 changed files with 82 additions and 81 deletions

View File

@ -156,6 +156,19 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
/// The current GPU uniform values.
uniforms: shaderpkg.Uniforms,
/// Custom shader uniform values.
custom_shader_uniforms: shadertoy.Uniforms,
/// Timestamp we rendered out first frame.
///
/// This is used when updating custom shader uniforms.
first_frame_time: ?std.time.Instant = null,
/// Timestamp when we rendered out more recent frame.
///
/// This is used when updating custom shader uniforms.
last_frame_time: ?std.time.Instant = null,
/// The font structures.
font_grid: *font.SharedGrid,
font_shaper: font.Shaper,
@ -382,16 +395,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
front_texture: Texture,
back_texture: Texture,
uniforms: shaderpkg.PostUniforms,
/// The first time a frame was drawn.
/// This is used to update the time uniform.
first_frame_time: std.time.Instant,
/// The last time a frame was drawn.
/// This is used to update the time uniform.
last_frame_time: std.time.Instant,
/// Swap the front and back textures.
pub fn swap(self: *CustomShaderState) void {
std.mem.swap(Texture, &self.front_texture, &self.back_texture);
@ -417,22 +420,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
return .{
.front_texture = front_texture,
.back_texture = back_texture,
.uniforms = .{
.resolution = .{ 0, 0, 1 },
.time = 1,
.time_delta = 1,
.frame_rate = 1,
.frame = 1,
.channel_time = @splat(@splat(0)),
.channel_resolution = @splat(@splat(0)),
.mouse = .{ 0, 0, 0, 0 },
.date = .{ 0, 0, 0, 0 },
.sample_rate = 1,
},
.first_frame_time = try std.time.Instant.now(),
.last_frame_time = try std.time.Instant.now(),
};
}
@ -467,18 +454,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
self.front_texture = front_texture;
self.back_texture = back_texture;
self.uniforms.resolution = .{
@floatFromInt(width),
@floatFromInt(height),
1,
};
self.uniforms.channel_resolution[0] = .{
@floatFromInt(width),
@floatFromInt(height),
1,
0,
};
}
};
@ -689,6 +664,18 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
.use_linear_correction = options.config.blending == .@"linear-corrected",
},
},
.custom_shader_uniforms = .{
.resolution = .{ 0, 0, 1 },
.time = 0,
.time_delta = 0,
.frame_rate = 60, // not currently updated
.frame = 0,
.channel_time = @splat(@splat(0)),
.channel_resolution = @splat(@splat(0)),
.mouse = @splat(0), // not currently updated
.date = @splat(0), // not currently updated
.sample_rate = 0, // N/A, we don't have any audio
},
// Fonts
.font_grid = options.font_grid,
@ -1359,21 +1346,14 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
}
}
// Update custom shader uniforms if necessary.
try self.updateCustomShaderUniforms();
// Setup our frame data
try frame.uniforms.sync(&.{self.uniforms});
try frame.cells_bg.sync(self.cells.bg_cells);
const fg_count = try frame.cells.syncFromArrayLists(self.cells.fg_rows.lists);
// If we have custom shaders, update the animation time.
if (frame.custom_shader_state) |*state| {
const now = std.time.Instant.now() catch state.first_frame_time;
const since_ns: f32 = @floatFromInt(now.since(state.first_frame_time));
const delta_ns: f32 = @floatFromInt(now.since(state.last_frame_time));
state.uniforms.time = since_ns / std.time.ns_per_s;
state.uniforms.time_delta = delta_ns / std.time.ns_per_s;
state.last_frame_time = now;
}
// If our font atlas changed, sync the texture data
texture: {
const modified = self.font_grid.atlas_grayscale.modified.load(.monotonic);
@ -1446,10 +1426,10 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
if (frame.custom_shader_state) |*state| {
// We create a buffer on the GPU for our post uniforms.
// TODO: This should be a part of the frame state tbqh.
const PostBuffer = Buffer(shaderpkg.PostUniforms);
const PostBuffer = Buffer(shadertoy.Uniforms);
const uniform_buffer = try PostBuffer.initFill(
self.api.bufferOptions(),
&.{state.uniforms},
&.{self.custom_shader_uniforms},
);
defer uniform_buffer.deinit();
@ -1961,6 +1941,44 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
};
}
/// Update uniforms for the custom shaders, if necessary.
///
/// This should be called exactly once per frame, inside `drawFrame`.
fn updateCustomShaderUniforms(
self: *Self,
) !void {
// We only need to do this if we have custom shaders.
if (!self.has_custom_shaders) return;
const now = try std.time.Instant.now();
defer self.last_frame_time = now;
const first_frame_time = self.first_frame_time orelse t: {
self.first_frame_time = now;
break :t now;
};
const last_frame_time = self.last_frame_time orelse now;
const since_ns: f32 = @floatFromInt(now.since(first_frame_time));
self.custom_shader_uniforms.time = since_ns / std.time.ns_per_s;
const delta_ns: f32 = @floatFromInt(now.since(last_frame_time));
self.custom_shader_uniforms.time_delta = delta_ns / std.time.ns_per_s;
self.custom_shader_uniforms.frame += 1;
self.custom_shader_uniforms.resolution = .{
@floatFromInt(self.size.screen.width),
@floatFromInt(self.size.screen.height),
1,
};
self.custom_shader_uniforms.channel_resolution[0] = .{
@floatFromInt(self.size.screen.width),
@floatFromInt(self.size.screen.height),
1,
0,
};
}
/// Convert the terminal state to GPU cells stored in CPU memory. These
/// are then synced to the GPU in the next frame. This only updates CPU
/// memory and doesn't touch the GPU.

View File

@ -182,23 +182,6 @@ pub const Uniforms = extern struct {
};
};
/// The uniforms used for custom postprocess shaders.
pub const PostUniforms = extern struct {
// Note: all of the explicit alignments are copied from the
// MSL developer reference just so that we can be sure that we got
// it all exactly right.
resolution: [3]f32 align(16),
time: f32 align(4),
time_delta: f32 align(4),
frame_rate: f32 align(4),
frame: i32 align(4),
channel_time: [4][4]f32 align(16),
channel_resolution: [4][4]f32 align(16),
mouse: [4]f32 align(16),
date: [4]f32 align(16),
sample_rate: f32 align(4),
};
/// Initialize the MTLLibrary. A MTLLibrary is a collection of shaders.
fn initLibrary(device: objc.Object) !objc.Object {
const start = try std.time.Instant.now();

View File

@ -165,20 +165,6 @@ pub const Uniforms = extern struct {
};
};
/// The uniforms used for custom postprocess shaders.
pub const PostUniforms = extern struct {
resolution: [3]f32 align(16),
time: f32 align(4),
time_delta: f32 align(4),
frame_rate: f32 align(4),
frame: i32 align(4),
channel_time: [4][4]f32 align(16),
channel_resolution: [4][4]f32 align(16),
mouse: [4]f32 align(16),
date: [4]f32 align(16),
sample_rate: f32 align(4),
};
/// Initialize our custom shader pipelines. The shaders argument is a
/// set of shader source code, not file paths.
fn initPostPipelines(

View File

@ -9,6 +9,20 @@ const configpkg = @import("../config.zig");
const log = std.log.scoped(.shadertoy);
/// The uniform struct used for shadertoy shaders.
pub const Uniforms = extern struct {
resolution: [3]f32 align(16),
time: f32 align(4),
time_delta: f32 align(4),
frame_rate: f32 align(4),
frame: i32 align(4),
channel_time: [4][4]f32 align(16),
channel_resolution: [4][4]f32 align(16),
mouse: [4]f32 align(16),
date: [4]f32 align(16),
sample_rate: f32 align(4),
};
/// The target to load shaders for.
pub const Target = enum { glsl, msl };