summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 23:49:01 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commit7784ac98a09c0f7185d12b66307d67361e7de69d (patch)
treeaa712051aea87537c94d9b25e3a6d44ef86727d9
parent6d148bb9c433814835935ead30020e43781b7f03 (diff)
downloadpardes-7784ac98a09c0f7185d12b66307d67361e7de69d.tar.gz
pardes-7784ac98a09c0f7185d12b66307d67361e7de69d.zip
Lapis's cells are drawn unblended: a see-through cell over a plaque with no ink loses its quad, not the whole frame its opaque pipeline
The cells a plaque shows through are recorded as they are emitted; once the frame's instances are built, those with no ink are dropped, and the frame falls back to the blended pipeline only when one has ink or the page itself is see-through. At 4K (scale 2, 120x40, RTX 3050) a lapis draw took 695 us of GPU against forge's 471; blending every cell for the plaques' sake was 94 us of it (now 601 us). Pixels identical in hidden captures at 1x and 2x. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--src/gui/gui.zig42
1 files changed, 38 insertions, 4 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index 8fd8d360..c737450b 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -5872,6 +5872,7 @@ fn renderFrame(
if (surface.chrome.fx & 2 == 0 or surface.panelTracks().len != 0) g.hover_glow = .{ .now_ns = g.sel_glow.now_ns };
if (g.glide.moving()) g.glide.offset = g.glide.spring.value(g.glide.now_ns);
cell_decor = CellDecor.of(chrome);
+ plaque_through_len = 0;
// G13: a body's dots follow its text at a quarter of its speed: its
// first line, and a glide's picture offset, in rows.
if (chrome.fx & 8 != 0 and chrome.decor_dots != null) for (surface.regionList()) |region| {
@@ -6159,6 +6160,25 @@ fn renderFrame(
for (groups.items[0..groups.len], 0..) |group, index|
std.debug.assert(cell_next[index] == group.cell_start + group.cell_count);
shifted = emitScrollRows(g, instances, cell_total, surface, layout, win_w, win_h, page);
+ // Over a theme's plaques a see-through cell with no ink blends to
+ // the plaque under it, so it draws nothing: its quad goes, and the
+ // cells are drawn unblended (a third of the GPU's frame at 4K)
+ // unless one see-through cell does have ink, or the page itself is
+ // see-through.
+ if (cell_decor.nplaques != 0) {
+ const space_u = @as(f32, @floatFromInt(g.space_slot.u)) / @as(f32, @floatFromInt(atlas_w));
+ const space_v = @as(f32, @floatFromInt(g.space_slot.v)) / @as(f32, @floatFromInt(atlas_h));
+ if (page.clear or plaque_through_len > plaque_through.len) cell_decor.inked_clear = true;
+ for (plaque_through[0..@min(plaque_through_len, plaque_through.len)]) |at| {
+ const instance = &instances[at];
+ // A grip's button takes its cell's ground back (emitSurfaceCell).
+ if (instance.effect & clear_bg_bit == 0) continue;
+ if (instance.u0 == space_u and instance.v0 == space_v) {
+ instance.x1 = instance.x0;
+ instance.y1 = instance.y0;
+ } else cell_decor.inked_clear = true;
+ }
+ }
@memcpy(instances[cell_total + shifted ..][0..decor_len], g.decor.items);
c.SDL_UnmapGPUTransferBuffer(g.device, g.vxfer.?);
@@ -6190,7 +6210,7 @@ fn renderFrame(
// Blended over what is under: a theme's plaques show through
// blank tag cells; a dissolve's old cells fade over its new.
const dissolving = if (group.track) |active| active.effect == .dissolve else false;
- c.SDL_BindGPUGraphicsPipeline(rp, if (cell_decor.nplaques != 0 or dissolving) g.pipeline_over else g.pipeline);
+ c.SDL_BindGPUGraphicsPipeline(rp, if (cell_decor.inked_clear or dissolving) g.pipeline_over else g.pipeline);
const samp_binding = c.SDL_GPUTextureSamplerBinding{ .texture = g.atlas_tex, .sampler = g.atlas_sampler };
c.SDL_BindGPUFragmentSamplers(rp, 0, &samp_binding, 1);
if (group.cell_count != 0) {
@@ -6516,7 +6536,7 @@ fn emitTagLayer(g: *Gui, instances: [*]CellInstance, next: *u32, layer: *const p
emitInstance(g, instances, next.*, 0, y, .{ .w = band_right - left, .h = bh, .x_off = left, .y_off = slide }, win_w, win_h, track, .body, (&background)[0..1], 0, null, old_layer, false, page);
// A theme's plaque is drawn under the band: the band's own ground
// lets it show (tagPlaques).
- if (cell_decor.nplaques != 0 and cell_decor.inPlaque(left + 0.5, top + 0.5)) instances[next.*].effect |= clear_bg_bit;
+ if (cell_decor.nplaques != 0 and cell_decor.inPlaque(left + 0.5, top + 0.5)) plaqueThrough(instances, next.*);
if (slide != 0) clipInstanceTop(&instances[next.*], top, win_h);
next.* += 1;
const cells = layer.cells[index * layer.cols ..][0..layer.cols];
@@ -6728,7 +6748,7 @@ fn emitSurfaceCell(
const cell_x = @as(f32, @floatFromInt(col)) * body_layout.w + body_layout.x_off;
const cell_y = @as(f32, @floatFromInt(row)) * body_layout.h + body_layout.y_off + 0.5;
if (cell_decor.nplaques != 0 and cell_decor.inPlaque(if (here.grip) cell_x + body_layout.w - 0.5 else cell_x + 0.5, cell_y))
- instances[next.*].effect |= clear_bg_bit;
+ plaqueThrough(instances, next.*);
next.* += 1;
// Tag glyphs keep their font size inside a full physical-width grip.
// A pane's grip cells are the cell shells' form (a dirty `*`): the
@@ -7060,7 +7080,7 @@ fn emitInstance(
if (cell_decor.nplaques != 0 and resolved.role == .tagline and cellCodepoint(&line[at]) == ' ' and
(std.mem.eql(u8, &resolved.bg, &cell_decor.grounds[0]) or std.mem.eql(u8, &resolved.bg, &cell_decor.grounds[1])) and
(cell_decor.inPlaque(px0 + 0.5, py0 + 0.5) or cell_decor.inPlaque(px0 + layout.w - 0.5, py0 + 0.5)))
- instances[idx].effect |= clear_bg_bit;
+ plaqueThrough(instances, idx);
if (cell_decor.dots) |dotted| {
if (!resolved.clear_bg and std.mem.eql(u8, &resolved.bg, &dotted)) {
instances[idx].effect |= dotted_bit;
@@ -9034,6 +9054,17 @@ fn updateDisplayScale(window: *c.SDL_Window) void {
/// it falls on, and pushLayerOpacity hands the shader its colours and
/// sizes. ponytail: one window per process, as display_scale.
var cell_decor: CellDecor = .{};
+/// The cells this frame lets a plaque show through, by instance, for
+/// renderFrame to drop the ones with no ink. Past its length the frame's
+/// cells are blended instead.
+var plaque_through: [16384]u32 = undefined;
+var plaque_through_len: usize = 0;
+
+fn plaqueThrough(instances: [*]CellInstance, at: u32) void {
+ instances[at].effect |= clear_bg_bit;
+ if (plaque_through_len < plaque_through.len) plaque_through[plaque_through_len] = at;
+ plaque_through_len += 1;
+}
const CellDecor = struct {
/// The page's ground, when the theme dots it.
dots: ?[3]u8 = null,
@@ -9048,6 +9079,9 @@ const CellDecor = struct {
nplaques: usize = 0,
grounds: [2][3]u8 = .{ .{ 0, 0, 0 }, .{ 0, 0, 0 } },
plaques_off: bool = false,
+ /// A see-through cell over the plaques has ink of its own, so this
+ /// frame's cells are blended (renderFrame).
+ inked_clear: bool = false,
/// G13: each body's rectangle and how far its dots have moved.
bodies: [pardes.MAX_PANES][4]f32 = undefined,