summaryrefslogtreecommitdiff
path: root/docs/render-pipeline.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/render-pipeline.md')
-rw-r--r--docs/render-pipeline.md61
1 files changed, 46 insertions, 15 deletions
diff --git a/docs/render-pipeline.md b/docs/render-pipeline.md
index 2a679b8d..7c94fbea 100644
--- a/docs/render-pipeline.md
+++ b/docs/render-pipeline.md
@@ -491,21 +491,31 @@ Mirror ghostty 1.3.2 (`zig-pkg/ghostty-*/src/renderer/shadertoy.zig`,
≈3 cells, at the corners of a 4K screen, where the grips and rails are.
`crt.zig` is deleted. If the user insists on barrel in the bundled CRT, keep
`crt.zig`'s inverse keyed to "bundled crt active".
-- **Compile**: GLSL → SPIR-V at runtime with glslang (ghostty's `pkg/glslang`,
- the same vendored package in zig-pkg, GUI build only, behind `-Dshadertoy`).
- Compiled on load and on file change (file_watch), never on the frame path;
- the last good pipeline stays on error; the error is a pane message. glslang is
- C++ with its own allocator: a recorded exception to the "Zig allocator hooks
- in all C deps" policy.
+- **Compile** (decision 3): a user's file is compiled by spawning `glslc` with
+ the build's own flags, prefix and file on its stdin, SPIR-V on its stdout
+ (no file of ours anywhere; std's spawn allocates before fork), on a thread of its own when the file joins the chain, never on the
+ frame path; the result is taken in at the next loop step. The prefix ends in
+ `#line 1`, so glslc's errors count the file's own lines, and they name the
+ file, not `<stdin>`. Level A redraws once a refresh of the window's display. A failed compile keeps the file's last good
+ pipeline and says glslc's first line as a message; glslc missing is said
+ once, and only files stay off (the bundled passes are compiled with the
+ build, through the same prefix). No file watching: `Shader <path>` twice
+ (out, then in) compiles it again.
- **Animation mode**: `ShaderAnimation off|on|always` (ghostty's
`custom-shader-animation`): off = redraw only when content changes; on =
continuous while the window is focused; always = continuous. Continuous means
the shell redraws its retained Surface (3.1), never a core tick or render.
-- **Existing crt/ripple/glitch** become bundled Shadertoy files in
- `shaders/post/` selected by the same builtins; `scene_effects` and
- `crt.frag.glsl`/`crt.zig` are deleted. The bundled CRT is rewritten to
- quality (6.1); ripple and glitch are dropped unless the feel review keeps a
- rewritten version.
+- **Existing crt** becomes a bundled Shadertoy file in `shaders/post/` under
+ the same builtin; `scene_effects` and `crt.frag.glsl`/`crt.zig` are deleted.
+ It takes a level, `Crt 0..3` (0 off, `on` the default 2, `off` and bare
+ still work), passed as `pardesLevel`: 2 is the polished rewrite, 3 at least
+ as strong as the old CRT (pixels changed by more than 8 in a channel against
+ a plain frame: 18.3% vs 17.4%). Ripple and Glitch were rewritten too, then
+ removed at the user's word after a live look (decision 8). The chain is
+ `Crt` and `Shader <path>` in the order they were put in
+ (`Shader off` empties it of files), `ShaderAnimation off|on|always`
+ (default on). The core keeps no clock for it: level A redraws are the
+ shell's.
- macOS (out of scope) would need spirv-cross → MSL, as ghostty does.
Region filters (e.g. "tint/blur one pane") are NOT built. If one is ever needed:
@@ -803,15 +813,15 @@ stripped. No visual change until stage 9 unless stated.
|---|---|---|
| 0 | GUI capture goldens: a handful of scenes rendered hidden in capture mode, PPM hashes checked in (same machine/driver) | the safety net the GUI stages need; machine- and driver-specific, so a LOCAL gate only, never CI |
| 1 | pure moves: render → `src/draw.zig`; Presentation/animation out of layout.zig | no behaviour |
-| 2a | clock plumbing: `now_ns` into pump/events, `nextWake`, `advance`, deadlines for linger/hover; all shells pass now and sleep to the wake; six tick drivers deleted; ack moves into pump; capturePrevious only when transitions are on. Animations still compute `frame = floor(elapsed / 16 ms)`, so every curve is identical | pacing is corrected (144 Hz no longer ~25% slow, ssh no longer drifts): a bug fix allowed in this phase. Unit tests step an injected clock by exactly 16 ms, so their numbers do not change. The snapshot harness runs the binary with `--test-clock` (each core-time step is exactly 16.67 ms per tick request, as today), so goldens see the same sequence and `stable` (test/snapshot.zig ~861) is unaffected by wall-time pacing |
+| 2a | clock plumbing: `Host.now` (the shell's monotonic ns) and `Pardes.advance(now)`, which runs one `.tick` per whole 16 ms frame since core time last stood still; `nextWake()` lists every core animation and answers the next frame while anything moves, the wait's end while something only waits (message linger, look-hover delay), null when idle; the pump sleeps to it and draws only stepped frames. No shell posts `.tick` any more (tty's timer thread only wakes the wait; GUI's AnimationClock, web's JS tick bank and the detached server's and the board's ticks are gone; grid mode moves its virtual clock straight to the next wake). `acknowledgePanelPresentation` stays in the shells (revisit at stage 8: grid mode acks `&.{}` on purpose). capturePrevious only when transitions are on. `.tick` stays as the one-frame step `advance` and the unit tests use, so every curve and every asserted number is unchanged | pacing is corrected (144 Hz no longer ~25% slow, ssh no longer drifts): a bug fix allowed in this phase. PARDES_TEST_CLOCK (set by the snapshot harness): tty and the detached server answer a virtual clock that a timed-out wait moves exactly to the core's next wake, so goldens replay the same frame sequence on any machine load. Review fixes: tty's wake timer is interruptible (a newer, shorter request cuts short an older sleep); a wait (linger, hover delay) is jumped to its end in one step, never counted against the 240-frame catch-up cap; an overshoot under 1.5 ms after a step is let go, so 60 and 120 Hz take exactly one step per display frame. Known exceptions: the GUI still polls every 16 ms when idle (SDL events, gamepad, fs tick, smooth scroll depend on it; revisit at stage 8); a minimized GUI renders every stepped frame of a running animation (stage 8) |
| 2b | (on `fx`, feel-reviewed) Tween/Spring curves, settle rules, OKLab crossfades, new durations from §8.1 | a look change, kept apart from 2a so either can be bisected |
| 3 | paint functions take `s: *Surface`; delete the swap hack | mechanical, wide |
| 4 | PLACE: Region list built once; renderPane/tags/notices read their rects from it | wait for tag→Text; the refactor is already replacing BOX_H with `pane.tag_rows` and `p.tagTop/bodyTop(pane, r)` — PLACE absorbs those into the region rects |
| 5 | JOIN: paint tags/notices/headers once into layers, copy into grid; the grid wins where the copies disagree, and each disagreement is listed for a later decision | goldens are the oracle; wide-grapheme re-clip at the edge (§3.2) |
| 6 | body layer joined the same way (paint once, copy visible rows) | riskiest join; A/B the old double paint in a temporary test with tag_bottom on and off, then delete it |
-| 7 | Layer merge (TagLayer + BodyLayer), wire v8, web accessors | after the other agent lands; touches mouse hit paths; breaks the macOS shell's layer ABI (accepted: macOS build ignored for now) |
-| 8 | GUI draws from regions: role tiers with track groups in tiers 2-3, page cover, hard-edged snapped decor for rules/rails/grips (pane chrome in the pane's tier), per-instance clip; delete inference functions, `transient_on` and `mark_hover` (breaks macOS glass hover; accepted, macOS ignored for now) | stage-0 PPM goldens byte-identical (possible only because hard decor is snapped and not anti-aliased); pane chrome now also shows during transitions, which is the one allowed visible delta, listed |
-| 9 | post chain: glslang, Shadertoy prefix, ping-pong, ShaderAnimation, redraw levels A/B (§5.5); bundled CRT without barrel; delete scene_effects/crt.zig/crt.frag | first visual change (the CRT look) |
+| 7 | Layer merge (TagLayer + BodyLayer), wire v8, web accessors | after the other agent lands; touches mouse hit paths; breaks the macOS shell's layer ABI (accepted: macOS build ignored for now); done: one `Layer` (src/Layer.zig) with `rows` (0 = no layer) and a `cursor{x, y}`; a tag of N rows is ONE layer of N grid rows (`tagHit` answers the row as `line`, `bodyHit` keeps its meaning), so the per-line layer bases are gone. Wire v8 ships rows, cursor y and the region list in the one bump; v7 and v9 peers are refused in both directions (tests). web: `tag_layer_value` 11 = rows, 12 = cursor y, and app.mjs lays every row. macOS: its Zig side compiles against `Layer`, but pardes.h still sees one row per tag layer (a taller tag shows its first row there) |
+| 8 | GUI draws from regions: role tiers with track groups in tiers 2-3, page cover, hard-edged snapped decor for rules/rails/grips (pane chrome in the pane's tier), per-instance clip; delete inference functions, `transient_on` and `mark_hover` (breaks macOS glass hover; accepted, macOS ignored for now) | stage-0 PPM goldens byte-identical (possible only because hard decor is snapped and not anti-aliased); pane chrome now also shows during transitions, which is the one allowed visible delta, listed Done: the frame is drawn in groups (makeGroups): tier 0, one group per track in paint order (tiers 2 and 3), tier 4 as two groups (the notices; then the guides and the debug box, which the core paints over them), tier 5 (bar cursors), each drawing cells, images, decor. Decor is every rule, rail, thumb, grip mark, spine, the workspace and column rules, the bottom band, notice rules and bar cursors: whole-pixel rects drawn by `decor.frag` over `ui.vert` as cell instances (colour in the ground, coverage in fg.r, blended like the overlay), so each carries its track's transition and clip; a closing pane's comes from the last frame's regions (`Surface.previous_regions`). Only `solid` exists yet: `decor.vert` and the other kinds (§5.1) come with the first effect that needs them. Deleted: taglineBaseRgb, topbarPaneBorderHeight, bottomTaglinePresent, frameChromeBg, cellBackgroundIs and the rail inference, paneGripCell's scan, transient_on, PaintPlan; one cover map (coverFrame: layer, grip and offset, focus, anchor, floating) is marked from layers and regions once a frame. New regions: `column` (spines, anchors, the focused column's tint), `guide`, `debug`; `Surface.chrome` is the palette, on the wire in v8 (not yet shipped, so no bump), and an attached GUI draws the same chrome (test). Per-instance clip (`CellInstance.clip_*`, 104 → 120 bytes an instance, ~15% more upload a frame) replaces the vertical transition's scissor. While any pane moves, opens or closes, notices stay in their own panes' groups, under whatever slides over them; tier 4 holds them only when nothing moves. Goldens: 01-12 byte-identical; 13-17 differ only in pixels past the grid (with a picture on screen the image pass left the scissor at the grid's size, cutting every rule end, rail foot and band that runs into the leftover pixels; they now run to the edge as in every scene without one); 16-debug shows the debug box (it was drawn from the grid under the source's context-row layer, so the GUI never showed it there); 18-mid-transition is new (virtual clock, PanelSlide Newcol with the picture, frame 6 of 12: chrome moves with its pane). Other deltas, outside the goldens: a guide over a tag or a context-row body now shows; with WindowOpacity < 100 a layer's bar cursor is ink like the grid's; an attached GUI gains the focus tint, notice rules, spines and the theme's page and caret colours. Deferrals fixed: the GUI sleeps when idle (SDL and queue events wake it; a smooth scroll, a gamepad, the test feed, a shell's kill deadline, a present to retry still poll), and a minimized or occluded window sleeps through animation. Tracy, `gui frame build`, 200x60, terminal output plus scrolling, ReleaseFast, two interleaved runs of ~180 frames: before 992/1015 µs median, after 989/987 µs |
+| 9 | post chain: glslang, Shadertoy prefix, ping-pong, ShaderAnimation, redraw levels A/B (§5.5); bundled CRT without barrel; delete scene_effects/crt.zig/crt.frag | first visual change (the CRT look) Done: src/gui/Post.zig, shaders/post/ (prefix, crt, ripple, glitch), post.vert. glslc (decision 3) with compileGlsl's flags, off the frame path; ghostty's Uniforms matched offset for offset by a test against a copy, and the prefix's block checked name by name; ghostty's test_shadertoy_crt and _focus compile, _invalid fails with glslc's words. Level A measured (Tracy, 200x60, Crt, idle 10 s): 215 chain-only redraws at 34 µs median CPU each, 6 core frames (957 µs) in the same time; the core's clock stays idle. Input is identity (test: the corner click with Crt on). The three bundled passes were rewritten: Crt without barrel or tube edge, scanlines and mask that average to one, dithered vignette; Ripple as rings in pixels, eased in, lit on their slopes in linear light, dithered; Glitch as short eased bursts of torn bands with an RGB split, keyed to iTime. Before/after stills for the user's judgement. |
| 10+ | `fx` bookmark: G1–G3 → feel review → G4 bundled → G5 lapis theme → P2s; tty T1–T2 → feel review → removals of audited effects (after user decision) → T3–T5 | each effect its own change, default off |
Tests to add: fixed-clock animation tests (Tween/Spring closed form, retarget
@@ -842,6 +852,26 @@ goldens; ssh/tty byte budgets; wire version bump breaks mixed-version attach.
4. The macOS shell breaks at stages 7 and 8 (Layer ABI, mark_hover). Accepted,
since the macOS build is ignored for now.
5. Stage-0 GPU goldens gate locally only.
+6. Revisit at the first visual stage (from stage 5): the notice layer lost
+ its hover word to keep the grid's behaviour. Notice words are Look/Exec
+ targets, so bring the hover affordance back in BOTH the grid and the
+ layer then. Done right after stage 8: a notice's word under the pointer
+ is lit as a header's is, in the layer and so in the grid's copy joined
+ from it. The same change deleted `mark_hover`, the `Cell.hover` bit and
+ the per-frame reset of it (never set since stage 5; macOS loses its
+ glass hover rect, accepted with open point 4).
+7. Revisit at stage 8 (from stage 6): a body without context rows has no
+ layer (paint once, straight onto the grid). If the GUI is to read every
+ body from a layer, give every body one then and measure the copy. The
+ context-row path also still builds the body's text twice (a pre-pass
+ with body_rows 0 decides the context rows, renderBody builds it again at
+ the layer's rows); the paint is single. Measure both there. Decided at
+ stage 8, measured (Tracy, 200x60, TreeContext on a scrolled source,
+ ReleaseFast): the pre-pass text build is 5 µs median and the layer's
+ copies 10 µs per context-row body per frame. Kept as they are: nothing in
+ the GUI reads a layerless body from anything but the grid, which is
+ exact, so a layer for every body would be ~10 µs a body a frame for no
+ pixel.
## 15. Questions for the user
@@ -873,3 +903,4 @@ goldens; ssh/tty byte budgets; wire version bump breaks mixed-version attach.
5. The animation pacing fix goes into the no-visual-change phase.
6. Cursor blink is on by default.
7. The bundled CRT has no barrel distortion, so clicks stay exact.
+8. (After trying them live) Ripple and Glitch are removed entirely; Crt stays.