Skip to content

fix(viewer): rotate pi3-64 video by dropping the vc4 overlay when rotated - #3189

Merged
vpetersson merged 4 commits into
masterfrom
fix/pi3-64-video-rotation-overlay
Jul 15, 2026
Merged

fix(viewer): rotate pi3-64 video by dropping the vc4 overlay when rotated#3189
vpetersson merged 4 commits into
masterfrom
fix/pi3-64-video-rotation-overlay

Conversation

@vpetersson

Copy link
Copy Markdown
Contributor

Problem

On the 64-bit Raspberry Pi 3 (pi3-64, VideoCore IV, eglfs), video plays but stays locked in landscape when the screen is set to a portrait screen_rotation — while images, web pages and the UI all rotate correctly. Reported on the forum: https://forums.screenly.io/t/on-raspberry-pi-3-b-with-anthias-2026-07-1-it-seems-that-the-videos-dont-work/6730

Root cause

pi3-64 can't present video through the QML VideoOutput RHI path on the vc4 GL driver, so it scans the decoded frame out on a dedicated vc4 DRM overlay plane (kmssink), composited by the display controller. That plane is independent of the eglfs QOpenGLCompositor, so QT_QPA_EGLFS_ROTATION — which rotates the GL scene (UI / images / web pages) — never touches it. The vc4 planes also advertise only rotate-0 / rotate-180, so the overlay can't be turned 90°/270° in hardware at all.

Fix

Take the hardware overlay path only when the screen is upright. When rotated, drop ANTHIAS_VIDEO_OVERLAY so VideoView falls back to the CPU-raster blit, whose widget composites through the eglfs-rotated backing store and therefore inherits the rotation like images/web pages do.

  • Upright: unchanged — full-rate 30 fps HW overlay.
  • Rotated: video rotates correctly, on the raster path (lower fps — the right trade for a rotated screen, and the only option since the hardware plane can't do 90°/270°).

Also corrects two stale comments that assumed every Qt6 board rotates video uniformly at the platform layer (they predated the overlay path).

Validation

Unit tests cover the env selection (upright → overlay, rotated → raster, pi3-64 only). Verified end-to-end on a real 64-bit Pi 3:

  • Unfixed, screen_rotation=90: two active DRM planes — the eglfs-rotated UI plane plus a landscape video overlay plane (bug reproduced).
  • With the fix, screen_rotation=90: only the composited UI plane remains; the video is folded into it and rotates with the UI. The viewer process shows ANTHIAS_VIDEO_RASTER=1, QT_QPA_EGLFS_ROTATION=90, and no ANTHIAS_VIDEO_OVERLAY.

🤖 Generated with Claude Code

…ated

On pi3-64 (VideoCore IV / eglfs) video is scanned out on a dedicated vc4
DRM overlay plane (kmssink, #3164) that the QOpenGLCompositor never
touches, so QT_QPA_EGLFS_ROTATION rotates the UI/images/webpages but
leaves the video landscape when the screen is set to portrait (reported
on the forum: video locked horizontal regardless of rotation). The vc4
planes advertise only rotate-0 / rotate-180, so the overlay can't be
turned 90/270 in hardware at all.

Take the HW overlay path only when the screen is upright; when rotated,
drop ANTHIAS_VIDEO_OVERLAY so VideoView falls back to the CPU-raster
blit, whose widget composites through the eglfs-rotated backing store
and so inherits the transform like images/webpages do. Upright keeps the
full-rate HW overlay (30fps); rotated trades frame rate for correct
orientation, the right call for a rotated screen. Also corrects the two
stale "every Qt6 board rotates uniformly" comments that predated the
overlay path.

Verified on a real 64-bit Pi 3: unfixed rotation=90 leaves two active
DRM planes (eglfs-rotated UI + a landscape video overlay); with the fix
only the composited UI plane remains and the video rotates with it.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_017BbxH9mN3JPpKKF3VmkeCf
@vpetersson
vpetersson requested a review from a team as a code owner July 14, 2026 19:18
@vpetersson
vpetersson requested a review from Copilot July 14, 2026 19:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adjusts AnthiasViewer’s environment setup so pi3-64 video correctly follows screen_rotation on eglfs by disabling the vc4 DRM overlay path when rotated, forcing the raster/composited fallback that inherits QT_QPA_EGLFS_ROTATION.

Changes:

  • Update _build_webview_env() to enable ANTHIAS_VIDEO_OVERLAY only when screen_rotation == 0 on pi3-64, and actively drop any stale overlay flag when rotated.
  • Add unit tests covering upright vs. rotated overlay selection for pi3-64.
  • Update rotation-related commentary in media_player.py and __init__.py to reflect the pi3-64 overlay exception.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
tests/test_viewer.py Adds assertions for pi3-64 env selection: upright keeps overlay, rotated drops overlay while preserving eglfs rotation.
src/anthias_viewer/media_player.py Updates rotation rationale comments around per-video rotation behavior.
src/anthias_viewer/init.py Implements the overlay drop-on-rotation behavior for pi3-64 and expands explanatory docs/comments.

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

Comment thread src/anthias_viewer/media_player.py
The vc4 overlay plane can only rotate 0°/180° in hardware (measured: it
advertises rotate-0 and rotate-180, no 90/270), so 90°/270° still fall to
the eglfs-composited raster path — but 180° is a free scanout-time
transform. VideoView::gstPlayOverlay now sets the kmssink plane's
rotation=180 property when QT_QPA_EGLFS_ROTATION=180, and _build_webview_env
keeps ANTHIAS_VIDEO_OVERLAY for 0°/180° (dropping it only for 90°/270°).

Measured on a real 64-bit Pi 3 (1080p30 H.264): 180° now holds the full
30 fps at ~7% CPU with no thermal cost, versus ~9 fps and thermal
throttling on the raster path. 90°/270° remain raster-only (a VideoCore IV
hardware limit — no 90/270 plane rotation exists anywhere on the SoC).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_017BbxH9mN3JPpKKF3VmkeCf
@vpetersson

Copy link
Copy Markdown
Contributor Author

Hardware-validated on a real 64-bit Pi 3, with the trade-offs measured

Extended the fix after measuring frame drops on the raster path. Full picture (1080p30 H.264, 30s steady-state window each):

Screen rotation Path taken Presented fps Dropped CPU / thermal
HW overlay (kms-overlay) 30.0 0% idle, cool
180° HW overlay + vc4 plane rotation=180 29.9 0% ~7% CPU, 70°C flat
90° / 270° eglfs-composited CPU raster ~9.5 ~68% CPU-bound, thermally throttles

Why 90/270 can't be smooth (hardware limit): VideoCore IV has no 90/270 video rotation anywhere — the vc4 DRM planes advertise only rotate-0/rotate-180 (measured), the ISP can't rotate 90/270, and the GPU VideoOutput path is black on this chip (#3084). Software rotation is the only option there, and it's CPU-bound: the raster toImage path does ~9.5fps, and a GStreamer videoflip does ~15fps when cool but collapses to single digits as the passively-cooled Pi 3 thermally throttles. So 90/270 rotated video is degraded-but-correct; smooth portrait video on a Pi 3 needs a Pi 4/5.

Why 180° is now full-rate: a DRM plane rotate-180 is a free scanout-time transform (zero per-frame CPU). VideoView::gstPlayOverlay sets the kmssink plane's rotation=4 when QT_QPA_EGLFS_ROTATION=180, so 180° keeps the 30fps HW overlay instead of falling to raster.

Validation evidence (on the board): at 180°, sink=kms-overlay plane-id=98, the video plane's rotation property reads value: 4 (rotate-180), 29.9 fps / 0 drops, 70°C flat. At 90°, the overlay plane is gone (1 active plane) and it correctly uses the raster fallback.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread src/anthias_webview/src/videoview.cpp
@vpetersson

Copy link
Copy Markdown
Contributor Author

Note: the GPU / zero-copy path was measured and is slower than raster on VC-IV — don't re-litigate

The obvious "just rotate on the GPU zero-copy like mpv/Kodi" idea was tested on the board (GStreamer GL vout, v4l2h264dec ! glupload ! glcolorconvert ! glvideoflip ! glimagesink, 1080p, max rate):

Path (1080p) fps Temp
GPU zero-copy + rotate 6.6 cool
GPU zero-copy, no rotate (baseline) 6.7 cool
CPU raster (this PR's 90/270 fallback) ~9.5 hot
DRM overlay scanout (0/180) 30 cool

The GL path imports the decoder dmabuf fine and rotates for free (6.6 vs 6.7 — identical), and stays cool (GPU-bound, not CPU). But VideoCore IV's V3D can't push 1080p GLES video above ~6.7 fps even with no rotationbelow the CPU raster path. So zero-copy GPU rotation would be a regression for 90/270, not a fix. This is the same wall as #3084: the only thing on this SoC that presents 1080p30 is the display controller's scanout (the DRM overlay plane), which rotates 0/180 only. Smooth 90/270 portrait video needs Pi 4 (V3D 4.2) / Pi 5. Hence: overlay (+ HW plane rotate for 180) for 0/180, raster for 90/270.

…tant

- _build_video_options docstring: drop the stale ``video-rotate`` bullet
  (the dict is now audio-device only; rotation is handled at the platform
  layer) per Copilot.
- videoview.cpp: use DRM_MODE_ROTATE_180 instead of the bare literal 4 in
  the kmssink plane-properties rotation, per Copilot.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_017BbxH9mN3JPpKKF3VmkeCf

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread src/anthias_viewer/__init__.py Outdated
…avior

Copilot: the docstring still said the pi3-64 overlay is used "only when
upright"; it's actually kept for 0°/180° (plane HW-rotate for 180°) and
dropped only for 90°/270°.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_017BbxH9mN3JPpKKF3VmkeCf
@sonarqubecloud

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

@vpetersson
vpetersson merged commit 6f50f27 into master Jul 15, 2026
10 checks passed
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