Repository navigation
fix(render): keep each display's backdrop on the zone that places it - #440
Merged
Merged
Conversation
A display whose zone has no face layers shows its slice of the scene canvas as its backdrop, and the scene canvas composed every output in zone order. A later zone's LED outputs that overlapped a display's box painted that zone's effect onto the physical display, so a custom zone's ripple layer showed up on LCDs the primary zone places. It hid while every device sat in one zone, because every overlap painted the same content. Projection now composes in two strata over the base: every LED output in zone order, then every display output in zone order. Display outputs are the ones is_led_sampled_zone rejects, the test the LED sampler and device routing already use. The GPU projected path inserts LED layers at a boundary index inside the admitted layer scratch, so frames stay allocation free and GPU resident. The CPU reconstruction runs the same two passes, and its full-scene identity copy runs in the stratum of the one output it stands in for. The single full-scene zone path needs no ordering, since one zone places every output. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Spec 86 §5.1 carried over Spec 64's view that zone-order overwrite in the scene canvas is a harmless detail of a preview image. That holds for LED outputs only. A display's box in the scene canvas is that display's backdrop, so it reaches hardware, and a later zone's LED output overlapping it painted the wrong zone onto the display. The section now states the two-stratum order (LED outputs first, then display outputs, each in zone order), the predicate that identifies a display output, why the box has to show the zone that places the display, and how the CPU reconstruction and the single full-scene zone path line up with it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An LCD with no face layers shows its slice of the scene canvas as its backdrop, and in a multi-zone scene a later zone could paint over that slice. On the owner's rig, putting the Ableton Push 2 in its own zone with a Touch Ripple layer made pad ripples show up partially on the Lian Li fan LCDs and the Corsair pump LCD. With this PR, a display's box in the scene canvas always shows the zone that places that display, whatever LED outputs from other zones overlap it.
💡 What was happening
The scene canvas is a projection: each scene zone contributes one layer per output in its layout, copying the zone's texture into that output's placed box over an opaque black base, in zone order (
append_projection_composition_layers_for_zoneinrender_zones/projection.rs). The primary zone places the case's LED devices and the display outputs. Custom LED zones come later. Wherever a later zone's LED output overlapped a display's box in the layout, the later zone won, and the display showed the other zone's content. Spec 86 §5.1 called overlapping outputs "a harmless detail of a preview image". That stops being true once display backdrops read the scene canvas: those pixels land on hardware.The overlap is ordinary. A desk device like the Push 2 has no physical relationship to the case, so its layout boxes sit wherever there is room, and on the owner's layout that is on top of the LCDs. It stayed invisible while every device lived in one zone, because every overlap painted the same content.
🛠️ What changes
The scene canvas now composes in two strata over the black base: every LED output first, in zone order, then every display output, also in zone order. Display outputs are identified with the predicate the LED sampler and device routing already use (
is_led_sampled_zone, false for theDisplaysegment), cached per output as aProjectionStratumwhen projection metadata is built, so the cache already rebuilds when a layout changes.Every path that builds the scene canvas follows the same order:
render_pass.rs,projection.rs)display_layer_startboundary kept onProjectedSceneFrames; display layers append after it. The capacity check runs before any layer lands, so steady frames still allocate nothing and stay GPU resident.compose_authoritative_scene_canvas)LED-versus-LED overlaps keep zone order, which only ever reaches preview images. Spec 86 §5.1 now states the two-stratum rule and why.
🧪 Validation
Six new tests in
render_zones/tests/projection.rsuse an 8x8 scene: a red zone places a display in the top-left quadrant and an LED output in the bottom-right, and a later blue zone places one LED output across the centre that overlaps both. They check that the display box stays red where blue overlaps it, blue shows outside the display box, the later zone wins LED-versus-LED overlap, and uncovered pixels stay black. They cover the CPU path without metadata, a fullrender_sceneon the CPU compositor, the identity fast path, layer ordering, a fullrender_sceneon the GPU that asserts a GPU-resident frame, and two overlapping displays keeping zone order. With the classifier forced back to the old single stratum, five of them fail (display box at (2, 2) ... left: [0, 0, 255] right: [255, 0, 0]); the display-versus-display test passes either way, as intended.cargo fmt --all -- --check-p hypercolor-daemon --all-targets -D warnings,--no-default-features --lib,--no-default-features --features wgpu,persistence-test-hooks --all-targetscargo test -p hypercolor-daemon --lib -- --test-threads=1 render_zoneswithHYPERCOLOR_REQUIRE_GPU_TESTS=1persistence-test-hooks, serial--test render_thread_testsdisplay_output_tests,canvas_capture_tests,display_preview_testsAn independent verifier passed it. Its own probes showed the cached CPU path, the no-metadata fallback, and CPU-composed appended layers are byte-identical on a rotated three-zone scene, a four-frame GPU
render_scenematches the CPU reference pixel for pixel with flat allocation counters, and a refused append leaves the layers and the boundary untouched. The extraVec::insertshifting costs about 1 µs per frame in a worst case of 8 displays ahead of 80 LED outputs.On hardware: the owner's rig runs this change on top of #438, with the Push 2 and ROLI touch zones over a primary zone that places three fan LCDs, the pump LCD, and the iCUE LINK LCD.
Display. Every HAL display driver and the simulator use that name, and the UI copies the segment name into layouts, so this needs a hand-edited or legacy layout to miss.🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Documentation