Skip to content

Multiview tile labels and click targets misalign after a source is unassigned #508

Description

@iamfatness

Owner report, 2026-09-12 (live):

The clickable areas did not adjust so now names are aligned wrong in the bottom row and the clickable areas are half on one source and half on another

Seen right after Sources -> Unassign on a mimoLive input, i.e. while the source
count changed 10 -> 9 and the multiview grid reflowed.

NOT ROOT-CAUSED. Ruled out so far, each by reading the code on main @ b3810925:

  • The core's two geometry functions agree exactly. buildMultiviewRenderPlan
    (what it draws) and buildMultiviewTiles (what it publishes for labels and
    hit-testing) compute identical totalSources/sourceCount, call the same
    computeMultiviewLayout and the same centeredAspectRect, and place from the
    same multiviewSources_[index] loop. They are also called in the SAME tick.
  • The structural-emit signature already includes the rects (and slot, label,
    role, tally) — MediaCore::enqueueMultiviewSharedTextureEvent — so a reflow
    cannot be silently skipped.
  • The event payload carries per-tile rects (multiviewSharedTextureJson).
  • MultiviewOverlayFormatting.SelectOverlayTiles is a pass-through — it caps
    SOURCE tiles at MaxShowInputs and never recomputes geometry, and both the
    click overlay and the decoration overlay build from that same list, which is
    why labels and hit areas are wrong TOGETHER.
  • The shell's refresh throttle is leading+trailing (RsbMinIntervalMs), so a
    structural change cannot be dropped.
  • The letterbox transform is constant — PositionOverlay scales by the
    multiview 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-sync refill, and every source-set change
re-ranks the whole subscription budget (totalChurn 77, every camera at churn
4-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's
published set for the same frame. Do not "fix" this by whitelisting or
recomputing geometry shell-side; the two sides agreeing is the invariant.

Activity

  1. iamfatness commented on Sep 13, 2026

    @iamfatness
    OwnerAuthor

    Live session 2026-09-13, real meeting, 9 multiview sources (multiviewer.sourceCount: 9, tileCount: 10, mode pgmPvwTop). 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) for pgmPvwTop:

    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) and buildMultiviewTiles (:3760) compute sourceCount with identical expressions and both call the same computeMultiviewLayout. No duplicated grid math.
    • The shell does not recompute the geometry. ShowMultiviewHost.TileRects binds StudioViewModel.MultiviewTileRects ← VideoSurfaceCoordinator._multiviewTileRects ← multiview.Tiles published 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 requires multiviewStructureEmitted_ && 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/Y and _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/Y and the displayed size. Neither reaches /snapshot (per-layer geometry is deliberately off the wire; only the tiles node carries rects), so this needs either a temporary log line in OnTileRectsChanged or a test that drives ShowMultiviewHost with 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.

  2. iamfatness commented on Sep 13, 2026

    @iamfatness
    OwnerAuthor

    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) and buildMultiviewTiles (overlay/click rects + labels) both compute sourceCount the same way, iterate multiviewSources_[index] in the same order, and map layout.sourceCells[index] through the same compositor::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 /snapshot multiviewer + 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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    backlogRanked in docs/BACKLOG.md

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions