summaryrefslogtreecommitdiff
path: root/src/gui/gui.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/gui.zig')
-rw-r--r--src/gui/gui.zig334
1 files changed, 169 insertions, 165 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index 2cde5edf..ab1875b7 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -12,7 +12,7 @@ const look = @import("../look.zig");
const message = pardes.Messages.Message;
const file_watch = @import("../file_watch.zig");
const deck = @import("deck.zig");
-const crt = @import("crt.zig");
+const Post = @import("Post.zig");
const pet = @import("pet.zig");
const fonts = @import("../fonts.zig"); // the Font builtin's half of the seam
const selection_pipe = @import("../selection_pipe.zig");
@@ -43,8 +43,6 @@ const overlay_vert_spv = @embedFile("overlay.vert.spv");
const overlay_frag_spv = @embedFile("overlay.frag.spv");
const image_vert_spv = @embedFile("image.vert.spv");
const image_frag_spv = @embedFile("image.frag.spv");
-const crt_vert_spv = @embedFile("crt.vert.spv");
-const crt_frag_spv = @embedFile("crt.frag.spv");
const bg_default = [3]u8{ 18, 18, 18 };
const fg_default = [3]u8{ 204, 204, 204 };
@@ -202,18 +200,17 @@ const touch_click_flash_max_frames: u8 = 14;
const touch_click_flash_vertices: usize = 1400;
const touch_scroll_tick: f32 = 0.02;
-test "a persistent scene presents a new panel's frame zero before advancing it" {
+test "a new panel's frame zero is presented before it advances" {
const core = try pardes.Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer core.deinit();
var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
defer arena.deinit();
const frame = pardes.animation.frame_ns;
- core.settings.scene_effects.crt = true;
core.advance(100);
- const scene_frame = try core.render(arena.allocator());
- core.acknowledgePanelPresentation(scene_frame.panelTracks());
- try std.testing.expectEqual(@as(?u64, 100 + frame), core.nextWake());
+ const idle = try core.render(arena.allocator());
+ core.acknowledgePanelPresentation(idle.panelTracks());
+ try std.testing.expectEqual(@as(?u64, null), core.nextWake());
core.settings.panel_transition = .slide;
core.update(.{ .command = "Newcol" });
@@ -235,10 +232,9 @@ test "a persistent scene presents a new panel's frame zero before advancing it"
for (second.panelTracks()) |track| try std.testing.expectEqual(@as(u16, 1), track.frame);
}
-// Per pane: background, rule, two grip strokes, two square rails, and the
-// rule under each of its notice chips.
-const pane_chrome_vertices: usize = 6 * (6 + pardes.Pane.Notices.max);
-const max_overlay_vertices: usize = 24 + pet.max_vertices + pardes.MAX_COLS * 6 + pardes.MAX_PANES * pane_chrome_vertices + touch_click_flash_vertices + max_touch_points * (1100 + max_touch_trail_points * overlay_circle_vertices);
+// The triangle overlay holds only the pet and the touch view: the chrome is
+// decor.
+const max_overlay_vertices: usize = pet.max_vertices + touch_click_flash_vertices + max_touch_points * (1100 + max_touch_trail_points * overlay_circle_vertices);
const font_px_step: f32 = 2.0;
const font_px_min: f32 = 8.0;
@@ -1765,7 +1761,6 @@ const Gui = struct {
decor_pipeline: *c.SDL_GPUGraphicsPipeline,
overlay_pipeline: *c.SDL_GPUGraphicsPipeline,
image_pipeline: *c.SDL_GPUGraphicsPipeline,
- crt_pipeline: *c.SDL_GPUGraphicsPipeline,
atlas_tex: *c.SDL_GPUTexture,
atlas_sampler: *c.SDL_GPUSampler,
linear_sampler: *c.SDL_GPUSampler,
@@ -1838,7 +1833,8 @@ const Gui = struct {
scene_tex_h: u32 = 0,
scene_failures: u8 = 0,
scene_target_failed: bool = false,
- presented_scene: crt.Frame = .{},
+ /// The post chain, run over the frame in scene_tex.
+ post: Post = .{},
pointer_present: bool = false,
arrow_cursor: ?*c.SDL_Cursor = null,
link_cursor: ?*c.SDL_Cursor = null,
@@ -1977,7 +1973,9 @@ fn windowCells(g: *const Gui) GridCells {
};
}
-fn windowPointToPixels(geometry: WindowGeometry, x: f32, y: f32) crt.Point {
+const Point = struct { x: f32, y: f32 };
+
+fn windowPointToPixels(geometry: WindowGeometry, x: f32, y: f32) Point {
return .{
.x = x * geometry.pixel_w / geometry.window_w,
.y = y * geometry.pixel_h / geometry.window_h,
@@ -1991,7 +1989,7 @@ test "high-density window coordinates map once to render pixels" {
.pixel_w = 1120,
.pixel_h = 720,
};
- try std.testing.expectEqual(crt.Point{ .x = 700, .y = 240 }, windowPointToPixels(geometry, 350, 120));
+ try std.testing.expectEqual(Point{ .x = 700, .y = 240 }, windowPointToPixels(geometry, 350, 120));
}
fn fixedCellLayout(g: *const Gui) CellLayout {
@@ -2397,7 +2395,6 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u
const decor_pipeline = try makePipeline(device, swapchain_format, decor_frag_spv, 0, backgroundLayerBlend());
const overlay_pipeline = try makeOverlayPipeline(device, swapchain_format);
const image_pipeline = try makeImagePipeline(device, swapchain_format);
- const crt_pipeline = try makeCrtPipeline(device, swapchain_format);
const overlay_buffer_size: u32 = @intCast(max_overlay_vertices * @sizeOf(OverlayVertex));
var ovb_info = c.SDL_GPUBufferCreateInfo{ .usage = c.SDL_GPU_BUFFERUSAGE_VERTEX, .size = overlay_buffer_size, .props = 0 };
@@ -2427,7 +2424,6 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u
.decor_pipeline = decor_pipeline,
.overlay_pipeline = overlay_pipeline,
.image_pipeline = image_pipeline,
- .crt_pipeline = crt_pipeline,
.atlas_tex = atlas_tex,
.atlas_sampler = atlas_sampler,
.linear_sampler = linear_sampler,
@@ -2464,7 +2460,6 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u
c.SDL_ReleaseGPUGraphicsPipeline(device, g.pipeline);
c.SDL_ReleaseGPUGraphicsPipeline(device, g.decor_pipeline);
c.SDL_ReleaseGPUGraphicsPipeline(device, g.overlay_pipeline);
- c.SDL_ReleaseGPUGraphicsPipeline(device, g.crt_pipeline);
c.SDL_ReleaseGPUTexture(device, g.atlas_tex);
c.SDL_ReleaseGPUSampler(device, g.atlas_sampler);
c.SDL_ReleaseGPUSampler(device, g.linear_sampler);
@@ -2490,6 +2485,7 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u
g.prepared_images.deinit(gpa);
g.cover.deinit(gpa);
g.decor.deinit(gpa);
+ g.post.deinit(gpa, device);
}
defer c.SDL_ReleaseGPUTransferBuffer(device, g.image_vxfer);
defer c.SDL_ReleaseGPUBuffer(device, g.image_vbuf);
@@ -2796,7 +2792,6 @@ fn paintAttached(g: *Gui, gpa: std.mem.Allocator, client: *detached_client.Clien
gpa,
null,
&surface,
- .{},
false,
) catch |err| blk: {
log.err("render: {t}", .{err});
@@ -3065,23 +3060,14 @@ const StdinFeed = struct {
f.fill = len - seq_start;
break;
};
- if (parseFinger(body[0..end])) |finger_in| {
- var finger = finger_in;
+ if (parseFinger(body[0..end])) |finger| {
if (g) |gp| {
var wpw: c_int = 0;
var wph: c_int = 0;
_ = c.SDL_GetWindowSizeInPixels(gp.window, &wpw, &wph);
const win_w: f32 = @floatFromInt(@max(wpw, 1));
const win_h: f32 = @floatFromInt(@max(wph, 1));
- if (mapScenePoint(gp, finger.x * win_w, finger.y * win_h, win_w, win_h)) |m| {
- finger.x = m.x / win_w;
- finger.y = m.y / win_h;
- handleFinger(&gp.touch, in, finger, win_w, win_h, @floatFromInt(gp.cell_w), @floatFromInt(gp.cell_h), @floatFromInt(gp.tagline_width));
- } else {
- finger.kind = .cancel;
- handleFinger(&gp.touch, in, finger, win_w, win_h, @floatFromInt(gp.cell_w), @floatFromInt(gp.cell_h), @floatFromInt(gp.tagline_width));
- in.post(.pointer_leave);
- }
+ handleFinger(&gp.touch, in, finger, win_w, win_h, @floatFromInt(gp.cell_w), @floatFromInt(gp.cell_h), @floatFromInt(gp.tagline_width));
} else if (in.core) |core| {
handleFinger(&grid_touch, in, finger, @floatFromInt(core.screen_w), @floatFromInt(core.screen_h), 1, 1, 1);
}
@@ -3288,6 +3274,20 @@ const Input = struct {
};
fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void {
+ // What the post chain sees of the pointer and the focus.
+ switch (sev.type) {
+ c.SDL_EVENT_MOUSE_MOTION => {
+ const at = windowPointToPixels(windowGeometry(g.window), sev.motion.x, sev.motion.y);
+ g.post.mouse(at.x, at.y, null);
+ },
+ c.SDL_EVENT_MOUSE_BUTTON_DOWN, c.SDL_EVENT_MOUSE_BUTTON_UP => {
+ const at = windowPointToPixels(windowGeometry(g.window), sev.button.x, sev.button.y);
+ g.post.mouse(at.x, at.y, sev.button.down);
+ },
+ c.SDL_EVENT_WINDOW_FOCUS_GAINED => g.post.focus(true),
+ c.SDL_EVENT_WINDOW_FOCUS_LOST => g.post.focus(false),
+ else => {},
+ }
switch (sev.type) {
c.SDL_EVENT_QUIT, c.SDL_EVENT_WINDOW_CLOSE_REQUESTED => in.close(),
c.SDL_EVENT_WINDOW_MOUSE_LEAVE => {
@@ -3460,7 +3460,7 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void {
},
c.SDL_EVENT_FINGER_DOWN, c.SDL_EVENT_FINGER_MOTION, c.SDL_EVENT_FINGER_UP, c.SDL_EVENT_FINGER_CANCELED => {
const f = sev.tfinger;
- var finger: Finger = .{
+ const finger: Finger = .{
.kind = switch (sev.type) {
c.SDL_EVENT_FINGER_DOWN => .down,
c.SDL_EVENT_FINGER_MOTION => .motion,
@@ -3479,15 +3479,7 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void {
const win_h: f32 = @floatFromInt(@max(ph, 1));
const fcw: f32 = @floatFromInt(g.cell_w);
const fch: f32 = @floatFromInt(g.cell_h);
- if (mapScenePoint(g, finger.x * win_w, finger.y * win_h, win_w, win_h)) |m| {
- finger.x = m.x / win_w;
- finger.y = m.y / win_h;
- handleFinger(&g.touch, in, finger, win_w, win_h, fcw, fch, @floatFromInt(g.tagline_width));
- } else {
- finger.kind = .cancel;
- handleFinger(&g.touch, in, finger, win_w, win_h, fcw, fch, @floatFromInt(g.tagline_width));
- in.post(.pointer_leave);
- }
+ handleFinger(&g.touch, in, finger, win_w, win_h, fcw, fch, @floatFromInt(g.tagline_width));
},
c.SDL_EVENT_PINCH_BEGIN, c.SDL_EVENT_PINCH_UPDATE => in.post(.{ .pinch = sev.pinch.scale }),
c.SDL_EVENT_GAMEPAD_ADDED => {
@@ -3747,14 +3739,30 @@ test "tagline pointer mapping retains physical grips and carries logical text co
try std.testing.expectEqual(pardes.TagKind.workspace, topbar.tag_hit.?.kind);
}
-fn mapScenePoint(g: *const Gui, x: f32, y: f32, w: f32, h: f32) ?crt.Point {
- return crt.mapScene(x, y, w, h, g.presented_scene.effects, g.presented_scene.time_seconds);
+test "a click at the window's corner lands in the corner cell with a post chain on" {
+ const core = try pardes.Pardes.init(std.testing.allocator, .{ .cols = 80, .rows = 24 });
+ defer core.deinit();
+ try std.testing.expect(core.settings.post.set(.crt, "", true));
+ var g: Gui = undefined;
+ g.cell_w = 10;
+ g.cell_h = 20;
+ g.tagline_width = 6;
+ g.tagline_height = 12;
+ const geometry: WindowGeometry = .{ .window_w = 400, .window_h = 240, .pixel_w = 800, .pixel_h = 480 };
+ const last = mouseCellWithGeometry(&g, core, 399.9, 239.9, geometry).?;
+ try std.testing.expectEqual(@as(u16, 79), last.col);
+ try std.testing.expectEqual(@as(u16, 23), last.row);
+ const first = mouseCellWithGeometry(&g, core, 0, 0, geometry).?;
+ try std.testing.expectEqual(@as(u16, 0), first.col);
+ try std.testing.expectEqual(@as(u16, 0), first.row);
}
+/// Where a window point lands: the cell drawn under it. Input is identity:
+/// no post pass moves a pixel (the bundled Crt has no barrel), so a click
+/// is exactly where it looks.
fn mouseCellWithGeometry(g: *const Gui, core: ?*const pardes.Pardes, x: f32, y: f32, geometry: WindowGeometry) ?MouseCell {
const physical = windowPointToPixels(geometry, x, y);
- const mapped = mapScenePoint(g, physical.x, physical.y, geometry.pixel_w, geometry.pixel_h) orelse return null;
- return gridCellAtPixels(g, core, mapped.x, mapped.y);
+ return gridCellAtPixels(g, core, physical.x, physical.y);
}
fn mouseCell(g: *const Gui, core: ?*const pardes.Pardes, x: f32, y: f32) ?MouseCell {
@@ -4064,6 +4072,7 @@ fn waitInput(ctx: ?*anyopaque, timeout_ms: u32) void {
// shell's queue raises for a terminal's output, a completion, a file
// system request. What the shell moves or times itself polls every
// frame: a smooth scroll, a gamepad's sticks, the test stdin feed,
+ // a post chain that moves on its own (ShaderAnimation),
// a retired shell waiting to be killed, a present to retry. A
// minimized window sleeps through the core's animation too: nothing
// shows it, and it is caught up at once when the window is back.
@@ -4072,7 +4081,8 @@ fn waitInput(ctx: ?*anyopaque, timeout_ms: u32) void {
} else for (s.ptys) |slot| {
if (slot) |pt| if (pt.kill_at != 0) break true;
} else false;
- const polls = g.scroll_lag != 0 or g.scroll_delta != 0 or g.gamepad != null or s.test_mode or core.needs_frame or retiring;
+ const polls = g.scroll_lag != 0 or g.scroll_delta != 0 or g.gamepad != null or s.test_mode or core.needs_frame or retiring or
+ g.post.animating(core.settings.shader_animation);
const minimized = !s.test_mode and c.SDL_GetWindowFlags(g.window) & (c.SDL_WINDOW_MINIMIZED | c.SDL_WINDOW_OCCLUDED) != 0;
// A latency trace is fed on stdin, whose poll below paces the loop:
// waiting here too would make every tick cost two frames.
@@ -4157,6 +4167,10 @@ fn pollFrame(ctx: ?*anyopaque) void {
const geom = windowCells(g);
if (updateCoreResize(core, geom.cols, geom.rows, g.cell_w, g.cell_h, g.tagline_width, g.tagline_height)) resetScroll(g);
stepScroll(g, core, s.gpa);
+ g.post.sync(s.gpa, s.io, g.device, g.swapchain_format, core);
+ // Between the core's frames, a chain that moves on its own redraws alone.
+ if (g.post.animating(core.settings.shader_animation) and !core.needs_frame and s.presented and
+ c.SDL_GetTicksNS() -| g.post.last_ns >= pardes.animation.frame_ns) redrawPost(g, s.gpa);
}
fn finishWindowOpacity(core: *pardes.Pardes, applied: *u8, failure: ?[]const u8) void {
@@ -4216,15 +4230,13 @@ fn present(ctx: ?*anyopaque, surface: *const pardes.Surface) void {
frame_started_ns = monoNs();
const frame = @constCast(surface);
s.surface = frame;
- const scene_requested = core.settings.scene_effects.crt or
- core.settings.scene_effects.ripple or core.settings.scene_effects.glitch;
+ const scene_requested = g.post.ready();
if (!scene_requested) g.scene_failures = 0;
s.presented = renderFrame(
g,
s.gpa,
core,
frame,
- core.settings.scene_effects,
core.settings.debug,
) catch |err| blk: {
log.err("render: {t}", .{err});
@@ -5190,7 +5202,6 @@ fn renderFrame(
gpa: std.mem.Allocator,
core: ?*const pardes.Pardes,
surface: *pardes.Surface,
- scene_effects: pardes.animation.SceneEffect,
debug_on: bool,
) !bool {
if (surface.pointer_shape != g.pointer_shape) {
@@ -5205,48 +5216,19 @@ fn renderFrame(
defer if (!command_consumed) {
_ = c.SDL_SubmitGPUCommandBuffer(cmd);
};
- var sw: u32 = 0;
- var sh: u32 = 0;
- var target: *c.SDL_GPUTexture = undefined;
- if (g.capture) {
- sw = @as(u32, surface.cols) * g.cell_w + g.capture_pad;
- sh = @as(u32, surface.rows) * g.cell_h + g.capture_pad;
- if (sw == 0 or sh == 0) {
- command_consumed = true;
- _ = c.SDL_SubmitGPUCommandBuffer(cmd);
- return false;
- }
- try ensureCaptureTexture(g, sw, sh);
- target = g.capture_tex.?;
- } else if (g.soft_present) {
- var pw: c_int = 0;
- var ph: c_int = 0;
- if (!c.SDL_GetWindowSizeInPixels(g.window, &pw, &ph) or pw <= 0 or ph <= 0) {
- command_consumed = true;
- _ = c.SDL_SubmitGPUCommandBuffer(cmd);
- return false;
- }
- sw = @intCast(pw);
- sh = @intCast(ph);
- try ensureCaptureTexture(g, sw, sh);
- target = g.capture_tex.?;
- } else {
- var swap_tex: ?*c.SDL_GPUTexture = null;
- if (!c.SDL_AcquireGPUSwapchainTexture(cmd, g.window, &swap_tex, &sw, &sh)) {
- command_consumed = true;
- _ = c.SDL_SubmitGPUCommandBuffer(cmd);
- return false;
- }
- target = swap_tex orelse { // swapchain busy: skip the frame
- command_consumed = true;
- _ = c.SDL_SubmitGPUCommandBuffer(cmd);
- return false;
- };
- }
+ const frame_target = try acquireTarget(g, cmd, @as(u32, surface.cols) * g.cell_w + g.capture_pad, @as(u32, surface.rows) * g.cell_h + g.capture_pad) orelse {
+ command_consumed = true;
+ _ = c.SDL_SubmitGPUCommandBuffer(cmd);
+ return false;
+ };
+ const target = frame_target.texture;
+ const sw = frame_target.w;
+ const sh = frame_target.h;
const win_w: f32 = @floatFromInt(sw);
const win_h: f32 = @floatFromInt(sh);
- var scene_on = scene_effects.crt or scene_effects.ripple or scene_effects.glitch;
+ // The post chain runs over a local frame, from the scene texture.
+ var scene_on = core != null and g.post.ready();
if (scene_on) ensureSceneTexture(g, sw, sh) catch {
g.scene_target_failed = true;
scene_on = false;
@@ -5559,64 +5541,87 @@ fn renderFrame(
drawOverlayGpu(g, cmd, rp, overlay_count);
c.SDL_EndGPURenderPass(rp);
- var rendered_scene: crt.Frame = .{};
if (scene_on) {
- var crt_target = std.mem.zeroes(c.SDL_GPUColorTargetInfo);
- crt_target.texture = target;
- crt_target.load_op = c.SDL_GPU_LOADOP_DONT_CARE;
- crt_target.store_op = c.SDL_GPU_STOREOP_STORE;
- const crt_pass = c.SDL_BeginGPURenderPass(cmd, &crt_target, 1, null);
- c.SDL_BindGPUGraphicsPipeline(crt_pass, g.crt_pipeline);
- const samp_binding = c.SDL_GPUTextureSamplerBinding{ .texture = g.scene_tex.?, .sampler = g.linear_sampler };
- c.SDL_BindGPUFragmentSamplers(crt_pass, 0, &samp_binding, 1);
- const now_ns = c.SDL_GetTicksNS();
- const time_wrap_ns: u64 = 4096 * std.time.ns_per_s;
- const time_seconds: f32 = @floatCast(
- @as(f64, @floatFromInt(now_ns % time_wrap_ns)) / @as(f64, std.time.ns_per_s),
- );
- rendered_scene = .{
- .effects = .{
- .crt = scene_effects.crt,
- .ripple = scene_effects.ripple,
- .glitch = scene_effects.glitch,
- },
- .time_seconds = time_seconds,
- };
- const crt_uniforms = [8]f32{
- time_seconds,
- @floatFromInt((now_ns / (std.time.ns_per_s / 60)) % 65_536),
- @floatFromInt(sw),
- @floatFromInt(sh),
- @floatFromInt(@intFromBool(scene_effects.crt)),
- @floatFromInt(@intFromBool(scene_effects.ripple)),
- @floatFromInt(@intFromBool(scene_effects.glitch)),
- 0,
+ if (core) |p| {
+ var boxes: [max_cursors]CursorBox = undefined;
+ const cursors = if (cursors_shown) cursorBoxes(g, surface, layout, win_h, &boxes) else 0;
+ g.post.describe(.{
+ .cursor = if (cursors > 0) boxes[0] else null,
+ .background = chrome.page orelse bg_default,
+ .foreground = chrome.fg orelse fg_default,
+ .selection_foreground = p.theme().sel_fg,
+ .selection_background = p.theme().sel_bg,
+ });
+ }
+ const chain_zone = tracy.zone(@src(), "post chain");
+ defer chain_zone.end();
+ g.post.draw(g.device, cmd, g.linear_sampler, g.swapchain_format, g.scene_tex.?, target, sw, sh, c.SDL_GetTicksNS(), background_opacity >= 1) catch {
+ g.scene_target_failed = true;
};
- c.SDL_PushGPUFragmentUniformData(cmd, 0, &crt_uniforms, @sizeOf(@TypeOf(crt_uniforms)));
- c.SDL_DrawGPUPrimitives(crt_pass, 3, 1, 0, 0);
- c.SDL_EndGPURenderPass(crt_pass);
}
+ command_consumed = true;
+ const shown = try finishFrame(g, gpa, cmd, frame_target, if (groups.tracks != 0) groups.items[1].track.?.frame else null);
+ if (shown and groups.tracks == 0) rememberPresentedImages(g, gpa, surface);
+ return shown;
+}
+
+const Target = struct { texture: *c.SDL_GPUTexture, w: u32, h: u32 };
+
+/// What this frame draws into: the capture texture in test mode (the grid's
+/// size plus PARDES_TEST_PAD) or with a soft presenter, else the swapchain's
+/// image; null when there is none to be had this time.
+fn acquireTarget(g: *Gui, cmd: *c.SDL_GPUCommandBuffer, capture_w: u32, capture_h: u32) !?Target {
if (g.capture) {
- command_consumed = true;
- try captureFrame(g, gpa, cmd, target, sw, sh, if (groups.tracks != 0) groups.items[1].track.?.frame else null);
- g.presented_scene = rendered_scene;
- if (groups.tracks == 0) rememberPresentedImages(g, gpa, surface);
+ if (capture_w == 0 or capture_h == 0) return null;
+ try ensureCaptureTexture(g, capture_w, capture_h);
+ return .{ .texture = g.capture_tex.?, .w = capture_w, .h = capture_h };
+ }
+ if (g.soft_present) {
+ var pw: c_int = 0;
+ var ph: c_int = 0;
+ if (!c.SDL_GetWindowSizeInPixels(g.window, &pw, &ph) or pw <= 0 or ph <= 0) return null;
+ try ensureCaptureTexture(g, @intCast(pw), @intCast(ph));
+ return .{ .texture = g.capture_tex.?, .w = @intCast(pw), .h = @intCast(ph) };
+ }
+ var swap_tex: ?*c.SDL_GPUTexture = null;
+ var sw: u32 = 0;
+ var sh: u32 = 0;
+ if (!c.SDL_AcquireGPUSwapchainTexture(cmd, g.window, &swap_tex, &sw, &sh)) return null;
+ // A busy swapchain skips the frame.
+ return .{ .texture = swap_tex orelse return null, .w = sw, .h = sh };
+}
+
+/// Submits the frame: captured, handed to the soft presenter, or presented.
+fn finishFrame(g: *Gui, gpa: std.mem.Allocator, cmd: *c.SDL_GPUCommandBuffer, target: Target, transition: ?u16) !bool {
+ if (g.capture) {
+ try captureFrame(g, gpa, cmd, target.texture, target.w, target.h, transition);
return true;
}
if (g.soft_present) {
- command_consumed = true;
- try softPresentFrame(g, cmd, target, sw, sh);
- g.presented_scene = rendered_scene;
- if (groups.tracks == 0) rememberPresentedImages(g, gpa, surface);
+ try softPresentFrame(g, cmd, target.texture, target.w, target.h);
return true;
}
- command_consumed = true;
- const submitted = c.SDL_SubmitGPUCommandBuffer(cmd);
- if (submitted) {
- g.presented_scene = rendered_scene;
- if (groups.tracks == 0) rememberPresentedImages(g, gpa, surface);
+ return c.SDL_SubmitGPUCommandBuffer(cmd);
+}
+
+/// Redraw level A (docs ยง5.5): the post chain alone, over the frame the core
+/// last drew, for a pass that moves on its own. No core render, no cell.
+fn redrawPost(g: *Gui, gpa: std.mem.Allocator) void {
+ const scene = g.scene_tex orelse return;
+ const cmd = c.SDL_AcquireGPUCommandBuffer(g.device) orelse return;
+ const target = (acquireTarget(g, cmd, g.scene_tex_w, g.scene_tex_h) catch null) orelse {
+ _ = c.SDL_SubmitGPUCommandBuffer(cmd);
+ return;
+ };
+ // A window the retained frame no longer fits waits for the core's.
+ if (target.w != g.scene_tex_w or target.h != g.scene_tex_h) {
+ _ = c.SDL_SubmitGPUCommandBuffer(cmd);
+ return;
}
- return submitted;
+ const zone = tracy.zone(@src(), "post chain redraw");
+ g.post.draw(g.device, cmd, g.linear_sampler, g.swapchain_format, scene, target.texture, target.w, target.h, c.SDL_GetTicksNS(), backgroundOpacity(g.applied_window_opacity) >= 1) catch {};
+ zone.end();
+ _ = finishFrame(g, gpa, cmd, target, null) catch {};
}
const ResolvedCell = struct {
@@ -7016,25 +7021,7 @@ fn makeImagePipeline(device: *c.SDL_GPUDevice, color_format: c.SDL_GPUTextureFor
return c.SDL_CreateGPUGraphicsPipeline(device, &info) orelse error.GpuCreate;
}
-fn makeCrtPipeline(device: *c.SDL_GPUDevice, color_format: c.SDL_GPUTextureFormat) !*c.SDL_GPUGraphicsPipeline {
- const vs = try makeShader(device, crt_vert_spv, c.SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
- defer c.SDL_ReleaseGPUShader(device, vs);
- const fs = try makeShader(device, crt_frag_spv, c.SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
- defer c.SDL_ReleaseGPUShader(device, fs);
-
- var col_desc = c.SDL_GPUColorTargetDescription{ .format = color_format, .blend_state = std.mem.zeroes(c.SDL_GPUColorTargetBlendState) };
- var info = std.mem.zeroes(c.SDL_GPUGraphicsPipelineCreateInfo);
- info.vertex_shader = vs;
- info.fragment_shader = fs;
- info.primitive_type = c.SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
- info.rasterizer_state.fill_mode = c.SDL_GPU_FILLMODE_FILL;
- info.rasterizer_state.cull_mode = c.SDL_GPU_CULLMODE_NONE;
- info.multisample_state.sample_count = c.SDL_GPU_SAMPLECOUNT_1;
- info.target_info = .{ .color_target_descriptions = &col_desc, .num_color_targets = 1 };
- return c.SDL_CreateGPUGraphicsPipeline(device, &info) orelse error.GpuCreate;
-}
-
-fn makeShader(device: *c.SDL_GPUDevice, code: []const u8, stage: c.SDL_GPUShaderStage, n_samplers: u32, n_uniform_buffers: u32) !*c.SDL_GPUShader {
+pub fn makeShader(device: *c.SDL_GPUDevice, code: []const u8, stage: c.SDL_GPUShaderStage, n_samplers: u32, n_uniform_buffers: u32) !*c.SDL_GPUShader {
var info = std.mem.zeroes(c.SDL_GPUShaderCreateInfo);
info.code = code.ptr;
info.code_size = code.len;
@@ -7519,25 +7506,32 @@ test "decor is read from the regions, each pane's in its own group" {
try std.testing.expectEqual(@as(u32, 0), groups.items[groups.ink].decor_count);
}
-/// The bar cursors: the grid's where no layer has the cell, and each layer's
-/// at its own pitch.
-fn cursorDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface, layout: CellLayout, win_w: f32, win_h: f32) !void {
+const CursorBox = Post.Frame.Cursor;
+const max_cursors = 1 + pardes.MAX_PANES + pardes.MAX_TAG_LAYERS;
+
+/// Every cursor on the frame where it is drawn, in window pixels: the grid's
+/// where no layer has its cell, and each layer's at that layer's pitch. A bar
+/// is an eighth of its cell wide, a block the whole cell.
+fn cursorBoxes(g: *const Gui, surface: *const pardes.Surface, layout: CellLayout, win_h: f32, out: *[max_cursors]CursorBox) usize {
const chrome = &surface.chrome;
+ const page = chrome.page orelse bg_default;
+ var n: usize = 0;
if (surface.cursor) |cursor| {
- if (cursor.bar and cursor.x < surface.cols and cursor.y < surface.rows and bodyLayerAt(surface, cursor.x, cursor.y) == null and tagLayerIn(surface.tagLayers(), cursor.x, cursor.y) == null) {
+ if (cursor.x < surface.cols and cursor.y < surface.rows and bodyLayerAt(surface, cursor.x, cursor.y) == null and tagLayerIn(surface.tagLayers(), cursor.x, cursor.y) == null) {
const cell = &surface.cells[@as(usize, cursor.y) * surface.cols + cursor.x];
const role = cellFontRole(cell);
const width: f32 = if (role == .tagline) @floatFromInt(g.tagline_width) else layout.w;
const height = std.math.clamp(if (role == .tagline) @as(f32, @floatFromInt(g.tagline_height)) else layout.h, 1.0, layout.h);
const x0 = @as(f32, @floatFromInt(cursor.x)) * layout.w;
const y0 = @as(f32, @floatFromInt(cursor.y)) * layout.h + @as(f32, @floatFromInt(taglineBandOffset(cursor.y, win_h, @intFromFloat(layout.h), @intFromFloat(height))));
- try addDecor(g, gpa, x0, y0, x0 + @max(1.0, width / 8.0), y0 + height, cursorColor(chrome, cell, role), 1, null, win_w, win_h);
+ out[n] = .{ .x0 = x0, .y0 = y0, .x1 = x0 + if (cursor.bar) @max(1.0, width / 8.0) else width, .y1 = y0 + height, .rgb = cursorColor(chrome, cell, role), .text = page, .bar = cursor.bar };
+ n += 1;
}
}
for (surface.bodyLayers()) |*layer| {
if (layer.rows == 0) continue;
const cursor = layer.cursor orelse continue;
- if (!cursor.bar or cursor.y >= layer.rows or cursor.x >= layer.cols) continue;
+ if (cursor.y >= layer.rows or cursor.x >= layer.cols) continue;
const left = @as(f32, @floatFromInt(layer.viewport.x)) * layout.w;
const bottom = @as(f32, @floatFromInt(layer.viewport.y + layer.viewport.h)) * layout.h;
const top = layer.rowTop(cursor.y, layout.h, @floatFromInt(g.tagline_height));
@@ -7547,12 +7541,13 @@ fn cursorDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface,
const width: f32 = if (role == .tagline) @floatFromInt(g.tagline_width) else layout.w;
const x = left + @as(f32, @floatFromInt(cursor.x)) * width;
const cell = &layer.cells[@as(usize, cursor.y) * layer.cols + cursor.x];
- try addDecor(g, gpa, x, top, x + @max(1, width / 8), @min(bottom, top + height), cursorColor(chrome, cell, role), 1, null, win_w, win_h);
+ out[n] = .{ .x0 = x, .y0 = top, .x1 = x + if (cursor.bar) @max(1, width / 8) else width, .y1 = @min(bottom, top + height), .rgb = cursorColor(chrome, cell, role), .text = page, .bar = cursor.bar };
+ n += 1;
}
for (surface.tagLayers()) |*layer| {
if (layer.rows == 0) continue;
const cursor = layer.cursor orelse continue;
- if (!cursor.bar or cursor.x >= layer.cols or cursor.y >= layer.rows) continue;
+ if (cursor.x >= layer.cols or cursor.y >= layer.rows) continue;
const width: f32 = @floatFromInt(g.tagline_width);
const left = @as(f32, @floatFromInt(layer.viewport.x)) * layout.w;
const right = left + @as(f32, @floatFromInt(layer.viewport.w)) * layout.w;
@@ -7560,8 +7555,17 @@ fn cursorDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface,
if (x >= right) continue;
const y = layer.viewport.y + cursor.y;
const top = @as(f32, @floatFromInt(y)) * layout.h + @as(f32, @floatFromInt(taglineBandOffset(y, win_h, g.cell_h, g.tagline_height)));
- try addDecor(g, gpa, x, top, @min(right, x + @max(1, width / 8)), top + @as(f32, @floatFromInt(g.tagline_height)), cursorColor(chrome, &layer.cells[@as(usize, cursor.y) * layer.cols + cursor.x], .tagline), 1, null, win_w, win_h);
+ out[n] = .{ .x0 = x, .y0 = top, .x1 = @min(right, x + if (cursor.bar) @max(1, width / 8) else width), .y1 = top + @as(f32, @floatFromInt(g.tagline_height)), .rgb = cursorColor(chrome, &layer.cells[@as(usize, cursor.y) * layer.cols + cursor.x], .tagline), .text = page, .bar = cursor.bar };
+ n += 1;
}
+ return n;
+}
+
+/// The bar cursors as decor; a block cursor is its cell's own colours.
+fn cursorDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface, layout: CellLayout, win_w: f32, win_h: f32) !void {
+ var boxes: [max_cursors]CursorBox = undefined;
+ for (boxes[0..cursorBoxes(g, surface, layout, win_h, &boxes)]) |box|
+ if (box.bar) try addDecor(g, gpa, box.x0, box.y0, box.x1, box.y1, box.rgb, 1, null, win_w, win_h);
}
/// The triangle overlay: the workspace pet and the touch debug view, over