From f962de52df4b3d33438150920ae95b8708b0dde0 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 22 Sep 2026 16:44:05 -0300 Subject: gui: PARDES_TEST_LATENCY trace of input reads and frame fences --- src/gui/gui.zig | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'src/gui/gui.zig') diff --git a/src/gui/gui.zig b/src/gui/gui.zig index 8da6f933..17bdb46f 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -2096,6 +2096,12 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u log.err("PARDES_TEST requires PARDES_TEST_CAPTURE_DIR", .{}); return error.SdlInit; } + if (test_mode) if (env.get("PARDES_TEST_LATENCY")) |path| { + var path_buf: [4096]u8 = undefined; + if (std.fmt.bufPrintSentinel(&path_buf, "{s}", .{path}, 0)) |z| + latency_fd = libc.open(z, .{ .ACCMODE = .WRONLY, .CREAT = true, .TRUNC = true, .CLOEXEC = true }, @as(libc.mode_t, 0o644)) + else |_| {} + }; _ = c.SDL_SetHint("SDL_JOYSTICK_HIDAPI", "1"); // SDL_HINT_JOYSTICK_HIDAPI _ = c.SDL_SetHint("SDL_JOYSTICK_HIDAPI_STEAMDECK", "1"); // ..._STEAMDECK @@ -2105,6 +2111,8 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u } var win_flags: c.SDL_WindowFlags = c.SDL_WINDOW_RESIZABLE; if (!test_mode) win_flags |= c.SDL_WINDOW_HIGH_PIXEL_DENSITY; + // A latency run renders to the capture texture; keep it off the desktop. + if (latency_fd >= 0) win_flags |= c.SDL_WINDOW_HIDDEN; const native_wayland = !test_mode and std.mem.eql(u8, std.mem.span(c.SDL_GetCurrentVideoDriver()), "wayland"); // Background-only opacity needs per-pixel alpha; whole-window compositor // opacity would dim the text as well. SDL requires this flag at creation. @@ -2769,6 +2777,7 @@ const StdinFeed = struct { } const rn = libc.read(0, f.buf[f.fill..].ptr, f.buf.len - f.fill); if (rn < 0) return out; + if (rn > 0) latencyNote("R {d}\n", .{monoNs()}); if (rn == 0) { out.eof = true; return out; @@ -3884,6 +3893,7 @@ fn present(ctx: ?*anyopaque, surface: *const pardes.Surface) void { const s = shellOf(ctx); const core = s.core; const g = s.gui orelse return; + frame_started_ns = monoNs(); const frame = @constCast(surface); s.surface = frame; const scene_requested = core.settings.scene_effects.crt or @@ -6371,6 +6381,25 @@ test "background opacity retains exact endpoints and percentage precision" { } } +/// PARDES_TEST_LATENCY trace for an external driver, CLOCK_MONOTONIC ns: +/// `R t` when stdin input is read, `F start fence` per captured frame (present +/// entry, and the GPU fence of that frame). Nothing is written without it. +var latency_fd: c_int = -1; +var frame_started_ns: u64 = 0; + +fn monoNs() u64 { + var ts: libc.timespec = undefined; + if (libc.clock_gettime(.MONOTONIC, &ts) != 0) return 0; + return @as(u64, @intCast(ts.sec)) * std.time.ns_per_s + @as(u64, @intCast(ts.nsec)); +} + +fn latencyNote(comptime fmt: []const u8, args: anytype) void { + if (latency_fd < 0) return; + var buf: [64]u8 = undefined; + const line = std.fmt.bufPrint(&buf, fmt, args) catch return; + _ = libc.write(latency_fd, line.ptr, line.len); +} + fn captureFrame(g: *Gui, gpa: std.mem.Allocator, cmd: *c.SDL_GPUCommandBuffer, target: *c.SDL_GPUTexture, sw: u32, sh: u32) !void { const size = c.SDL_CalculateGPUTextureFormatSize(g.swapchain_format, sw, sh, 1); if (size == 0) { @@ -6398,6 +6427,7 @@ fn captureFrame(g: *Gui, gpa: std.mem.Allocator, cmd: *c.SDL_GPUCommandBuffer, t defer c.SDL_ReleaseGPUFence(g.device, fence); var fences = [_]*c.SDL_GPUFence{fence}; if (!c.SDL_WaitForGPUFences(g.device, true, &fences, 1)) return error.GpuSubmit; + latencyNote("F {d} {d}\n", .{ frame_started_ns, monoNs() }); const mapped = c.SDL_MapGPUTransferBuffer(g.device, xfer, false) orelse return error.GpuMap; defer c.SDL_UnmapGPUTransferBuffer(g.device, xfer); -- cgit v1.3