Skip to content

refactor(viewer): consolidate Qt6 boards onto cage + Wayland#2883

Closed
vpetersson wants to merge 1 commit into
worktree-jiggly-sauteeing-stonebrakerfrom
chore/wayland-consolidate-qt6
Closed

refactor(viewer): consolidate Qt6 boards onto cage + Wayland#2883
vpetersson wants to merge 1 commit into
worktree-jiggly-sauteeing-stonebrakerfrom
chore/wayland-consolidate-qt6

Conversation

@vpetersson
Copy link
Copy Markdown
Contributor

Note: Stacked on top of #2879 (the generic-arm64 PR), so the diff here is the single consolidation commit. Base is worktree-jiggly-sauteeing-stonebraker; rebase onto master once #2879 lands.

Description

Pi4-64, Pi5, x86, and arm64 now all run the viewer under cage (a wlroots kiosk compositor) with Qt on QT_QPA_PLATFORM=wayland and mpv on --vo=gpu --gpu-context=wayland. Previously only x86 and (per #2879) generic arm64 took this path; Pi4-64 and Pi5 ran Qt linuxfb + mpv --vo=drm directly on KMS. Folding all four onto one display stack drops a per-board branch each in image_builder, Dockerfile.viewer, start_viewer.sh, and media_player.py.

Notable per-file changes:

  • tools/image_builder/utils.pycage + qt6-wayland move out of the per-board branch into the shared is_qt6 block (now in every Qt6 image, not just x86/arm64). va-driver-all stays x86-only (no VAAPI on ARM SoCs — Pi uses v4l2-request/m2m, Rockchip/Allwinner/Amlogic use V4L2 M2M too).
  • docker/Dockerfile.viewer.j2ENV QT_QPA_PLATFORM=wayland gated on is_qt6 instead of board in ('x86', 'arm64').
  • bin/start_viewer.shcase "$DEVICE_TYPE" in x86|arm64|pi4-64|pi5) now wraps the viewer in cage on all four boards. The render-GID mirror that previously only mattered on x86 (VAAPI / /dev/dri/renderD128) also applies on Pi — the GL context (V3D render node) needs the same access for --vo=gpu --gpu-context=wayland.
  • src/anthias_viewer/media_player.py--vo=gpu --gpu-context=wayland for every Qt6 board; the previous --drm-mode=1920x1080@60 pin is dropped on Pi4-64/Pi5 (no-op under cage anyway, and GPU scaling handles 4K without the A72/A76 CPU zimg upscale that the pin was working around). --vd-lavc-threads=4 stays.
  • tests/test_media_player.py — assertions updated; new parametrised test covers all four Qt6 device types getting --vo=gpu --gpu-context=wayland.
  • website/data/faq.yaml — two entries that claimed "no Wayland compositor in the stack" are corrected.

Validation

On-device frame-drop comparison on a Pi4 Model B (Debian Trixie, DEVICE_TYPE=pi4-64), 30 s of 1080p H.264 over mpv --hwdec=auto-safe from inside the viewer container:

VO flags Asset 1 drops/30s Asset 2 drops/30s
--vo=drm --drm-mode=1920x1080@60 (current) 59–75 19–20
--vo=gpu --gpu-context=drm (no cage) 3–6 3

--vo=gpu --gpu-context=wayland under cage measured separately on the rebuilt viewer image (results in the PR comments). decoder-frame-drop-count was 0 in every run — the drops are all on the VO side, which is exactly the path the move from CPU zimg upscale to GPU scaling improves.

Checklist

  • I have performed a self-review of my own code.
  • New and existing unit tests pass locally and on CI with my changes.
  • I have done an end-to-end test for Raspberry Pi devices.
  • I have tested my changes for x86 devices.
  • I added a documentation for the changes I have made (when necessary).

Pi4-64, Pi5, x86, and arm64 now all run the viewer under `cage`
(a wlroots kiosk compositor) with mpv on --vo=gpu
--gpu-context=wayland and Qt on QT_QPA_PLATFORM=wayland. Previously
only x86 and (per the in-flight arm64 PR) generic arm64 took this
path; Pi4-64 and Pi5 ran Qt linuxfb + mpv --vo=drm directly on KMS.
Folding them all onto one display stack drops a per-board branch
each in image_builder, Dockerfile.viewer, start_viewer.sh, and
media_player.

--drm-mode=1920x1080@60 is dropped on Pi4-64/Pi5: under cage mpv
doesn't hold DRM master so the flag is a no-op, and the GPU does
the scaling that the CPU zimg upscale at 4K previously couldn't
keep up with. --vd-lavc-threads=4 stays.

cage + qt6-wayland move from the per-board apt extension into the
shared is_qt6 branch in image_builder. va-driver-all stays
x86-only (no VAAPI on ARM). The render-GID mirror in
start_viewer.sh now applies to all four Qt6 boards because the GL
context on Pi also needs /dev/dri/renderD128 access.

FAQ entries that claimed "no Wayland compositor in the stack" are
updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vpetersson vpetersson requested a review from a team as a code owner May 12, 2026 21:08
@vpetersson vpetersson self-assigned this May 12, 2026
@vpetersson vpetersson requested a review from Copilot May 12, 2026 21:08
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR consolidates all Qt6-capable device types (pi4-64, pi5, x86, arm64) onto a single viewer display stack: running the viewer under cage with Qt on QT_QPA_PLATFORM=wayland, and mpv on --vo=gpu --gpu-context=wayland. This removes prior per-board branching (notably the Pi KMS --vo=drm path) and aligns runtime/build configuration, tests, and docs accordingly.

Changes:

  • Move cage + qt6-wayland into the shared Qt6 viewer image dependencies and gate QT_QPA_PLATFORM=wayland on is_qt6.
  • Wrap the viewer in cage for all Qt6 boards in start_viewer.sh, including render-node group mirroring for GPU access.
  • Update mpv invocation defaults and unit tests to assert Wayland GL VO usage across all Qt6 device types; adjust FAQ entries to reflect the Wayland compositor in the stack.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
website/data/faq.yaml Updates FAQ wording to reflect the new cage/Wayland-based viewer stack.
tools/image_builder/utils.py Consolidates Qt6 viewer apt deps to include cage and qt6-wayland across boards.
docker/Dockerfile.viewer.j2 Gates QT_QPA_PLATFORM=wayland on is_qt6 for all Qt6 images.
bin/start_viewer.sh Runs viewer under cage for all Qt6 boards and mirrors host render-node GID.
src/anthias_viewer/media_player.py Switches mpv VO to Wayland GL (--vo=gpu --gpu-context=wayland) and adjusts Pi tuning.
tests/test_media_player.py Updates/extends assertions to cover Wayland VO on all Qt6 device types.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +187 to +191
# straight on KMS/DRM, with mpv on --vo=drm. Qt6 boards (pi4-64,
# pi5, x86, arm64) run the viewer under `cage` (a kiosk wlroots
# compositor) with QT_QPA_PLATFORM=wayland and mpv on --vo=gpu
# --gpu-context=wayland — no X code path on either track. The
# cage + qt6-wayland pair is added to the Qt6 apt extension below.
# Covers x86 (balenaOS doesn't expose /dev/fb0), arm64 Armbian
# boards (Rock Pi / Orange Pi / Banana Pi, …), and the Pi4-64 / Pi5
# (consolidated onto Wayland so all Qt6 boards share one display
# stack — see #TBD).
Comment thread website/data/faq.yaml
- question: How do I rotate the screen for portrait orientation?
answer: |
Rotation happens at the kernel / firmware level. The Anthias viewer renders straight to the Linux framebuffer (Qt `linuxfb`) and to KMS (mpv `--vo=drm`) — there's no Wayland compositor in the stack — so the standard Raspberry Pi config knobs apply directly.
Rotation happens at the kernel / firmware level on the Pi. The viewer renders through `cage` (a kiosk Wayland compositor that talks straight to KMS) on Pi4/Pi5/x86/arm64, and through Qt `linuxfb` directly on legacy 32-bit Pi boards (Pi2/Pi3) — neither path goes through a desktop compositor, so the standard Raspberry Pi config knobs apply directly.
@vpetersson
Copy link
Copy Markdown
Contributor Author

On-device validation blocked the Pi4-64/Pi5 consolidation

Tested the rebuilt viewer image on a real Pi4 Model B (Debian Trixie, 4K display via HDMI). Same 1080p H.264 asset, same 30-s window, frame drops via mpv \${frame-drop-count}:

Path mpv flags Drops / 30s
Current production --vo=drm --drm-mode=1920x1080@60 --vd-lavc-threads=4 --hwdec=auto-safe 59–75
GPU output, no cage --vo=gpu --gpu-context=drm --hwdec=auto-safe 3–6
This PR (cage + Wayland) --vo=gpu --gpu-context=wayland --vd-lavc-threads=4 --hwdec=auto-safe 738+, slow-motion playback

decoder-frame-drop-count was 0 in every run — the regression is on the VO side.

Why this regresses on Pi

  1. The Pi V3D can't composite 4K in real time on top of a separate Wayland surface. cage selects the connector's preferred mode (4K on this TV) and mpv renders into a Wayland surface that cage composites and scans out. The current --drm-mode=1920x1080@60 pin sidesteps this by running the connector at 1080p; under cage that flag is a no-op (cage holds DRM master).
  2. mpv 0.40 in Debian Trixie has no v4l2request hwdec — only v4l2m2m-copy, which falls back to software decode under cage in this image (hwdec=no confirmed via \${hwdec-current}). No DMA-BUF zero-copy path is available.
  3. --vo=dmabuf-wayland is not viable — it would zero-copy from decoder to wayland surface, but it has no hwdec to receive from (see point 2), and the existing code comment notes it segfaults under cage on x86 anyway.

Options

  • (A) Drop Pi4-64/Pi5 from this PR. Keep x86+arm64 on cage+Wayland (already there from feat(install): generic-arm64 best-effort support (Armbian SBCs) #2879). Effectively close this PR.
  • (B) Force a 1080p output mode on Pi via kernel cmdline (video=HDMI-A-1:1920x1080@60 in /boot/firmware/cmdline.txt), so cage runs at 1080p and the V3D has the same upscale headroom it had with --drm-mode. Requires an Ansible/install change to ship.
  • (C) Wait for newer mpv (v4l2request hwdec + drm-prime interop) and revisit.

Leaning toward (A) — the consolidation goal hinged on Pi being able to keep up, and on this hardware + mpv combo it can't. Marking the PR draft pending direction.

Side-finding worth keeping

Even without cage, just switching the Pi --vo=drm--vo=gpu --gpu-context=drm (offload scaling from CPU zimg to V3D) cuts drops from 59–75 → 3–6 on this Pi4. That's a separate small win worth landing on its own if we don't ship the full consolidation.

(Tested on a 4K-connected Pi4-64 Rev 1.5; the Pi5 has roughly 2× the V3D throughput so it might just squeak through, but I'd want the same test there before assuming.)

@vpetersson vpetersson marked this pull request as draft May 12, 2026 22:35
vpetersson added a commit that referenced this pull request May 13, 2026
…4 to 1080p

Folds in PR #2883: Pi 4-64 / Pi 5 now run under cage with mpv on
--vo=gpu --gpu-context=wayland, joining x86 and arm64 on a single
Wayland-based display stack. Drops the --vo=drm legacy path
entirely from MPVMediaPlayer. Qt 5 boards (pi2 / pi3) stay on
linuxfb via VLCMediaPlayer — out of scope here.

Replaces the perf branch's `--vo=gpu --gpu-context=drm` standalone
fix with the consolidated cage path. The previous standalone
finding (3-6 vo drops / 30 s on Pi 4 at 4K) was a Pi-without-cage
optimization; once Pi runs under cage like every other Qt6 board,
the same trick applies via wayland but cage's composite step adds
its own pass and the V3D on Pi 4 can't keep up at 4K (738 vo
drops / 30 s measured at native 4K under cage). Fix: move the
1080p mode pin one layer up from app code to host config — the
new ansible/.../cmdline.txt.j2 conditional appends
`video=HDMI-A-1:1920x1080@60 video=HDMI-A-2:1920x1080@60` when
`device_type == 'pi4-64'`. With output pinned to 1080p there's no
upscale anywhere in the pipeline, matching the bandwidth profile
of today's --vo=drm production setup.

Pi 5 / x86 / arm64 keep the connector's preferred mode (typically
4K). Pi 5's V3D 7.1 has roughly 2× Pi 4's throughput; x86 iGPUs
handle 4K via VAAPI; arm64 SBC perf varies by SoC.

Other notable changes folded in from #2883:

* tools/image_builder/utils.py — `cage` + `qt6-wayland` move out
  of the per-board branch into the shared is_qt6 block.
  `wlr-randr` (was x86-only) goes in the shared block too since
  rotation now happens via wlr-randr on every Qt6 board.
  `va-driver-all` stays x86-only (no VAAPI on Pi / ARM SoCs).
* docker/Dockerfile.viewer.j2 — QT_QPA_PLATFORM=wayland gated on
  is_qt6 instead of board in ('x86', 'arm64').
* bin/start_viewer.sh — case on DEVICE_TYPE: every Qt6 board
  takes the cage + sudo path. Pi2 / Pi3 stay on the legacy
  direct-sudo path.
* src/anthias_viewer/media_player.py — single --vo=gpu
  --gpu-context=wayland for all reachable device types. The
  per-board rotate_args block is gone: every Qt6 device inherits
  the transform from cage via wlr-randr, so mpv would
  double-rotate if it set --video-rotate.
* tests/test_media_player.py — parametrised tests for all four
  Qt6 boards (x86, arm64, pi4-64, pi5) hitting the same VO path;
  rotation tests assert mpv *never* sets --video-rotate under
  cage.
* website/data/faq.yaml — rotation entry points at Settings page
  / wlr-randr; resolution entry calls out the Pi 4 1080p pin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vpetersson
Copy link
Copy Markdown
Contributor Author

Closing. Folded into #2885, which now consolidates Pi 5 / x86 / arm64 onto cage + Wayland (Pi 4 stays on linuxfb — on-device testing showed the V3D 6.0 can't keep up with cage's composite pass on top of mpv, even with a 1080p HDMI mode pin).

The base of this PR (worktree-jiggly-sauteeing-stonebraker) merged to master as #2879, so the cage path is already live for x86 / arm64. #2885 picks up Pi 5 (and a Pi 4 perf improvement on the linuxfb path) on top of it.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants