Repository navigation
Multiview tile labels and click targets misalign after a source is unassigned #508
Description
Activity
Live session 2026-09-13, real meeting, 9 multiview sources (
multiviewer.sourceCount: 9,tileCount: 10, modepgmPvwTop). I did not find the culprit, but I did find the fingerprint, and it explains the two most confusing parts of the report — why only the BOTTOM row, and why the offset is half a source.Why the offset is exactly half a tile, and only in the bottom row
compositor::computeMultiviewLayout(native/src/compositor/CompositorLayout.h:189-209) forpgmPvwTop:rows = count <= 4 ? 1 : 2 cols = (count + rows - 1) / rows tileW = 1 / cols rowLeft = (1 - tileW * inThisRow) / 2 // "Center a partial last row rather than left-hanging it."At count = 9:
rows=2,cols=5,tileW=0.2. Row 0 holds 5 tiles →rowLeft = 0. Row 1 holds 4 →rowLeft = (1 - 0.8)/2 = 0.1= exactly half a tile.So any disagreement between the composited image and the overlay about the source count — or about whether the last row is centred at all — is invisible in row 0 (identical under both readings) and shows up in the last row as a half-tile horizontal shift. That is precisely "names are aligned wrong in the bottom row and the clickable areas are half on one source and half on another", and it is why the top row looked fine.
It also explains "the clickable areas did not adjust": going from 10 sources to 9 (the unassign in the original report) leaves row 0 byte-identical and moves ONLY the bottom row, by that half tile.
What I ruled OUT by reading the code
- The two core paths do not disagree.
buildMultiviewRenderPlan(MediaCore.cpp:3597) andbuildMultiviewTiles(:3760) computesourceCountwith identical expressions and both call the samecomputeMultiviewLayout. No duplicated grid math. - The shell does not recompute the geometry.
ShowMultiviewHost.TileRectsbindsStudioViewModel.MultiviewTileRects←VideoSurfaceCoordinator._multiviewTileRects←multiview.Tilespublished by the core. Labels and hit-testing (HitTestSourceTile,:588) both read that same list, which is why they are wrong together. - Stale published rects — my first hypothesis — is not supported.
enqueueMultiviewSharedTextureEvent(:1243-1266) mixes every tile's x/y/w/h at 1e-4 precision into the signature, and the skip requiresmultiviewStructureEmitted_ && signature == last. A changed rect always re-emits, even though a source-set change does not clear the flag.
The remaining candidate, untested
Since labels and clicks share one rect list that matches the composited layout, the surviving suspect is the normalized → screen mapping in
ShowMultiviewHost:_overlayOffsetX/Yand_overlayDisplayedWidth/Height. The multiview texture is 16:9 and the panel may not be, so the video is letterboxed inside the overlay; if that mapping is computed for the wrong box or not refreshed on a layout/resize pass, every tile shifts. Against that hypothesis: a mapping error should offset ALL rows, not just the last — so either the reporter only noticed it where the tiles are narrow, or the mapping is fine and something else re-orders the bottom row specifically.What would settle it: the overlay event payload (the core's published
multiview.Tiles) captured at the moment the miscount is on screen, next to_overlayOffsetX/Yand the displayed size. Neither reaches/snapshot(per-layer geometry is deliberately off the wire; only thetilesnode carries rects), so this needs either a temporary log line inOnTileRectsChangedor a test that drivesShowMultiviewHostwith a 10→9 source change and asserts the bottom-row hit rect.Reproduce it cheaply: any source count whose last row is partial — 9 sources (5+4) is the clearest, 6 (3+3) has no partial row and should be immune. If 6 sources is clean and 9 is broken, the count/centering disagreement is confirmed and the letterbox mapping is exonerated.
- The two core paths do not disagree.
Update 2026-09-13 — likely a one-tick transient, not a persistent bug
Owner: "haven't seen the clickable-area mismatch again after that one time — it could be fixed."
This fits the code. The two core paths that produce the multiview geometry are provably identical:
MediaCore::buildMultiviewRenderPlan(composited video) andbuildMultiviewTiles(overlay/click rects + labels) both computesourceCountthe same way, iteratemultiviewSources_[index]in the same order, and maplayout.sourceCells[index]through the samecompositor::centeredAspectRect. There is no mapping or ordering discrepancy between the picture and the clickable rects.
So a mismatch can only be a transient stale overlay for a single structural tick right after a source-set change (the unassign): the composited texture re-lays-out immediately, and the shell's published tile rects catch up on the next structural emit. The report "the clickable areas did not adjust" describes exactly that one-tick lag, which self-heals — consistent with it not recurring.
Disposition
Downgraded from a persistent defect to a watch item. Not blind-fixing (no reproducible persistent root cause). If it recurs and persists (rects stay wrong for more than a moment), that would be a real overlay-staleness bug in
VideoSurfaceCoordinator's multiview signature/emit path — capture/snapshotmultiviewer+ the overlay rects at that moment and reopen with it. Cheap repro to keep in mind: a source count with a partial last row (9 = 5+4 shifts the bottom row half a tile; 6 = 3+3 has no partial row).
Owner report, 2026-09-12 (live):
Seen right after
Sources -> Unassignon a mimoLive input, i.e. while the sourcecount changed 10 -> 9 and the multiview grid reflowed.
NOT ROOT-CAUSED. Ruled out so far, each by reading the code on
main @ b3810925:buildMultiviewRenderPlan(what it draws) and
buildMultiviewTiles(what it publishes for labels andhit-testing) compute identical
totalSources/sourceCount, call the samecomputeMultiviewLayoutand the samecenteredAspectRect, and place from thesame
multiviewSources_[index]loop. They are also called in the SAME tick.role, tally) —
MediaCore::enqueueMultiviewSharedTextureEvent— so a reflowcannot be silently skipped.
multiviewSharedTextureJson).MultiviewOverlayFormatting.SelectOverlayTilesis a pass-through — it capsSOURCE tiles at
MaxShowInputsand never recomputes geometry, and both theclick overlay and the decoration overlay build from that same list, which is
why labels and hit areas are wrong TOGETHER.
RsbMinIntervalMs), so astructural change cannot be dropped.
PositionOverlayscales by themultiview canvas size, which does not change with source count.
Likely-relevant context found while chasing it: during that window the slot
was being written by a phantom
roster-syncrefill, and every source-set changere-ranks the whole subscription budget (
totalChurn77, every camera at churn4-12). So the reflow was happening repeatedly, not once. The refill is fixed
separately; whether the misalignment is a transient during reflow or a stuck
state is UNKNOWN.
Next step: instrument the shell's tile-rect apply — log the incoming tile
rects against the texture identity at
VideoSurfaceCoordinator.ApplyMultiview...and at
ShowMultiviewHost.OnTileRectsChanged— and compare with the core'spublished set for the same frame. Do not "fix" this by whitelisting or
recomputing geometry shell-side; the two sides agreeing is the invariant.