summaryrefslogtreecommitdiff
path: root/docs/rendering-parity-design.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/rendering-parity-design.md')
-rw-r--r--docs/rendering-parity-design.md132
1 files changed, 132 insertions, 0 deletions
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.