diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-16 13:20:29 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 7cbd44dd1cfe97126e7fc996214e458525d2980c (patch) | |
| tree | eeb02eabaf39231dece68647129040e546aa80a2 /docs | |
| parent | 3042f33df2b16b61c08d6535fba268103728dd1e (diff) | |
| download | pardes-7cbd44dd1cfe97126e7fc996214e458525d2980c.tar.gz pardes-7cbd44dd1cfe97126e7fc996214e458525d2980c.zip | |
Align macOS rendering with Linux and establish parity regressions
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/macos.md | 58 | ||||
| -rw-r--r-- | docs/rendering-parity-design.md | 132 |
2 files changed, 164 insertions, 26 deletions
diff --git a/docs/macos.md b/docs/macos.md index 3c02de8a..3fdf399f 100644 --- a/docs/macos.md +++ b/docs/macos.md @@ -14,6 +14,10 @@ plumbing actually requires, and which parts of it are distribution machinery rather than integration. Read that for the alternatives; this file is what was built and why. +Future layout/compositing ownership and the regression baseline are discussed in +[Shared rendering contract](rendering-parity-design.md). That proposal is design +groundwork; the native drawing implementation remains in place. + ## Why not ghostty's split Ghostty was read carefully before this was written, and this backend @@ -496,33 +500,22 @@ different kinds of assertion because a snapshot is the core's cell buffer and the core has no font: `font Menlo-Regular` asks the view what it is actually wearing, and the snapshots catch the grid moving when the cell changes size. -### The tagline band follows the core's rule, not this shell's +### Compact tags and context rows -A pane tag is drawn at `gui_tagline_font_percent` of the body face -(`TaglineSize <percent>` changes it live) and the band it sits on shrinks with -it, while the grid row stays body-sized. Where that shorter band sits inside its -row is no longer this shell's arithmetic: `pardes_tagline_band_offset` and -`pardes_topbar_pane_border_px` answer out of `src/pardes.zig`, and that is the -same rule `src/gui/gui.zig` draws with. +`TaglineSize <percent>` scales the tag face, pitch and glyph band. The tag's +background still fills a whole body-grid row, as in the SDL renderer. Painting +only the compact band leaves dark strips between tags. Glyph offsets come from +`pardes_tagline_band_offset`; all measurements cross the ABI in physical pixels. -It is shared because it drifted. This shell centred every band in its own row, -and centring two reduced-height bands is precisely the case -`config.gui_topbar_pane_border_px` exists to prevent: the topbar's unused -half-band meets the first pane tag's unused half-band and the window background -shows through the seam. The strip is as wide as the bands are short — on a -20-pixel cell, 4 physical pixels at the default 82%, 10 at 50%, 14 at 30% — so it -read as "the tagline is wrong on the mac" rather than as one missing rule. Row -zero is bottom-aligned now, the first pane-tag row top-aligned, the two joined by -`gui_topbar_pane_border_px` in the theme's scrollbar-track colour -(`pardes_topbar_pane_border_rgb`, or a compiled override), every row between -centred, and a `Tagbottom` band on the final row bottom-aligned against the -window edge — with the sub-cell strip below it painted in that band's own colour, -because the core grid holds only whole cells and a window is any height it likes. +The chrome overlay runs after the compact layers. It draws the topbar rule, +the column rule and the pane's tag/body boundary using the theme's border +colour. The pane rule moves above a bottom-positioned tag. Tag-layer fields 11 +and 12 preserve that placement and colour in frozen transition frames. -The offsets cross as PHYSICAL PIXELS. The host multiplies its points by the -backing scale going in and divides coming out, which is the snapping `Metrics` -already does for the cell, and is what keeps a one-pixel rule one pixel instead -of a two-pixel smear. +Tree-sitter context rows use the compact height and pitch, with full-width row +backgrounds. Their one-physical-pixel separators use the same border colour as +SDL, after discontinuous declarations and after the final context row. The +remaining body starts immediately after the compact rows. ### The cell is snapped to device pixels, not to points @@ -713,6 +706,20 @@ The blur is a **sibling** of the grid inside a plain container, never its parent. Hiding a superview hides its subviews, so a nested backdrop drew a blank window for every opaque theme the moment it was hidden. +`WindowOpacity <0..100>` sets one background coverage throughout the frame. +Background fills replace existing coverage, so overlapping ground, cell, tag +and context layers cannot increase the requested opacity. The window itself +stays clear below 100%; a second tinted window backdrop would compound it. +Text and cursor ink stay opaque. PDF and image pixels follow the SDL image +pipeline: remove the destination by source coverage, then add the image at the +requested opacity. This also preserves the background under transparent image +pixels. Theme colours and attachment rasters are interpreted as sRGB. + +`test/macos-snapshots/rendering-parity.snap` checks background colour and alpha, +regular and bottom tags, compact context separators, and PDF fit, tint and +scrolling. Its native-metrics mode uses backing pixels like the shipping app; +the older grid-only tests keep their display-independent point metrics. + ## Threading One core, touched only from the main thread, plus one pty reader task per pane @@ -955,8 +962,7 @@ select scripts or directories instead. The executable stays in the build cache. `test/macos_e2e.swift` links the same Swift sources the app does, minus `main.swift`, into a second binary — test scaffolding does not ship inside the -product. Scripts are `test/macos-snapshots/*.snap` (seven of them: boot, cwd, -drop, font, keys, rotate, trackpad) and speak the tty suite's +product. Scripts are `test/macos-snapshots/*.snap` and speak the tty suite's vocabulary (`start`, `wait`, `stable`, `text`, `key`, `snap`, `command`, `mouse`, `click`, `wheel`, `resize`, `draw`) plus what only exists here: `fingers <n> <col> <row>`, `force <col> <row>`, diff --git a/docs/rendering-parity-design.md b/docs/rendering-parity-design.md new file mode 100644 index 00000000..b6036543 --- /dev/null +++ b/docs/rendering-parity-design.md @@ -0,0 +1,132 @@ +# Shared rendering contract: design groundwork + +Status: proposed direction, not an implemented renderer abstraction. The current +change fixes the native macOS backend and adds regression coverage. It does not +move layout or drawing into a new shared module. + +## Why the backends diverged + +The core shares editor state, cells, tag layers, body layers, and PDF placements. +It does not yet specify every visual operation needed to present them. SDL and +AppKit therefore independently reconstruct tag backgrounds, compact text bands, +context separators, overlay order, and opacity. A correct cell snapshot can +coexist with an incorrect rendered frame. + +The macOS/Linux comparison exposed concrete gaps in this contract: + +- Reduced tag glyph bands were also used as background bounds on macOS; SDL + fills the entire row. Column and pane rules were missing or painted before + an overlapping layer. +- Tree-sitter context rows had matching compact geometry, but the separator + used the scroll-track color instead of the border color. +- Repeated source-over background fills compounded window opacity. At 71%, + two overlapping fills yield about 92%, and three about 98%. The window's own + colored background added another layer behind the view. +- CoreText fallback runs did not inherit the selected foreground color. +- PDF/image composition ignored window opacity on macOS, while SDL applies it + to raster content. Theme colors also need an explicit sRGB interpretation. + +These are duplicated policy decisions, rather than a reason to replace CoreText +or require identical fonts on every host. + +## Proposed ownership boundary + +A shared scene builder should translate the existing surface and its layers +into an ordered list of positioned drawing operations. Platform code should +consume that list without deciding which rows are compact, which edge has a +separator, or which objects participate in window opacity. + +The builder would own: + +- Tag and context geometry, background extents, separator positions and colors, + clipping, and the order of backgrounds, text, images, cursors, and overlays. +- Coordinate conversion rules: logical layout units, supplied font metrics, + device scale, and the rounding policy for one-device-pixel rules. +- Color and compositing semantics, including clear backgrounds, replacement + background fills, image coverage, selection, and opaque native text/cursors. +- Stable image references and a defined frame lifetime, including snapshots + retained for transitions and postprocessing. + +Backends would continue to own native font selection, shaping/rasterization, +image upload/cache management, and presentation. Native event handling, window +management, and the text-only terminal frontend are outside this proposal. + +A small 2D operation vocabulary could include solid rectangles, positioned text +runs, images with source/destination rectangles, and explicit clips. Operations +must carry semantic blend behavior and a documented color space. Wrapping +`fillRect` and `drawText` while leaving each backend to build the scene would +preserve most of the duplication that caused these bugs. + +Do not freeze a new ABI before extracting one small path and checking its needs. +Font metrics must enter the shared layout explicitly; changing fonts can still +change line capacity and glyph appearance. Pixel equality across different +fonts, rasterizers, and display scales is not the acceptance criterion. + +## Compositing contract to preserve + +Let p be WindowOpacity in [0, 1]. An ordinary background region has alpha p, +regardless of how many logical layers cover it. Clear background regions remain +clear. Native text and cursors retain their own opacity. + +For an image sample with coverage a and straight RGB c, the existing SDL +background-layer behavior produces premultiplied output: + +``` +out.rgb = p * a * c + (1 - a) * dst.rgb +out.a = p * a + (1 - a) * dst.a +``` + +Thus PDF text is part of a raster page and fades with the page; native text does +not. Transparent image pixels preserve the destination. Ordinary source-over +with source alpha p*a is not equivalent. Any future operation API needs to +express this distinction directly and specify sampling and premultiplication. + +## Tests available before extraction + +`test/macos-snapshots/tag-ink.snap` checks actual rendered tag foreground pixels, +covering failures that a cell-grid golden cannot detect. + +`test/macos-snapshots/rendering-parity.snap` uses native backing-scale metrics +and checks rendered tag backgrounds and rules, top/bottom tags, opacity 100/71/0, +invalid opacity rejection, and nested multiline Tree-sitter context separators. +It also checks PDF placement and sampled page alpha at those opacity values, +height fit, tint modes, and scrolling. `context.zig` is the nested context fixture; +`docs/9p.pdf` is the existing PDF fixture. Optional PNG output is enabled with +`PARDES_TEST_CAPTURE_DIR`. + +The Zig test named `mac tag layer ABI preserves logical capacity and physical +grip` covers tag-bottom and border-color metadata as well as existing capacity +and grip behavior. The existing Linux `test/window_opacity.py` checks background +changes, native glyph preservation, zero opacity, invalid input, and restoration. + +Run on macOS: + +```sh +zig build unit-test -Dplatform=macos '-Dtest-filter=mac tag layer ABI' +zig build macos-e2e -Dplatform=macos +zig build -Dplatform=macos +``` + +These are baseline regressions, not a comprehensive cross-backend image oracle. +PDF alpha samples do not establish exact raster color or interpolation equality; +offscreen AppKit captures do not test the desktop compositor. Live comparison +still matters for window transparency and display color management. + +## Suggested later extraction and acceptance gates + +1. Add shared geometry/scene assertions for tags and context rows using supplied + metrics at several scales. Include clipping, partial rows, bottom tags, and + final separator order; expected values should come from the contract rather + than a copy of either backend's implementation. +2. Extract that scene construction once and adapt both native renderers. Keep + current backend pixel checks running without regenerating goldens merely to + accept differences. Compare matching fixtures and settings on both systems. +3. Add synthetic RGBA image coverage cases before extracting image composition: + opaque, transparent, and partial-alpha pixels over a known destination at + p=0, intermediate p, and p=1. Include fit, crop, and clip boundaries. +4. Extend the contract to the remaining overlays and retained transition frames. + Measure frame time and allocations before broadening the abstraction. + +The desired result is one definition of visual policy with small native drawing +adapters. A new GPU engine, shared font rasterizer, or wholesale backend rewrite +is not required to reach that boundary. |
