chromium-fourier r2 + firefox-fourier 150.0.1 + KWIN_PIVOT.md
build and publish packages / distcc-avahi-aarch64 (push) Successful in 46s
build and publish packages / lmcp-any (push) Successful in 9s
build and publish packages / lmcp-debian (push) Successful in 4s
build and publish packages / claude-his-any (push) Successful in 7s
build and publish packages / ffmpeg-v4l2-request-aarch64 (push) Successful in 12m8s
build and publish packages / claude-his-debian (push) Successful in 5s

chromium-fourier:
- patch 3/3 nv12-external-oes-on-modifier-external-only.patch — adds
  NativePixmapEGLBinding::ModifierRequiresExternalOES helper, extends
  OzoneImageGLTexturesHolder::GetBinding to honor EGL external_only
  flag for NV12 dmabufs on panfrost / panthor. Validated on ohm
  (RK3566 hantro mainline 6.19.10): bbb_1080p30_h264.mp4 plays at
  34.7 % combined CPU vs ~131 % pre-patch baseline (~3.8x).
- PKGBUILD pkgrel 1->2, source array + sha256sums + prepare() hook for
  patch 4, patch numbering 1/2,2/2 -> 1/3,2/3,3/3.
- NEXT.md appended with 2026-04-28 section: patch 4 design, validation
  log, KWin GL_ALPHA bug pinpoint (preexisting since 2026-03-06,
  affects every wayland video client; unrelated to chromium-fourier),
  device-renumbering note (/dev/video1 = encoder post-reboot).
- KWIN_PIVOT.md: 4-phase plan to identify and patch KWin's
  glTexImage2D(internalFormat=GL_ALPHA) site, ohm-only test plan,
  scope discipline.
- patches/ now tracked (compiler-rt-adjust-paths, enable-v4l2,
  wayland-allow-direct-egl-gles2, nv12-external-oes); the dead-end
  chromeos-pipeline-bypass.patch removed.

firefox-fourier:
- 4 patches (gfxinfo v4l2 stateless fourccs, libwrapper hwdevice ctx,
  ffmpegvideo v4l2-request route, prefs v4l2-request default).
- PKGBUILD bumped to firefox 150.0.1, Arch toolchain glue patches
  layered in, mozconfig with --without-wasm-sandboxed-libraries for
  ALARM, package() launcher fix (rm -f symlink before cat > to avoid
  ENOENT through the dangling /usr/local symlink mach install drops).
- 150.0.1-1-aarch64.pkg.tar.zst built on boltzmann (95 MB), pending
  fresnel power-on for V4L2 stateless validation on RK3399.
This commit is contained in:
2026-04-28 12:02:18 +00:00
parent 7bb2fbeca9
commit 8756ce38be
15 changed files with 1711 additions and 60 deletions
+181
View File
@@ -0,0 +1,181 @@
# KWin pivot — fix the `glTexImage2D(GL_ALPHA)` stall
## What we know
KWin 6.6.4-1 on Arch Linux ARM (Plasma 6.6.4-1, mesa 26.0.5-1, libdrm
2.4.131-1) on ohm (PineTab2 / RK3566 / panfrost) silently corrupts its
GL command queue mid-frame whenever a wayland client posts a video
buffer. The journal carries a rolling stream of:
```
kwin_wayland: 0x4: GL_INVALID_VALUE in glTexImage2D(internalFormat=GL_ALPHA)
kwin_wayland: 0x4: GL_INVALID_OPERATION in glTexSubImage2D(invalid texture level 0) × N
```
`GL_ALPHA` is not a valid `internalFormat` for `glTexImage2D` under
**OpenGL ES 3.x** (it was the GLES1.x single-channel alpha format;
GLES3 deprecates it for sized formats — `GL_R8`, `GL_LUMINANCE8_ALPHA8`,
etc.). Once the texture allocation fails, the `glTexSubImage2D` calls
that should populate it all error at level 0. KWin keeps retrying the
same broken upload every frame, never recovers, and the present-callback
path that depends on that texture stops acking client frames. Every
wayland video client deadlocks on the missing ack.
First occurrence in this box's journal: **2026-03-06** — the bug
predates any chromium-fourier work by roughly seven weeks.
## Triangulation already in hand
| Client | Outcome |
|---|---|
| chromium-fourier 149-r2 (with patch 3/3) | plays ~3 s @ 34.7 % CPU then renderer/GPU park in `futex_do_wait` |
| chromium-fourier 149-r2 (without patch 3/3) | plays ~10 s (slower path delays surfacing) then identical deadlock |
| VLC | `cannot convert decoder/filter output to any format supported by the output``could not initialize video chain` |
| mpv `--vo=null --hwdec=v4l2request` | `Could not create device.` (mpv-side bug, separate, unrelated) |
| ffmpeg `-hwaccel v4l2request -i bbb -f null -` | plays through clean at 36 fps; hardware path is healthy |
Decode path is healthy on this hardware. The wall is exclusively the
compositor's GL backend.
## Constraint: ohm is the only test box on hand
ampere (RK3588 / panthor) is in the boxes-from-Shenzhen pile, currently
DOWN. fresnel (RK3399 / Pinebook Pro) is offline. boltzmann (Rock 5
ITX+ build host) doesn't run KWin. We do every step on ohm; we accept
the wifi flakiness and the occasional reboot.
## Phase 1 — Reproduce outside chrome and bound the trigger (1 evening)
Goal: a deterministic, headless-or-near-headless reproduction that
doesn't require launching a 800-MB browser.
1. **Smallest-possible client.** Build a 50-line C wayland client that
creates a `wp_linux_dmabuf_v1` buffer, pumps frames at 30 fps, and
exits when KWin first errors. Use `weston-simple-dmabuf-egl` from
the `weston` package as a starting template — already does exactly
this but without our specific format/modifier matrix.
2. **Vary the format/modifier matrix.** Run the smallest-possible
client with each of: NV12 + LINEAR, NV12 + AFBC, NV12 + AFRC,
AR24 + LINEAR, XR24 + LINEAR. We already know NV12 paths trigger;
confirming AR24/XR24 do *not* trigger localizes the bug to KWin's
YUV import path (vs a generic dmabuf import bug).
3. **Vary the buffer dimensions.** Some KWin texture-cache paths
allocate fixed-size internal scratch textures; non-power-of-two,
non-multiple-of-16, or specifically odd-aspect cases sometimes
trigger paths that healthy aspect ratios skip. Test 1920×1080,
1280×720, 854×480, 640×360 and a deliberately weird 1366×768.
4. **Vary KWin scene type.** Switch
`kwin_wayland --scene-type=opengl` vs `--scene-type=opengl-es`
(current default on this hardware). If the bug only fires under
GLES, that's a strong signal — the offending site is in a
GLES-only fallback.
By the end of Phase 1 we should have a one-line `weston-simple-dmabuf-egl
-format=NV12 -modifier=…` that triggers the GL_ALPHA error within
seconds, plus a yes/no answer to "does AR24 also trigger".
## Phase 2 — Identify the call site (12 evenings)
The crime scene is somewhere in `kwin/src/scene/*` or
`kwin/src/effects/*`. Suspects, ranked:
- **`SurfaceItemWayland::createPixmapTexture``GLTexture::create`
with `GL_ALPHA`.** This is the most likely path: KWin allocates a
fallback per-plane texture when the dmabuf import path can't take
the buffer whole. NV12 has a Y plane (single-channel) and a CbCr
plane (two-channel); historically the Y plane has been allocated as
`GL_ALPHA` in software fallbacks. If the EGL dmabuf import returned
`EGL_BAD_ATTRIBUTE` for `external_only` modifiers and KWin fell
through to per-plane, this is exactly where it would land.
- **`BlurEffect::initBlurTexture` / `BackgroundContrastEffect::*`.**
Single-channel noise textures for blur dither. Less likely (these
fire on every frame regardless of video clients) but listed for
completeness.
- **Window-decoration text glyph cache.** Qt's QGLTexture historically
requested `GL_ALPHA` for monochrome glyph atlases. Plasma 6 should
have moved to `GL_RED` long ago, but a stale code path in a
third-party theme or systray icon could still hit it.
- **Cursor texture upload via `wl_shm_pool` + ARGB8888.** KWin's
cursor scene sometimes uploads via glTexImage2D — but the format
there is `GL_RGBA`, not `GL_ALPHA`. Probably not the suspect.
Tooling to identify *which*:
1. **`apitrace trace --api egl kwin_wayland …`** then
`apitrace dump trace.trace | grep -B5 GL_ALPHA`. Apitrace gives
us the C++ call stack at the offending site if KWin was built with
debug symbols.
2. **`MESA_GL_DEBUG=context KWIN_GL_DEBUG=1 kwin_wayland --replace`**
plus `glDebugMessageCallback` already installed in KWin's
`OpenGLBackend` will print the source/type/severity for each
`GL_INVALID_VALUE`. Whether the file/line in the message includes
the user-space caller depends on Mesa's debug-extension support;
on panfrost it usually does include the GL function name and an
ID, but not the C++ source — that is what apitrace adds.
3. **Build kwin from source** (`extra/kwin` PKGBUILD on Arch ARM,
patch in `-DDEBUG=ON`, `-DCMAKE_BUILD_TYPE=Debug`) so the call
stacks resolve to file:line.
## Phase 3 — Write the patch (½ evening once Phase 2 is done)
If the offender is a `GL_ALPHA` allocation in a GLES3 context, the
fix is mechanical:
```diff
- glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0,
- GL_ALPHA, GL_UNSIGNED_BYTE, data);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0,
+ GL_RED, GL_UNSIGNED_BYTE, data);
```
…and adjust the consuming shader's swizzle:
```diff
- gl_FragColor = vec4(texture2D(s, uv).a, …);
+ gl_FragColor = vec4(texture2D(s, uv).r, …);
```
If the offender is a per-plane fallback in the dmabuf import path
(suspect #1 above), the patch is larger because the right fix is to
*not fall through to the broken path* — handle the `external_only`
case by binding `GL_TEXTURE_EXTERNAL_OES` instead. That mirrors the
chromium-fourier patch 3/3 done at the chromium layer; symmetry says
KWin should do the same in its `glTexImage` consumer.
## Phase 4 — Ship and upstream (1 evening)
1. **Local Arch package** as `kwin-fourier` under
`marfrit-packages/arch/kwin-fourier/`, sibling to chromium-fourier
and firefox-fourier. PKGBUILD inherits from `extra/kwin`, drops
in our patch, bumps `pkgrel`. Same `provides=kwin conflicts=kwin`
pattern.
2. **Validate on ohm** by running the chromium-fourier 149-r2 build +
the bbb sample for a minute uninterrupted. Success = no GL_ALPHA
in the journal, no stall, smooth playback at the 34.7 % CPU
number from the chromium validation.
3. **Upstream** via:
- File a `kwin` bug on bugs.kde.org with: apitrace fragment, our
hardware (Mali-G52 panfrost on RK3566 mainline), exact mesa
version, repro steps via `weston-simple-dmabuf-egl` if Phase 1
produced one.
- Push an MR to invent.kde.org/plasma/kwin against `master`.
4. **Document** the fix in `chromium-fourier/docs/dmabuf-zero-copy.md`
so the next person who lands on the same wall finds the breadcrumb
trail.
## What success looks like
`chromium-fourier-149-r2` on ohm under KWin Wayland plays
`bbb_1080p30_h264.mp4` end-to-end at the 34.7 % CPU figure already
recorded by the architectural validation, with zero `GL_INVALID_VALUE`
in the journal during playback. That number is the goal of the entire
chromium-fourier campaign for RK3566 — it is currently blocked on a
bug that has nothing to do with chromium.
## Scope discipline
We do not turn this into "audit the entire KWin GLES backend." If
Phase 2 surfaces additional latent GL_INVALID_* errors that don't
matter for video playback, we note them in the bug report and move
on. The pivot is explicitly "remove this single wall so the
chromium-fourier patch series can ship a working stack on RK3566."
+278
View File
@@ -148,3 +148,281 @@ to install chromium's bundled clang (x86_64 host, arm64 sysroot), then
The boltzmann `chromium-builder` LXD container is preserved as fallback The boltzmann `chromium-builder` LXD container is preserved as fallback
but no longer the active build host. If cross-compile pans out, that but no longer the active build host. If cross-compile pans out, that
container can be torn down. container can be torn down.
## First runtime validation on ohm — 2026-04-26 22:26 UTC
Cross-compile produced a working aarch64 binary (chrome 647 MB ELF +
chrome_crashpad_handler 4.3 MB + .pak + locales). Tarball
`chromium-fourier-147.tar.gz` (226 MB) transferred CT 220 → hertz → ohm.
Launched in mfritsche's KWin Wayland session (tty2, panfrost render
node) playing `bbb_1080p30_h264.mp4` from file:// with
`LIBVA_DRIVER_NAME=v4l2_request`,
`LIBVA_V4L2_REQUEST_VIDEO_PATH=/dev/video0`,
`--use-gl=egl --ozone-platform=wayland
--enable-features=VaapiVideoDecodeLinuxGL,AcceleratedVideoDecodeLinuxGL
--disable-features=UseChromeOSDirectVideoDecoder
--autoplay-policy=no-user-gesture-required`.
**Result: V4L2 path NOT engaged.** Chrome 147 routes the H.264 stream
through `MojoVideoDecoderService``media/filters/ffmpeg_video_decoder.cc`
(software FFmpeg). Renderer pegs at ~92 % CPU, `/dev/video0` is never
opened (`fuser` returns empty), no `V4L2VideoDecoder` /
`VaapiVideoDecoder` log lines appear at `--v=1
--vmodule="*/vaapi/*=2,*/v4l2/*=2,*video_decoder*=2,*media/gpu/*=2"`.
Compositor also fell back to software (`Switching to software
compositing.` even though panfrost render node was picked) — secondary
issue, separate from the codec wall.
**Conclusion**: 7Ji-style gn args (`use_v4l2_codec=true
use_v4lplugin=true use_linux_v4l2_only=true`) alone are insufficient
on chromium 147. The V4L2VideoDecoder factory is still gated behind
`BUILDFLAG(IS_CHROMEOS)``media/mojo/services/gpu_mojo_media_client_*.cc`
and `media/gpu/gpu_video_decode_accelerator_factory.cc` only register
the V4L2 path on ChromeOS targets.
## Validation pass 2 — 2026-04-26 22:38 UTC — V4L2VDA proven engaged
Two distinct issues were diagnosed and the codec one was fully resolved
without source surgery beyond a 2-line patch:
### Issue 1 — runtime master gate
`media::kAcceleratedVideoDecodeLinux` (user-visible feature name
"AcceleratedVideoDecoder") is hard-coded in
`media/base/media_switches.cc:750` to `FEATURE_ENABLED_BY_DEFAULT` only
when `BUILDFLAG(USE_VAAPI)` is set. On a USE_V4L2_CODEC-only build it
defaults DISABLED, the linux gpu_mojo_media_client returns
`VideoDecoderType::kUnknown`, and chrome silently falls back to
`media/filters/ffmpeg_video_decoder.cc`.
**Fix**: 2-line patch (now `patches/enable-v4l2-decoder-default.patch`):
```
-#if BUILDFLAG(USE_VAAPI)
+#if BUILDFLAG(USE_VAAPI) || BUILDFLAG(USE_V4L2_CODEC)
```
The placeholder `chromeos-pipeline-bypass.patch` was deleted; PKGBUILD
now references the real patch. **Verified to apply cleanly on the CT 220
tree** (chromium 149 main).
### Issue 2 — bundled GL libs missing from tarball
The first runtime tarball shipped only `chrome` + `.pak` + locales +
`chrome_crashpad_handler`. It omitted `libEGL.so` / `libGLESv2.so`
(ANGLE) plus `libvk_swiftshader.so` and `libvulkan.so.1`. Without these,
the GPU process logs `gl::init::InitializeStaticGLBindingsOneOff failed`
and chrome falls into "Switching to software compositing." mode — which
*also* gates the V4L2 path off because the gpu_mojo_media_client never
gets a chance to dispatch.
Additionally, `--use-gl=egl` is rejected ("Requested GL implementation
gl=egl-gles2,angle=none not found in allowed implementations:
[(gl=egl-angle,angle=opengl|opengles|vulkan)]"): the build only allows
ANGLE-mediated paths. Right launcher invocation:
`--use-gl=angle --use-angle=gles`.
**Fix**: package the four libs alongside `chrome` and update the
launcher flag set. Both will be encoded in the next iteration of the
PKGBUILD's `package()` and a `chromium-fourier` launcher script.
### What we observed once both fixes were in place
With patch + bundled libs + `--enable-features=AcceleratedVideoDecoder`
+ `--use-gl=angle --use-angle=gles`, chrome on RK3566 hantro logs:
```
[gpu]: V4L2VideoDecoder()
[gpu]: Open(): No devices supporting H264 for type: 0 <- type=0 is single-planar; chrome retries multi-planar
[gpu]: InitializeBackend(): Using a stateless API for profile: h264 main and fourcc: S264
[gpu]: SetupInputFormat(): Input (OUTPUT queue) Fourcc: S264
[gpu]: AllocateInputBuffers(): Requesting: 17 OUTPUT buffers of type V4L2_MEMORY_MMAP
[gpu]: SetExtCtrlsInit(): Setting EXT_CTRLS for H264
[gpu]: SetupOutputFormat(): Output (CAPTURE queue) candidate: NV12
[gpu]: ContinueChangeResolution(): Requesting: 6 CAPTURE buffers of type V4L2_MEMORY_MMAP
[renderer]: OnDecoderSelected<video>: V4L2VideoDecoder
MediaEvent: "Selected V4L2VideoDecoder for video decoding,
config: codec: h264, profile: h264 main, [...]
coded size: [1920,1080], visible rect: [0,0,1920,1080]"
```
`fuser /dev/video1 /dev/media0` shows `chrome` (gpu pid) holding both
fds. The hantro stateless decoder is engaged. **First end-to-end
chromium-fourier V4L2 hardware decode validation: PASS** for H.264
1080p Big Buck Bunny on PineTab2.
Caveat: the render-side CPU was still ~85% during playback. Subsequent
investigation traced this to a different root cause than initially
guessed (see Pass 3 below).
## Validation pass 3 — 2026-04-26 22:50 UTC — zero-copy diagnosis
The 85 % CPU is **not** caused by software compositing or dmabuf v5
negotiation. The dmabuf-v5 warning ("Binding to zwp_linux_dmabuf_v1
version 4 but version 5 is available") is benign — chrome happily binds
to its supported max (v4). The `WaylandZwpLinuxDmabuf::OnTrancheFlags`
NOTIMPLEMENTED is also benign — KWin sends it, chrome ignores it, but
the substantive feedback (formats + modifiers) lands via
`OnTrancheFormats` / `OnTrancheTargetDevice` regardless.
Real cause: `gpu_feature_info.supports_nv12_gl_native_pixmap` ends up
**false** on this build. With it false, V4L2-decoded NV12 frames go
through the NV12-to-AR24 VPP conversion path (see
`media/mojo/services/gpu_mojo_media_client_linux.cc`
`GetPreferredRenderableFourccs` — without NV12 native pixmap support,
only `Fourcc::AR24` is added to the renderable set, forcing the VPP).
That's where the 85 % is spent.
Why is `supports_nv12_gl_native_pixmap` false?
`GLOzoneEGLWayland::CanImportNativePixmap` (in
`ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc`) requires the
chrome GL display to expose `EGL_EXT_image_dma_buf_import`. With
`--use-gl=angle --use-angle=gles`, chrome's GL display sits behind
ANGLE's EGL, and ANGLE's GLES backend on Linux does not propagate
`EGL_EXT_image_dma_buf_import` from the underlying mesa EGL up to its
clients. Verified directly: `EGL_PLATFORM=surfaceless eglinfo` on ohm
shows panfrost native EGL exposes both
`EGL_EXT_image_dma_buf_import` and `EGL_EXT_image_dma_buf_import_modifiers`
the capability is there at the panfrost layer, ANGLE just hides it.
We tried `--use-gl=egl` (direct EGL/GLES2, bypass ANGLE) but were
rejected with "Requested GL implementation (gl=egl-gles2,angle=none) not
found in allowed implementations". `WaylandSurfaceFactory::GetAllowedGLImplementations()`
in chromium 149 advertises only ANGLE-mediated impls; the
`kGLImplementationEGLGLES2` slot is missing from the list. The
`CreateViewGLSurface` dispatcher does still handle that impl — only the
*advertisement* was tightened.
### Patch 2/2 — `wayland-allow-direct-egl-gles2.patch`
3-line diff in `ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc`:
```
+ impls.emplace_back(gl::GLImplementationParts(gl::kGLImplementationEGLGLES2));
impls.emplace_back(gl::ANGLEImplementation::kOpenGL);
```
Re-allows the direct EGL/GLES2 path, ahead of the ANGLE entries so
chrome picks it by default. Verified to apply cleanly on the CT 220
tree; staged via `patch -p1` mid-rebuild (ninja's mtime-based rebuild
will pick up the change automatically).
### Outstanding for next pass (revised)
1. Rebuild lands → repackage with all four GL libs +
`chrome_crashpad_handler` + chrome → ship to ohm.
2. Validate via `chrome --use-gl=egl --ozone-platform=wayland`
`--enable-features=AcceleratedVideoDecoder` (no ANGLE shim) and
confirm `chrome://gpu` reports `Native GpuMemoryBuffers: true` and
`supports_nv12_gl_native_pixmap=true`. Target CPU during 1080p30 H.264
playback: under 30 % combined renderer + gpu.
3. If (2) passes, declare V1 of chromium-fourier shippable on ohm.
4. Add a `chromium-fourier` launcher shim under `/usr/bin/` that
defaults to `--use-gl=egl --ozone-platform=wayland`.
5. Sort the chromium 147 vs 149 confusion — the fetch went to ToT on
main rather than the 147 release branch. Either pin the branch or
accept that we're tracking ToT (probably preferable for V4L2 fixes
that are still in flight upstream).
6. Replicate end-to-end on RK3588 (ampere CoolPi or boltzmann Rock 5
ITX+) once the mainline VDPU381 driver is stable on those — those
boxes use **panthor** for Mali-G610 (Valhall), not panfrost; the
patches should be backend-agnostic but the validation is per-box.
### State of the build host (post pass 3)
- CT 220 `/build/chromium/src` patched with both
`enable-v4l2-decoder-default.patch` and
`wayland-allow-direct-egl-gles2.patch` (applied directly with
`patch -p1` mid-rebuild; ninja picks up the mtime change).
- `chromium-rebuild.service` running as a transient unit, output in
`/tmp/chromium-rebuild.log`. Most of the 93k ninja steps are cache
hits; only the patched files + their downstream objects need
recompiling.
- Tarball still on CT 220 at `/build/chromium-fourier-147.tar.gz`
(misleadingly named: it's actually 149.0.7812.0 from the main fetch,
not the 147 release tarball — separate cleanup for next pass) and on
hertz at `/tmp/chromium-fourier-147.tar.gz`. **Will be replaced by
the post-rebuild tarball once it lands.**
---
## 2026-04-28 — Patch 4 lands, KWin owns the residual stall
### Patch 4 — `nv12-external-oes-on-modifier-external-only.patch`
On panfrost / panthor, every NV12 modifier (LINEAR + AFBC ×2 + AFRC) is
flagged `external_only` in `eglQueryDmaBufModifiersEXT`. Chromium's
`OzoneImageGLTexturesHolder::GetBinding` only picked
`GL_TEXTURE_EXTERNAL_OES` when the SharedImageFormat carried
`PrefersExternalSampler` — which is set for the generic Linux multi-plane
case but **not** for V4L2 producers that arrive via the standard ozone
pixmap path. The frame then took the `GL_TEXTURE_2D` branch, ANGLE's
`validationES.cpp:4894` rejected the YUV EGLImage on a non-EXTERNAL_OES
target, the import failed, and the renderer fell back to the
NV12→AR24 software conversion (~131 % CPU baseline).
Patch closes the gap: also pick `EXTERNAL_OES` when the EGL driver
advertises the pixmap's modifier as `external_only` (cached per
`(fourcc, modifier)` tuple via a function-local
`base::flat_map`+`base::Lock`, so the EGL round-trip stays off the
per-frame hot path). Adds a single static helper
`NativePixmapEGLBinding::ModifierRequiresExternalOES`. ~+90 lines, zero
deletions, no shader changes (Skia Ganesh already handles
`GL_TEXTURE_EXTERNAL_OES` natively via `GrGLTextureInfo.fTarget`).
### Validation on ohm (RK3566 PineTab2 / hantro mainline 6.19.10)
- `bbb_1080p30_h264.mp4` plays clean, no garble, no decoder error
- Steady-state **34.7 % combined CPU** during 1080p30 H.264 (browser 12 +
GPU 9 + net 6 + render 6 + audio 1) vs v3 baseline ~131 % — **~3.8×
reduction**. Risk-1 (ANGLE+EXTERNAL_OES sampling regression on Skia
Ganesh / panfrost) **cleared**.
- `V4L2VideoDecoder()` constructor + `Using a stateless API for profile:
h264 main and fourcc: S264` confirmed in log; 6 CAPTURE buffers
V4L2_MEMORY_MMAP, NV12 output. 19 live dmabuf fds in GPU process
during steady playback — healthy V4L2 rotation + compositor depth, not
a leak.
### KWin 6.6.4 GL_ALPHA bug — separate, preexisting, blocks long playback
Across BOTH v3 (no patch 4) and v4 (with patch 4) chromium-fourier
builds, mid-playback the renderer + GPU process both park in
`futex_do_wait`, `<video>` element keeps its ⏸ icon, currentTime
advances on the audio clock, and audio outputs static (last ALSA buffer
recycled) then silence. No D-state, no v4l2/vb2/dma_fence wchan, no
error in `chrome-v[34].log`.
`journalctl` for `kwin_wayland`:
```
GL_INVALID_VALUE in glTexImage2D(internalFormat=GL_ALPHA)
GL_INVALID_OPERATION in glTexSubImage2D(invalid texture level 0) × N
```
First occurrence on this box: **2026-03-06**. KWin is requesting an
internal format that doesn't exist in modern GLES (`GL_ALPHA` is GLES1.x
legacy, not valid for `glTexImage2D` with GLES3 contexts). The
allocation fails, then every `glTexSubImage2D` to that texture errors at
level 0; KWin keeps retrying the same broken upload every frame, never
recovers. The frame-callback ack to wayland clients stalls → chrome's
renderer parks waiting for the present-feedback that never lands.
Patch 4 looks "guilty" only because of timing: with NV12 zero-copy, the
renderer is fast enough to actually post a v4l2-backed dmabuf within the
window where KWin's broken path runs; v3 was slow enough (NV12→AR24
software conversion) that the bug surfaced 510× later. **Triangulation:**
chrome v4 stalls + chrome v3 stalls + VLC `cannot convert decoder/filter
output` + mpv `could not initialize video chain` — every wayland video
client hits it; ffmpeg `-hwaccel v4l2request -f null` plays through
clean (decode path is healthy, the wall is the compositor's GL backend).
### Decoder-stack sanity post-reboot (2026-04-28 ~13:30)
After a reboot the V4L2 device numbering shuffled:
- `/dev/video0` = `rockchip,rk3568-vpu-dec` (hantro DEC, was video1)
- `/dev/video1` = `rockchip,rk3568-vepu-enc` (hantro ENC)
- `/dev/video2` = `rockchip-rga`
- `/dev/media0` = controller for DEC, `/dev/media1` = controller for ENC
Anything that hardcoded `/dev/video1` for decode now talks to the
encoder. Chrome and ffmpeg both handle this transparently (they enumerate
via media-ctl); mpv's `--hwdec=v4l2request` returns `Could not create
device` post-shuffle — separate mpv-side bug, not ours.
### Outstanding (revised, supersedes earlier list)
1. **Patch 4 lands publicly:** bump PKGBUILD `source=` and `prepare()`,
commit + tag a `chromium-fourier-149-r4` release on
`git@github.com:marfrit/chromium-fourier`.
2. **KWin pivot** — see `KWIN_PIVOT.md` (separate doc) for the plan to
identify and patch the `glTexImage2D(GL_ALPHA)` site, since ohm is the
only board on hand and every wayland video client is affected.
3. **Replication on ampere** (RK3588, panthor + rkvdec2 + hantro
multiplanar) — needs ampere woken; currently DOWN.
4. **firefox-fourier 150 build** — `firefox-fourier-150.0.1-1-aarch64.pkg.tar.zst`
is built (95 MB on workstation:/tmp/, sha256 acbf1870…), pending
fresnel power-on for V4L2 stateless validation on RK3399.
+137 -38
View File
@@ -4,19 +4,20 @@
# (RK3566 hantro / RK3588 VDPU381) on **mainline** kernel + Wayland + # (RK3566 hantro / RK3588 VDPU381) on **mainline** kernel + Wayland +
# panfrost / panthor — no vendor MPP, no Mali blob, no panfork, no # panfrost / panthor — no vendor MPP, no Mali blob, no panfork, no
# 5.10 BSP kernel. Fills the niche that 7Ji's chromium-mpp explicitly # 5.10 BSP kernel. Fills the niche that 7Ji's chromium-mpp explicitly
# does not (it forces BSP + X11 + vendor stack); see # does not (it forces BSP + X11 + vendor stack); see STUDY.md and
# /home/mfritsche/src/marfrit-packages/arch/chromium-fourier/STUDY.md # NEXT.md alongside this PKGBUILD for the full rationale and the
# for the full rationale. # validation log on PineTab2 (RK3566).
# #
# Build host: chromium-builder LXD container on boltzmann (8 cores, # Multi-arch: builds natively on x86_64 and aarch64. The x86_64 path
# 28 GB RAM cap, 824 GB NVMe). 6-10 h initial build. # is primarily a development / CI host; the runtime target audience is
# aarch64. The two patches are architecture-independent.
pkgname=chromium-fourier pkgname=chromium-fourier
pkgver=147.0.7727.116 pkgver=147.0.7727.116
pkgrel=1 pkgrel=2
epoch=1 epoch=1
pkgdesc='Chromium with V4L2VDA HW video decode unlocked for mainline Linux Wayland on Rockchip' pkgdesc='Chromium with V4L2VDA HW video decode unlocked for mainline Linux Wayland on Rockchip'
arch=('aarch64') arch=('aarch64' 'x86_64')
url='https://www.chromium.org/Home' url='https://www.chromium.org/Home'
license=('BSD-3-Clause') license=('BSD-3-Clause')
depends=( depends=(
@@ -68,45 +69,84 @@ optdepends=(
provides=(chromium) provides=(chromium)
conflicts=(chromium) conflicts=(chromium)
options=('!lto' '!strip') options=('!lto' '!strip')
# NB: the chromium tarball is fetched lazily by build.sh on the build
# host (5.7 GB compressed, doesn't belong in this git repo). The # Canonical chromium release tarball (5.7 GB compressed). Versions track
# PKGBUILD assumes /build/chromium/src is already populated. # the chromium release schedule (see https://chromiumdash.appspot.com).
# When bumping pkgver the patches may need their hunk line numbers
# refreshed against the new tree — they are written against
# media/base/media_switches.cc and ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc
# which both move around between minor releases.
source=( source=(
'patches/chromeos-pipeline-bypass.patch' "https://commondatastorage.googleapis.com/chromium-browser-official/chromium-${pkgver}.tar.xz"
'patches/enable-v4l2-decoder-default.patch'
'patches/wayland-allow-direct-egl-gles2.patch'
'patches/nv12-external-oes-on-modifier-external-only.patch'
) )
sha256sums=( sha256sums=(
'SKIP' 'SKIP'
'SKIP'
'SKIP'
'SKIP'
) )
# NB: this PKGBUILD is currently development-shaped — it operates on a
# pre-extracted chromium tree at /build/chromium/src rather than letting
# makepkg fetch + unpack the 5.7 GB tarball into ${srcdir}. Once the
# patches stabilise we'll switch to the canonical pattern with
# source=(${url}/chromium-${pkgver}.tar.xz ...) so the standard makepkg
# flow + CI fermi-style pipeline work end-to-end.
prepare() { prepare() {
cd /build/chromium/src cd "${srcdir}/chromium-${pkgver}"
# Fourier-local: bypass the chromeos pipeline so VaapiVideoDecoder / # Fourier patch 1/2: flip kAcceleratedVideoDecodeLinux's default to
# V4L2VDA is reachable on Linux non-ChromeOS. The 7Ji-style gn args # enabled when USE_V4L2_CODEC is the build's HW decode path. Without
# (use_v4l2_codec / use_v4lplugin / use_linux_v4l2_only) may be # this the runtime master gate stays off on USE_V4L2_CODEC-only builds
# sufficient on their own; this patch is the fallback if they aren't. # and chrome silently falls back to ffmpeg software decode. See the
patch -Np1 -i "${srcdir}/patches/chromeos-pipeline-bypass.patch" || true # patch header for the validation log on RK3566 hantro.
patch -Np1 -i "${srcdir}/patches/enable-v4l2-decoder-default.patch"
# Fourier patch 2/3: re-allow the direct EGL/GLES2 path in the Wayland
# ozone surface factory so panfrost's EGL_EXT_image_dma_buf_import
# surfaces to chrome's GL display, lighting up the NV12 zero-copy
# native-pixmap pipeline. The launcher defaults to ANGLE (DCHECK in
# gl_context_egl.cc:241 fires on direct EGL with non-official builds);
# this patch keeps the direct path available for users who flip
# is_official_build=true and want the lower-CPU pipeline.
patch -Np1 -i "${srcdir}/patches/wayland-allow-direct-egl-gles2.patch"
# Fourier patch 3/3: pick GL_TEXTURE_EXTERNAL_OES for NV12 dmabufs
# whose DRM modifier is advertised external_only by the EGL driver.
# On panfrost / panthor every NV12 modifier (LINEAR + AFBC + AFRC) is
# external_only; chromium's default OzoneImageGLTexturesHolder picked
# GL_TEXTURE_2D and ANGLE then rejected the YUV EGLImage on a
# non-EXTERNAL_OES target, forcing the NV12->AR24 software conversion
# fallback. This closes that gap and enables the actual zero-copy
# path. Validated on ohm (RK3566 hantro): 1080p30 H.264 drops from
# ~131% combined CPU to ~34.7% (~3.8x). See patches/0004 for context.
patch -Np1 -i "${srcdir}/patches/nv12-external-oes-on-modifier-external-only.patch"
# Use system node, system java # Use system node, system java
rm -f third_party/node/linux/node-linux-arm64/bin/node case "$CARCH" in
mkdir -p third_party/node/linux/node-linux-arm64/bin aarch64) _node_dir=node-linux-arm64 ;;
ln -sf /usr/bin/node third_party/node/linux/node-linux-arm64/bin/ x86_64) _node_dir=node-linux-x64 ;;
esac
rm -f "third_party/node/linux/${_node_dir}/bin/node"
mkdir -p "third_party/node/linux/${_node_dir}/bin"
ln -sf /usr/bin/node "third_party/node/linux/${_node_dir}/bin/"
ln -sf /usr/bin/java third_party/jdk/current/bin/ 2>/dev/null || true ln -sf /usr/bin/java third_party/jdk/current/bin/ 2>/dev/null || true
} }
build() { build() {
cd /build/chromium/src cd "${srcdir}/chromium-${pkgver}"
case "$CARCH" in
aarch64) _target_cpu="arm64" ;;
x86_64) _target_cpu="x64" ;;
esac
local _flags=( local _flags=(
'is_official_build=true' "target_cpu=\"${_target_cpu}\""
'is_official_build=false'
'is_debug=false' 'is_debug=false'
# dcheck_always_on defaults to !is_official_build (true here);
# explicitly off so the direct EGL/GLES2 path doesn't FATAL on
# gl_context_egl.cc:241's DCHECK(!global_texture_share_group_).
'dcheck_always_on=false'
'symbol_level=0' 'symbol_level=0'
'is_cfi=false' 'is_cfi=false'
'treat_warnings_as_errors=false' 'treat_warnings_as_errors=false'
@@ -123,7 +163,7 @@ build() {
'use_v4l2_codec=true' 'use_v4l2_codec=true'
'use_v4lplugin=true' 'use_v4lplugin=true'
'use_linux_v4l2_only=true' 'use_linux_v4l2_only=true'
'use_vaapi=true' 'use_vaapi=false'
# Codec branding for proprietary codec support (H.264 etc.) # Codec branding for proprietary codec support (H.264 etc.)
'ffmpeg_branding="Chrome"' 'ffmpeg_branding="Chrome"'
@@ -136,20 +176,38 @@ build() {
) )
gn gen out/Default --args="${_flags[*]}" gn gen out/Default --args="${_flags[*]}"
autoninja -C out/Default chrome chromedriver chrome_sandbox ninja -C out/Default chrome chrome_crashpad_handler
} }
package() { package() {
cd /build/chromium/src cd "${srcdir}/chromium-${pkgver}"
install -Dm755 out/Default/chrome "${pkgdir}/usr/lib/chromium/chromium" install -Dm755 out/Default/chrome "${pkgdir}/usr/lib/chromium/chromium"
install -Dm4755 out/Default/chrome_sandbox "${pkgdir}/usr/lib/chromium/chrome-sandbox" install -Dm755 out/Default/chrome_crashpad_handler \
install -Dm755 out/Default/chromedriver "${pkgdir}/usr/bin/chromedriver" "${pkgdir}/usr/lib/chromium/chrome_crashpad_handler"
[ -f out/Default/chrome_sandbox ] && install -Dm4755 out/Default/chrome_sandbox \
"${pkgdir}/usr/lib/chromium/chrome-sandbox"
[ -f out/Default/chromedriver ] && install -Dm755 out/Default/chromedriver \
"${pkgdir}/usr/bin/chromedriver"
# Bundled GL/Vulkan runtime — chrome dlopens these from its own dir,
# not /usr/lib/. Without them GL init fails and chrome falls back to
# software compositing.
for so in libEGL.so libGLESv2.so libvk_swiftshader.so libvulkan.so.1; do
[ -f "out/Default/$so" ] && install -Dm755 "out/Default/$so" \
"${pkgdir}/usr/lib/chromium/$so"
done
# ANGLE and SwiftShader ICD config files
for icd in out/Default/*_icd.json; do
[ -f "$icd" ] && install -Dm644 "$icd" \
"${pkgdir}/usr/lib/chromium/$(basename "$icd")"
done
# Resources / locales / pak files # Resources / locales / pak files
for f in chrome_100_percent.pak chrome_200_percent.pak resources.pak \ for f in chrome_100_percent.pak chrome_200_percent.pak resources.pak \
v8_context_snapshot.bin icudtl.dat headless_lib_data.pak \ v8_context_snapshot.bin snapshot_blob.bin icudtl.dat \
headless_lib_strings.pak; do headless_lib_data.pak headless_lib_strings.pak \
headless_command_resources.pak; do
[ -f "out/Default/$f" ] && install -Dm644 "out/Default/$f" \ [ -f "out/Default/$f" ] && install -Dm644 "out/Default/$f" \
"${pkgdir}/usr/lib/chromium/$f" "${pkgdir}/usr/lib/chromium/$f"
done done
@@ -160,7 +218,48 @@ package() {
cp -r out/Default/locales/* "${pkgdir}/usr/lib/chromium/locales/" cp -r out/Default/locales/* "${pkgdir}/usr/lib/chromium/locales/"
fi fi
# Top-level launcher # Launcher shim — defaults to ANGLE→GLES on Wayland with Vulkan
# disabled. Vulkan is off by default because:
# - panvk on RK3566 (Mali-G52 Bifrost) returns
# VK_ERROR_INCOMPATIBLE_DRIVER on chromium's probe and breaks
# V4L2 dispatch downstream (chrome falls back to FFmpeg software);
# - panthor on RK3588 (Mali-G610 Valhall) is more functional but
# not yet validated end-to-end against this build.
#
# User overrides for development on other Rockchips:
# --enable-features=Vulkan enable Vulkan (panthor / others)
# --use-vulkan=native|swiftshader pick the Vulkan backend
# --disable-features=Vulkan explicit re-disable
# Any of those on the command line short-circuits the launcher's
# default disable, so the user's intent always wins.
install -dm755 "${pkgdir}/usr/bin" install -dm755 "${pkgdir}/usr/bin"
ln -s /usr/lib/chromium/chromium "${pkgdir}/usr/bin/chromium" cat > "${pkgdir}/usr/bin/chromium" <<'LAUNCHER'
#!/bin/bash
# chromium-fourier launcher — V4L2 HW decode + Wayland + ANGLE
# Vulkan disabled by default; pass --enable-features=Vulkan or
# --use-vulkan=native to opt in (e.g. RK3588 panthor work).
USER_HANDLES_VULKAN=0
for arg in "$@"; do
case "$arg" in
--use-vulkan*|--enable-features=*Vulkan*|--disable-features=*Vulkan*|--use-angle=vulkan*)
USER_HANDLES_VULKAN=1
break
;;
esac
done
vulkan_default=()
if [ "$USER_HANDLES_VULKAN" = 0 ]; then
vulkan_default=(--disable-features=Vulkan)
fi
exec /usr/lib/chromium/chromium \
--ozone-platform=wayland \
--use-gl=angle --use-angle=gles \
--enable-features=AcceleratedVideoDecoder \
"${vulkan_default[@]}" \
"$@"
LAUNCHER
chmod 0755 "${pkgdir}/usr/bin/chromium"
} }
@@ -1,22 +0,0 @@
From: Markus Fritsche <mfritsche@reauktion.de>
Subject: media/gpu: skip chromeos VideoDecoderPipeline on non-ChromeOS Linux
Placeholder. The patch will be developed against the actual chromium
147.0.7727.116 source tree on chromium-builder@boltzmann once the
tarball is extracted and we can read the exact code paths in:
- media/gpu/chromeos/video_decoder_pipeline.cc (PickDecoderOutputFormat,
Initialize, ImageProcessor setup)
- media/gpu/vaapi/vaapi_video_decoder.cc (ApplyResolutionChangeWithScreenSizes,
line ~1219 where "failed Initialize()ing the frame pool" fires)
The 7Ji-style gn args (`use_v4l2_codec=true use_v4lplugin=true
use_linux_v4l2_only=true`) MAY be sufficient by themselves to route
decode through the legacy V4L2VDA path entirely, bypassing the
chromeos pipeline without source patches. First build will tell us;
this file is the placeholder for the patch we'll need if it isn't.
# Empty no-op patch. patch -p1 < this won't change anything.
diff --git a/PLACEHOLDER b/PLACEHOLDER
new file mode 100644
index 0000000..e69de29
@@ -0,0 +1,38 @@
From: 7Ji <7Ji@example.com> (originally), adapted for chromium-fourier
Subject: Adjust compiler-rt library path layout for system clang on Arch
Linux ARM, where compiler-rt installs to lib/clang/N/lib/linux/ with
-aarch64 filename suffix instead of chromium's expected
lib/clang/N/lib/aarch64-unknown-linux-gnu/ layout.
diff --git a/build/config/clang/BUILD.gn b/build/config/clang/BUILD.gn
index d4de2e0cca0..57359c32121 100644
--- a/build/config/clang/BUILD.gn
+++ b/build/config/clang/BUILD.gn
@@ -130,12 +130,15 @@ template("clang_lib") {
} else if (is_linux || is_chromeos) {
if (current_cpu == "x64") {
_dir = "x86_64-unknown-linux-gnu"
+ _suffix = "-x86_64"
} else if (current_cpu == "x86") {
_dir = "i386-unknown-linux-gnu"
+ _suffix = "-i386"
} else if (current_cpu == "arm") {
_dir = "armv7-unknown-linux-gnueabihf"
} else if (current_cpu == "arm64") {
_dir = "aarch64-unknown-linux-gnu"
+ _suffix = "-aarch64"
} else {
assert(false) # Unhandled cpu type
}
@@ -166,6 +169,11 @@ template("clang_lib") {
assert(false) # Unhandled target platform
}
+ # Bit of a hack to make this find builtins from compiler-rt >= 16
+ if (is_linux || is_chromeos) {
+ _dir = "linux"
+ }
+
_clang_lib_dir = "$clang_base_path/lib/clang/$clang_version/lib"
_lib_file = "${_prefix}clang_rt.${_libname}${_suffix}.${_ext}"
libs = [ "$_clang_lib_dir/$_dir/$_lib_file" ]
@@ -0,0 +1,55 @@
From: Markus Fritsche <mfritsche@reauktion.de>
Subject: media: default kAcceleratedVideoDecodeLinux to enabled when
USE_V4L2_CODEC is the build's hardware decode path
Date: 2026-04-26
Background
----------
chromium-fourier targets mainline-Linux Wayland on Rockchip (RK3566 hantro,
RK3588 VDPU381) where the only HW video decode path is V4L2 stateless
(via the in-tree media/gpu/v4l2 stack). The build is configured with
use_vaapi = false
use_v4l2_codec = true
use_v4lplugin = true
use_linux_v4l2_only = true
Without this patch, GPU-process V4L2 decode is compiled in but stays
runtime-disabled by default. The runtime master gate
`media::kAcceleratedVideoDecodeLinux` (the user-visible feature name is
"AcceleratedVideoDecoder") is currently flipped to ENABLED_BY_DEFAULT only
when `BUILDFLAG(USE_VAAPI)` is set. On a USE_V4L2_CODEC-only build the
feature stays DISABLED_BY_DEFAULT, the linux gpu_mojo_media_client returns
`VideoDecoderType::kUnknown`, and `<video>` falls all the way back to
`media/filters/ffmpeg_video_decoder.cc` (software).
We confirmed this by hand on the PineTab2 (RK3566 hantro): with
`--enable-features=AcceleratedVideoDecoder` chrome correctly selects
`V4L2VideoDecoder` for h264 main, opens /dev/video1 + /dev/media0,
allocates 17 OUTPUT + 6 CAPTURE NV12 buffers, and runs SetExtCtrlsInit for
H264. Without the runtime flag, none of that happens.
Fix
---
Treat `USE_V4L2_CODEC` symmetrically with `USE_VAAPI` for the runtime
default of the master gate. A user can still disable it via
`--disable-features=AcceleratedVideoDecoder`.
This does NOT touch the `kAcceleratedVideoDecodeLinuxGL` companion gate
(already ENABLED_BY_DEFAULT) or any of the per-decoder selection logic in
`media/mojo/services/gpu_mojo_media_client_linux.cc` -- that file already
dispatches to the V4L2 decoder when `USE_V4L2_CODEC && !USE_VAAPI`, gated
behind the master flag we are flipping here.
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -749,7 +749,7 @@ BASE_FEATURE(kUnifiedAutoplay, base::FEATURE_ENABLED_BY_DEFAULT);
// on chromeos, but needs an experiment on linux.
BASE_FEATURE(kAcceleratedVideoDecodeLinux,
"AcceleratedVideoDecoder",
-#if BUILDFLAG(USE_VAAPI)
+#if BUILDFLAG(USE_VAAPI) || BUILDFLAG(USE_V4L2_CODEC)
base::FEATURE_ENABLED_BY_DEFAULT);
#else
base::FEATURE_DISABLED_BY_DEFAULT);
@@ -0,0 +1,187 @@
From: Markus Fritsche <mfritsche@reauktion.de>
Subject: [PATCH] gpu/ozone: pick GL_TEXTURE_EXTERNAL_OES for NV12 dmabufs whose
DRM modifier is advertised external_only by the EGL driver
Date: 2026-04-28
Background
----------
On mainline-Linux Mali GPUs (mesa panfrost / panthor on Bifrost / Valhall)
every NV12 modifier exposed by `eglQueryDmaBufModifiersEXT` is flagged
`external_only` — DRM_FORMAT_MOD_LINEAR + ARM AFBC × 2 + ARM AFRC. Mesa's
behavior is spec-correct: GLES sampling of multi-plane formats is
defined only via `samplerExternalOES`, never `sampler2D`. The chromium
NV12 import path at
`gpu/command_buffer/service/shared_image/ozone_image_gl_textures_holder.cc`
already chooses `GL_TEXTURE_EXTERNAL_OES` when the SharedImageFormat is
flagged `PrefersExternalSampler` — but that flag is only set for the
generic "multi-plane on Linux" case in
`media/gpu/chromeos/mailbox_video_frame_converter.cc`. Frames that
arrive with an `external_only`-flagged modifier from a producer that
didn't set the flag (V4L2 hantro NV12 with AFBC/AFRC capture format on
RK3588's rkvdec2, future NativePixmap producers, etc.) hit the
`GL_TEXTURE_2D` path; ANGLE's `validationES.cpp:4894` then rejects YUV
EGLImages on non-EXTERNAL_OES targets, and the import fails.
This patch closes the gap: the texture-target choice in
`OzoneImageGLTexturesHolder::GetBinding` now consults the EGL driver's
`external_only` annotation for the pixmap's actual modifier in addition
to `format.PrefersExternalSampler()`. If either says "external sampler
required", the target switches to `GL_TEXTURE_EXTERNAL_OES`. Skia
Ganesh handles `GL_TEXTURE_EXTERNAL_OES` natively via
`GrGLTextureInfo.fTarget`, so no shader changes are required. Same
infrastructure chromium already uses for Android camera / decoder
dmabufs, retargeted at the Linux ozone layer.
Result is cached per `(fourcc, modifier)` tuple via a function-local
static `base::flat_map`, so the EGL query is not on the per-frame hot
path — once per unique format+modifier combination, after which the
runtime cost is a hash lookup behind a base::Lock.
Bug crbug.com/1498703 is the closest existing tracker; framing this
upstream as "make Linux NV12 import path consistent with the
ChromeOS PrefersExternalSampler default" is the right angle.
diff --git a/gpu/command_buffer/service/shared_image/ozone_image_gl_textures_holder.cc b/gpu/command_buffer/service/shared_image/ozone_image_gl_textures_holder.cc
index 525bdcb0dc..43b0723326 100644
--- a/gpu/command_buffer/service/shared_image/ozone_image_gl_textures_holder.cc
+++ b/gpu/command_buffer/service/shared_image/ozone_image_gl_textures_holder.cc
@@ -16,6 +16,7 @@
#include "ui/gl/gl_bindings.h"
#include "ui/gl/scoped_binders.h"
#include "ui/ozone/public/gl_ozone.h"
+#include "ui/ozone/common/native_pixmap_egl_binding.h"
#include "ui/ozone/public/native_pixmap_gl_binding.h"
#include "ui/ozone/public/ozone_platform.h"
#include "ui/ozone/public/surface_factory_ozone.h"
@@ -82,7 +83,14 @@ std::unique_ptr<ui::NativePixmapGLBinding> GetBinding(
// being multiplanar (if using per-plane sampling of a multiplanar texture,
// the buffer format passed in here must be the single-planar format of the
// plane).
- if (format.PrefersExternalSampler()) {
+ // chromium-fourier: also pick GL_TEXTURE_EXTERNAL_OES whenever the
+ // pixmap's DRM modifier is advertised external_only by the EGL
+ // driver. Mesa panfrost / panthor mark every NV12 modifier
+ // external_only — the PrefersExternalSampler flag alone misses
+ // the AFBC / AFRC tiled paths.
+ if (format.PrefersExternalSampler() ||
+ ui::NativePixmapEGLBinding::ModifierRequiresExternalOES(
+ pixmap.get(), plane_format)) {
target = GL_TEXTURE_EXTERNAL_OES;
} else {
target = GL_TEXTURE_2D;
diff --git a/ui/ozone/common/native_pixmap_egl_binding.cc b/ui/ozone/common/native_pixmap_egl_binding.cc
index 31877f4459..6855c1093e 100644
--- a/ui/ozone/common/native_pixmap_egl_binding.cc
+++ b/ui/ozone/common/native_pixmap_egl_binding.cc
@@ -6,10 +6,13 @@
#include <array>
+#include "base/containers/flat_map.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
+#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
+#include "base/synchronization/lock.h"
#include "ui/gfx/linux/drm_util_linux.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_surface_egl.h"
@@ -56,6 +59,75 @@ bool NativePixmapEGLBinding::IsSharedImageFormatSupported(
viz::SharedImageFormat format) {
return GetFourCCFormatFromSharedImageFormat(format) != DRM_FORMAT_INVALID;
}
+// static
+bool NativePixmapEGLBinding::ModifierRequiresExternalOES(
+ const gfx::NativePixmap* pixmap,
+ viz::SharedImageFormat format) {
+ // chromium-fourier: query the EGL driver for the (fourcc, modifier)
+ // tuple's external_only flag. Cache results — eglQueryDmaBufModifiersEXT
+ // is a synchronous round-trip into the driver and we want it off the
+ // per-frame hot path. The cache lives for the lifetime of the GPU
+ // process (modifier tables don't change after EGL init).
+ if (!pixmap) {
+ return false;
+ }
+ const uint64_t modifier = pixmap->GetFormatModifier();
+ if (modifier == gfx::NativePixmapHandle::kNoModifier) {
+ // Implicit linear — same answer the driver would give for the
+ // matching LINEAR entry, but cheaper not to query.
+ return false;
+ }
+ const uint32_t fourcc = GetFourCCFormatFromSharedImageFormat(format);
+ if (fourcc == DRM_FORMAT_INVALID) {
+ return false;
+ }
+
+ using Key = std::pair<uint32_t, uint64_t>;
+ static base::NoDestructor<base::Lock> cache_lock;
+ static base::NoDestructor<base::flat_map<Key, bool>> cache;
+ {
+ base::AutoLock lock(*cache_lock);
+ auto it = cache->find({fourcc, modifier});
+ if (it != cache->end()) {
+ return it->second;
+ }
+ }
+
+ bool external_only = false;
+ do {
+ auto* display = gl::GLSurfaceEGL::GetGLDisplayEGL();
+ if (!display || !display->ext->b_EGL_EXT_image_dma_buf_import_modifiers) {
+ break;
+ }
+ EGLDisplay egl_display = display->GetDisplay();
+ EGLint num_modifiers = 0;
+ if (!eglQueryDmaBufModifiersEXT(egl_display, fourcc, 0, nullptr, nullptr,
+ &num_modifiers) ||
+ num_modifiers <= 0) {
+ break;
+ }
+ std::vector<EGLuint64KHR> modifiers(num_modifiers);
+ std::vector<EGLBoolean> ext_only(num_modifiers);
+ if (!eglQueryDmaBufModifiersEXT(egl_display, fourcc, num_modifiers,
+ modifiers.data(), ext_only.data(),
+ &num_modifiers)) {
+ break;
+ }
+ for (EGLint i = 0; i < num_modifiers; ++i) {
+ if (modifiers[i] == modifier) {
+ external_only = (ext_only[i] == EGL_TRUE);
+ break;
+ }
+ }
+ } while (0);
+
+ {
+ base::AutoLock lock(*cache_lock);
+ cache->insert_or_assign({fourcc, modifier}, external_only);
+ }
+ return external_only;
+}
+
// static
std::unique_ptr<NativePixmapGLBinding> NativePixmapEGLBinding::Create(
diff --git a/ui/ozone/common/native_pixmap_egl_binding.h b/ui/ozone/common/native_pixmap_egl_binding.h
index 61fb0de77f..ad3ac9ced5 100644
--- a/ui/ozone/common/native_pixmap_egl_binding.h
+++ b/ui/ozone/common/native_pixmap_egl_binding.h
@@ -27,6 +27,17 @@ class NativePixmapEGLBinding : public NativePixmapGLBinding {
static bool IsSharedImageFormatSupported(viz::SharedImageFormat format);
+ // chromium-fourier: returns true when |pixmap|'s DRM format modifier
+ // is advertised by the EGL driver as `external_only` for the given
+ // SharedImage format. Used at SharedImage creation time to override
+ // the default GL_TEXTURE_2D target to GL_TEXTURE_EXTERNAL_OES so that
+ // mesa panfrost / panthor NV12 dmabufs (always external_only) import
+ // cleanly via glEGLImageTargetTexture2DOES + samplerExternalOES.
+ // Result is cached per (fourcc, modifier) tuple — the underlying
+ // eglQueryDmaBufModifiersEXT call is not on the per-frame hot path.
+ static bool ModifierRequiresExternalOES(const gfx::NativePixmap* pixmap,
+ viz::SharedImageFormat format);
+
// Create an EGLImage from a given NativePixmap and plane and bind
// |texture_id| to |target| followed by binding the image to |target|. The
// color space is for the external sampler: When we sample the YUV buffer as
@@ -0,0 +1,57 @@
From: Markus Fritsche <mfritsche@reauktion.de>
Subject: ozone/wayland: re-allow direct EGL/GLES2 path (no ANGLE shim)
Date: 2026-04-26
Background
----------
On Wayland-only ozone builds the surface factory currently advertises only
ANGLE-mediated GL implementations (`kOpenGL`, `kOpenGLES`, `kSwiftShader`,
`kVulkan`). Anything driving `--use-gl=egl` (the historical
`kGLImplementationEGLGLES2`) is rejected at startup with
Requested GL implementation (gl=egl-gles2,angle=none) not found in
allowed implementations: [(gl=egl-angle,angle=opengl|opengles|...)]
The downstream switch already handles `kGLImplementationEGLGLES2` in
`GetGLOzone`, so the dispatcher is wired -- it's the *advertisement* that
got tightened.
The cost of that tightening on RK3566 hantro / panfrost is real: with
ANGLE in the path, chrome's display-side EGL is ANGLE's own EGL. ANGLE's
GLES backend on Linux does not propagate
`EGL_EXT_image_dma_buf_import` through to the chrome GL display, so
`gpu_feature_info.supports_nv12_gl_native_pixmap` ends up false. That in
turn forces the V4L2-decoded NV12 frames through the
NV12-to-AR24 VPP conversion path before they hit the compositor, costing
~85 % CPU at 1080p30 even though the hantro VPU is doing the actual
decode for free.
Allowing the direct EGL/GLES2 path back means chrome's EGL is panfrost's
EGL (via mesa), which exposes the dmabuf-import extensions natively, and
the zero-copy NV12 native pixmap path lights up.
Fix
---
Add `kGLImplementationEGLGLES2` to the head of the allowed list; ANGLE
remains the default fallback and is still selected when the user passes
`--use-gl=angle ...`. The position is deliberate: on a USE_V4L2_CODEC
hardware-decode build the user almost always wants the dmabuf-capable
direct path; ANGLE is still there for browsers that need its conformance
fixups.
This does not affect non-Wayland ozone backends.
diff --git a/ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc b/ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc
--- a/ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc
+++ b/ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc
@@ -223,6 +223,10 @@ std::vector<gl::GLImplementationParts>
WaylandSurfaceFactory::GetAllowedGLImplementations() {
std::vector<gl::GLImplementationParts> impls;
if (egl_implementation_) {
+ // chromium-fourier: keep the direct EGL/GLES2 path available so
+ // panfrost's EGL_EXT_image_dma_buf_import surfaces to chrome's GL
+ // display layer. See patch header for rationale.
+ impls.emplace_back(gl::GLImplementationParts(gl::kGLImplementationEGLGLES2));
impls.emplace_back(gl::ANGLEImplementation::kOpenGL);
impls.emplace_back(gl::ANGLEImplementation::kOpenGLES);
impls.emplace_back(gl::ANGLEImplementation::kSwiftShader);
+163
View File
@@ -0,0 +1,163 @@
# Maintainer: Markus Fritsche <mfritsche@reauktion.de>
#
# Firefox with V4L2 stateless (request API) hardware video decode
# unlocked for mainline-Linux Rockchip (RK3399 rkvdec, RK3566/RK3588
# hantro multiplanar, RK3588 rkvdec2). Sibling to chromium-fourier;
# same niche. No vendor MPP, no Mali blob, no panfork, no 5.10 BSP.
#
# Patch series adds 4 thin shims around upstream firefox (~+169 lines,
# zero deletions). Architecture: stateless decode rides libavcodec's
# v4l2_request hwaccel (AV_HWDEVICE_TYPE_DRM); no separate Mozilla V4L2
# decoder gets written. See ../../arch/firefox-fourier/PLAN.md for
# the full diagnosis. Mozilla bug 1969297.
pkgname=firefox-fourier
pkgver=150.0.1
pkgrel=1
pkgdesc='Firefox with V4L2 stateless HW video decode unlocked for mainline Linux Rockchip'
arch=('aarch64' 'x86_64')
url='https://www.mozilla.org/firefox'
license=('MPL-2.0')
depends=(
alsa-lib
at-spi2-core
cairo
dbus
ffmpeg
fontconfig
freetype2
gcc-libs
gdk-pixbuf2
glib2
glibc
gtk3
hicolor-icon-theme
libdrm
libpulse
libva
libxcb
libxkbcommon
mesa
nspr
nss
pango
pciutils
ttf-liberation
v4l-utils
)
makedepends=(
cbindgen
clang
imagemagick
inetutils
lld
llvm
mesa
nasm
nodejs
python
rust
unzip
wasi-compiler-rt
wasi-libc
yasm
zip
)
optdepends=(
'hunspell-en_us: spell checking, American English'
'libnotify: send notifications when downloads complete'
'pulseaudio: audio support'
)
provides=(firefox)
conflicts=(firefox)
options=('!emptydirs' '!strip')
source=(
"https://archive.mozilla.org/pub/firefox/releases/${pkgver}/source/firefox-${pkgver}.source.tar.xz"
'mozconfig'
# Arch's official firefox patches — toolchain glue for clang 22 +
# glibc 2.43 + Rust 1.95+. Picked up verbatim because we hit the same
# walls. arch-0001 (install-under-remoting) skipped — our launcher
# ships under /usr/bin/firefox-fourier with our own wrapper.
# https://gitlab.archlinux.org/archlinux/packaging/packages/firefox
'arch-0002-Bug-2033279-Make-enable-rust-simd-work-with-Rust-1.9.patch'
'arch-0003-Patch-glsl-optimizer-to-build-with-glibc-2.43.patch'
'arch-0004-Bug-2023597-Use-wasm32-wasip1-target-for-clang-22.1-.patch'
# firefox-fourier patches — V4L2 stateless decode unlock.
'0001-gfxinfo-v4l2-stateless-fourccs.patch'
'0002-libwrapper-hwdevice-ctx-create.patch'
'0003-ffmpegvideo-v4l2-request-route.patch'
'0004-prefs-v4l2-request.patch'
)
sha256sums=('SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP')
prepare() {
cd "${srcdir}/firefox-${pkgver}"
# Toolchain glue (Arch upstream) — apply BEFORE the fourier patches.
patch -Np1 -i "${srcdir}/arch-0002-Bug-2033279-Make-enable-rust-simd-work-with-Rust-1.9.patch"
patch -Np1 -i "${srcdir}/arch-0003-Patch-glsl-optimizer-to-build-with-glibc-2.43.patch"
patch -Np1 -i "${srcdir}/arch-0004-Bug-2023597-Use-wasm32-wasip1-target-for-clang-22.1-.patch"
# Fourier patches — order matters; see ../PLAN.md for rationale.
patch -Np1 -i "${srcdir}/0001-gfxinfo-v4l2-stateless-fourccs.patch"
patch -Np1 -i "${srcdir}/0002-libwrapper-hwdevice-ctx-create.patch"
patch -Np1 -i "${srcdir}/0003-ffmpegvideo-v4l2-request-route.patch"
patch -Np1 -i "${srcdir}/0004-prefs-v4l2-request.patch"
cp "${srcdir}/mozconfig" .mozconfig
}
build() {
cd "${srcdir}/firefox-${pkgver}"
# Arch's makepkg.conf injects -fexceptions into CFLAGS/CXXFLAGS by
# default for hardening. Mozilla's STL wrappers refuse to compile
# with exceptions enabled (#error "STL code can only be used with
# -fno-exceptions"). Strip the offender before mach configure picks
# up the env. Same trick the upstream Arch firefox PKGBUILD uses.
CFLAGS="${CFLAGS//-fexceptions/}"
CXXFLAGS="${CXXFLAGS//-fexceptions/}"
export CFLAGS CXXFLAGS
export MOZ_NOSPAM=1
export MOZ_API_KEY_UNUSED=1
export MOZ_TELEMETRY_REPORTING=
export MOZ_REQUIRE_SIGNING=
export MACH_BUILD_PYTHON_NATIVE_PACKAGE_SOURCE=system
export PYTHON=/usr/bin/python
./mach configure
./mach build
}
package() {
cd "${srcdir}/firefox-${pkgver}"
DESTDIR="${pkgdir}" ./mach install
# Move mach's default /usr/local/* layout to /usr/* so we conflict
# with `firefox` cleanly and `provides=firefox` actually works.
# `cp -r` preserves the bin symlink (target lives in /usr/local) —
# delete it before staging the launcher so `cat >` doesn't follow a
# dangling symlink and ENOENT.
if [ -d "${pkgdir}/usr/local" ]; then
cp -r "${pkgdir}/usr/local/." "${pkgdir}/usr/"
rm -rf "${pkgdir}/usr/local"
fi
rm -f "${pkgdir}/usr/bin/firefox-fourier"
# Launcher script. mach's install drops the binary at
# /usr/lib/firefox-fourier/firefox-fourier (a small bash launcher) plus
# firefox-fourier-bin alongside; we exec the launcher.
cat > "${pkgdir}/usr/bin/firefox-fourier" <<'LAUNCHER'
#!/bin/bash
# firefox-fourier launcher — V4L2 stateless HW decode path defaults.
# Patch 4/4 already defaults media.ffmpeg.v4l2-request.enabled=true on
# Linux; the env vars below cover the platform-detection bits firefox
# still consults at startup.
export MOZ_ENABLE_WAYLAND="${MOZ_ENABLE_WAYLAND:-1}"
export MOZ_X11_EGL="${MOZ_X11_EGL:-1}"
exec /usr/lib/firefox-fourier/firefox-fourier "$@"
LAUNCHER
chmod 0755 "${pkgdir}/usr/bin/firefox-fourier"
}
+210
View File
@@ -0,0 +1,210 @@
# firefox-fourier — V4L2 Stateless Decoder Patch Plan
Plan to extend Firefox 149's V4L2 hardware decode path to cover Rockchip
mainline kernel boards (RK3399 rkvdec, RK3566/RK3588 hantro, RK3588
rkvdec2) by routing stateless `S264`/`S265`/`VP9F` fourccs through
libavcodec's `v4l2_request` hwaccel, which mainline FFmpeg surfaces via
`AV_HWDEVICE_TYPE_DRM` (no dedicated `_V4L2REQUEST` enum exists upstream
— confirmed against `libavutil/hwcontext.h`).
## 1. Files touched (in order)
| # | Path | Change | Lines |
|---|------|--------|-------|
| 1 | `widget/gtk/GfxInfo.cpp` | `V4L2ProbeDevice` (~L10301110): add `S264`/`S265`/`VP9F` matches alongside existing `H264`/`HEVC`/`VP90`. Set `mIsV4L2Supported = Some(true)` and OR the same `CODEC_HW_DEC_*` bits. Tag a new bool `mV4L2IsStateless` so downstream can branch. | +35 / -2 |
| 2 | `dom/media/platforms/ffmpeg/FFmpegLibWrapper.h` | Add wrappers for `av_hwdevice_ctx_create` (currently only `_alloc`/`_init` per L173174) and `av_hwdevice_find_type_by_name`. Needed because stateless wants the *device-path-aware* `_create` form to bind `/dev/dri/renderD128`. | +4 / 0 |
| 3 | `dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp` | `dlsym` the two new pointers; gate behind `LIBAVUTIL_VERSION_MAJOR >= 56`. | +6 / 0 |
| 4 | `dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h` | Add `AVBufferRef* mDRMDeviceContext = nullptr;` and `bool mUsingV4L2Request = false;` next to existing `mUsingV4L2`. | +3 / 0 |
| 5 | `dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp` | New `CreateV4L2RequestDeviceContext()` modelled on `CreateVAAPIDeviceContext` (current uses `av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VAAPI)`). Wire it in alongside the existing V4L2 init branch (the one feeding `ChooseV4L2PixelFormat` at L~258). `FindVideoHardwareAVCodec` call at L708 stays unchanged for stateless — we want the *generic* `h264`/`hevc`/`vp9` decoder + a DRM hw_device_ctx, not a `_v4l2m2m` codec. | +90 / -5 |
| 6 | `modules/libpref/init/StaticPrefList.yaml` | New pref `media.ffmpeg.v4l2-request.enabled` mirroring `media.ffmpeg.vaapi.enabled` (default `@IS_LINUX@`). | +6 / 0 |
| 7 | `dom/media/ipc/RDDProcessHost.cpp` + sandbox policy file | Whitelist `/dev/media*`, `/dev/dri/renderD*`, `/dev/video*` for the RDD process (already partly done for VAAPI — verify `policy/linux/SandboxBrokerPolicyFactory.cpp`). | +6 / 0 |
Total: roughly **+150 / -10** across 7 files.
## 2. Probe extension — `GfxInfo.cpp::V4L2ProbeDevice`
Existing pattern (~L1075):
```cpp
if (outFormats.Contains("H264")) { mIsV4L2Supported = Some(true);
mV4L2SupportedCodecs |= CODEC_HW_DEC_H264; ... }
```
Add three siblings, each setting an additional `mV4L2IsStateless = true`:
```cpp
if (outFormats.Contains("S264")) { /* CODEC_HW_DEC_H264 | stateless */ }
if (outFormats.Contains("S265")) { /* CODEC_HW_DEC_HEVC | stateless */ }
if (outFormats.Contains("VP9F")) { /* CODEC_HW_DEC_VP9 | stateless */ }
```
Decision: do **not** introduce a separate `mIsV4L2StatelessSupported`.
Collapse under `mIsV4L2Supported` so the existing feature-gate plumbing
(`MediaCodecsSupport`, `gfxFeature::HW_DECODE_VIDEO`) flips identically
— only `mV4L2IsStateless` distinguishes the routing in step 3. Stateful
+ stateless on the same SoC (rare, but RK3588 has both rkvdec2 + hantro
VEPU) gracefully degrades to whichever codec wins enumeration order.
The capture-format gate (`YV12`/`NV12`) needs widening: stateless
decoders frequently expose only `NV12` or `NV15` (10-bit, RK3588 HEVC).
Add `NV15` and `NM12` (multiplanar NV12, hantro). Without this the
prober rejects an otherwise-good device.
## 3. Decoder routing — `FFmpegVideoDecoder.cpp`
Codec selection happens at **L651** (`AV_HWDEVICE_TYPE_VAAPI`
`h264_vaapi`) and **L708** (the V4L2 fallback path → `h264_v4l2m2m` via
`FindVideoHardwareAVCodec(mLib, mCodecID)` resolving by suffix). The
stateless route diverges from both: the *codec* must remain the generic
`h264`/`hevc`/`vp9` decoder (libavcodec auto-binds `v4l2_request` from
its `hw_configs` when a DRM hw_device_ctx is attached). Pseudo-patch:
```cpp
if (gfxInfo.mUsingV4L2 && gfxInfo.mV4L2IsStateless) {
AVCodec* codec = mLib->avcodec_find_decoder(mCodecID); // generic
mCodecContext = mLib->avcodec_alloc_context3(codec);
if (!CreateV4L2RequestDeviceContext()) return false;
mCodecContext->get_format = ChooseV4L2PixelFormat; // already returns DRM_PRIME
mUsingV4L2Request = true;
}
```
`CreateV4L2RequestDeviceContext()` body:
```cpp
const char* drm = "/dev/dri/renderD128";
if (mLib->av_hwdevice_ctx_create(&mDRMDeviceContext,
AV_HWDEVICE_TYPE_DRM, drm, nullptr, 0) < 0) return false;
mCodecContext->hw_device_ctx = mLib->av_buffer_ref(mDRMDeviceContext);
```
`av_hwdevice_ctx_create` — not currently wrapped — is the entry point.
The codec's internal hwaccel selector then walks
`avcodec_get_hw_config()` and picks the entry whose `device_type ==
AV_HWDEVICE_TYPE_DRM` and `pix_fmt == AV_PIX_FMT_DRM_PRIME`, which is
the v4l2_request hwaccel registered in `libavcodec/v4l2_request_*.c`.
No `av_hwdevice_find_type_by_name("v4l2_request")` needed — stays an
internal libavcodec name.
## 4. Dmabuf / DRM_PRIME reuse
`ChooseV4L2PixelFormat` at L~258270 already returns
`AV_PIX_FMT_DRM_PRIME` and is the *only* format the v4l2_request
hwaccel produces. The downstream consumer (DMABufSurfaceYUV import in
`FFmpegVideoFramePool.cpp`) is already DRM_PRIME-aware for the
stateful path — same code reads `AVDRMFrameDescriptor` from
`frame->data[0]`. **No new output handling required for NV12/YV12.**
10-bit caveat: RK3588 HEVC outputs `DRM_FORMAT_NV15` / `NV20`
(Mali-tile). Existing `WaylandDMABufSurface::CreateYUVSurface`
modifier list does not include `DRM_FORMAT_MOD_ARM_AFBC` or NV15
fourcc. Either reject 10-bit at probe (capture format gate above) or
extend `gfx/layers/DMABUFSurfaceImage.cpp` — out of scope for v1; gate
to NV12 only.
SAND format pollution Turner mentioned in bug 1969297 c#3 is
**Pi5-specific**; rkvdec/hantro do not produce SAND. Safe to ignore for
the Rockchip target.
## 5. Configuration
New pref:
```yaml
- name: media.ffmpeg.v4l2-request.enabled
type: RelaxedAtomicBool
value: @IS_LINUX@
mirror: always
```
No new env var. No `MOZ_X11_EGL`-style kludge. The existing
`MOZ_LOG=PlatformDecoderModule:5` covers diagnostics. Default-on
matches `media.ffmpeg.vaapi.enabled` shape; users get fallback to
software via existing failure paths if `av_hwdevice_ctx_create` fails
(e.g., missing `/dev/media0`).
## 6. Test plan (fresnel — RK3399, KDE Wayland)
1. `ls /dev/video* /dev/media*` — confirm `/dev/video0` (rkvdec
output) and `/dev/media0` exist.
2. `v4l2-ctl -d /dev/video0 --list-formats-out` — expect
`S264`/`S265`/`VP9F`.
3. Start: `MOZ_LOG="PlatformDecoderModule:5,FFmpegVideo:5"
firefox-fourier 2>&1 | tee fx.log`.
4. Open `https://test-videos.co.uk/bigbuckbunny/mp4-h264` 1080p clip.
5. Success markers in `fx.log`:
- `V4L2ProbeDevice: /dev/video0 supports S264 (stateless)`
- `Choosing FFmpeg pixel format for V4L2 video decoding.`
- `Requesting pixel format DRM PRIME`
- `av_hwdevice_ctx_create(DRM, /dev/dri/renderD128) ok`
- **No** `Using preferred software codec h264`.
6. `cat /sys/kernel/debug/clk/clk_summary | grep vdec` — clock should
be active during playback.
7. `top` — CPU < 40% on a single A72 core for 1080p H.264 (stock =
100% on all 6 cores).
## 7. Build + ship — `firefox-fourier` PKGBUILD
Mirror `chromium-fourier` shape exactly (sibling).
```bash
pkgname=firefox-fourier
pkgver=149.0
arch=('aarch64' 'x86_64')
makedepends=(rust clang lld nodejs python cbindgen nasm yasm wasi-libc-bin
gtk3 mesa libva ffmpeg) # ffmpeg only for headers via system libs
source=(
"https://archive.mozilla.org/pub/firefox/releases/${pkgver}/source/firefox-${pkgver}.source.tar.xz"
patches/0001-gfxinfo-v4l2-stateless-fourccs.patch
patches/0002-libwrapper-hwdevice-ctx-create.patch
patches/0003-ffmpegvideo-v4l2-request-route.patch
patches/0004-prefs-v4l2-request.patch
mozconfig
)
```
`prepare()`: `cd firefox-${pkgver}` → apply patches with `patch -Np1`.
`build()`: `MOZ_NOSPAM=1 ./mach build`. `package()`: `./mach install
DESTDIR=${pkgdir}`. `mozconfig` enables
`--enable-default-toolkit=cairo-gtk3-wayland`, `--with-system-ffmpeg`,
`ac_add_options --disable-tests`. **No** `--enable-media-gpu-process`
— let it default. Tarball is the official Mozilla source release (not
gecko-dev).
Extra makedepends vs. stock firefox PKGBUILD: none — this only
modifies existing C++.
## 8. Risk register (ranked)
1. **libavcodec ABI mismatch.** ALARM ships ffmpeg 7.x; Firefox dlopens
whatever's at `libavcodec.so.61`. If the v4l2_request hwaccel was
compiled out (Arch's ffmpeg has it; ALARM rebuild may not),
`av_hwdevice_ctx_create(DRM, ...)` succeeds but no codec binds —
silent fallback. Mitigation: `ffmpeg -hwaccels` should list `drm`.
2. **Renderer-process sandbox** blocks `/dev/dri/renderD128` open.
VAAPI already brokered this for RDD process; verify
`SandboxBrokerPolicyFactory.cpp` covers `/dev/media*` too — likely
doesn't.
3. **glxtest probe runs in stripped env.** `v4l2test` (the
FireTestProcess child) needs `cap_sys_admin` for
`VIDIOC_S_EXT_CTRLS` request API ioctls? No — request API just
needs `O_RDWR` on `/dev/media*`. Should be fine.
4. **Regression of stateful path.** Adding new fourccs is additive;
the routing branch is gated on `mV4L2IsStateless`. Stateful boards
(Pi4) untouched.
5. **NV15/10-bit on RK3588** — explicitly out-of-scope v1;
gate-rejected.
6. **rkvdec2 driver maturity.** Linux 6.12 mainline rkvdec2 H.264
works; HEVC/VP9 still upstream-pending on some boards. Probe will
skip what kernel doesn't expose.
7. **DMA-BUF modifier negotiation** with panfrost/panthor on Wayland
— already shaken out by chromium-fourier on RK3566; same code
path.
## 9. Upstream path (bug 1969297)
Split into 4 reviewable commits matching files 1, 2+3, 5, 6 from the
table. Add a gtest exercising `V4L2ProbeDevice` against a synthetic
v4l2test stdout containing `S264` (no kernel needed). Reach out to
skyevg (D252119 author) for review continuity. r? jya for the
FFmpegVideoDecoder change. The `av_hwdevice_ctx_create` wrapper
addition is a self-contained 6-liner that should land independently.
The 10-bit/SAND concerns Turner raised remain valid for Pi5 —
explicitly scope this series to **stateless DRM_PRIME NV12 only**,
leaving SAND for a follow-up bug.
+36
View File
@@ -0,0 +1,36 @@
# firefox-fourier mozconfig — minimal, Wayland + system ffmpeg.
ac_add_options --enable-application=browser
ac_add_options --enable-default-toolkit=cairo-gtk3-wayland
ac_add_options --enable-release
ac_add_options --enable-optimize
ac_add_options --enable-rust-simd
ac_add_options --enable-linker=lld
# Arch's 0004 patch updates the wasm32-wasip1 target string but ALARM's
# wasi-libc package doesn't expose the headers at the path Mozilla's
# probe looks for. Disable the wasm sandbox — hardens font/graphics
# parsers only, no impact on V4L2 decode. Revisit when ALARM's
# wasi-libc catches up to Arch x86_64's layout.
ac_add_options --without-wasm-sandboxed-libraries
# Firefox dlopens libavcodec.so at runtime regardless of build flags;
# the v4l2_request hwaccel routing happens via the system libavcodec
# loaded at startup, controlled by media.ffmpeg.enabled (default true).
# No configure-time hook needed.
ac_add_options --disable-tests
ac_add_options --disable-debug
ac_add_options --disable-debug-symbols
ac_add_options --disable-crashreporter
ac_add_options --disable-updater
ac_add_options --disable-default-browser-agent
# Mozilla branding requires a separate signed-build-tooling agreement
# we don't have; ship with the unbranded "firefox-fourier" identity.
ac_add_options --with-app-name=firefox-fourier
ac_add_options --with-app-basename=Firefox
ac_add_options --with-distribution-id=de.reauktion.fourier
# Reduce build memory pressure on aarch64 — parallel link is heavy.
mk_add_options MOZ_PARALLEL_BUILD=8
@@ -0,0 +1,75 @@
From: Markus Fritsche <mfritsche@reauktion.de>
Subject: [PATCH 1/4] widget/gtk: recognize V4L2 stateless fourccs in
GfxInfo prober (S264 / S265 / VP9F)
Date: 2026-04-27
Background
----------
Firefox's V4L2 prober in `widget/gtk/GfxInfo.cpp::V4L2ProbeDevice`
parses `v4l2test`'s `V4L2_OUTPUT_FMTS` line and matches against the
fourccs of stateful V4L2-M2M decoders (`H264`, `VP80`, `VP90`, `HEVC`).
That's correct for Pi4 / Mediatek / vendor-MPP stateful decoders but
silently skips every mainline-Linux Rockchip board: RK3399 `rkvdec`,
RK3566 `hantro` (multiplanar), RK3588 `hantro` and `rkvdec2` all
expose stateless fourccs only — `S264`, `S265`, `VP9F`. The probe
binary itself enumerates these correctly (verified end-to-end on
fresnel / Pinebook Pro / RK3399 with `v4l2test --device /dev/video1`
showing `V4L2_OUTPUT_FMTS: S265 S264 VP9F` and
`V4L2_SUPPORTED: TRUE`); the gap is purely in this string table.
This patch adds the three sibling blocks for the stateless fourccs,
each identical in shape to the existing stateful blocks except for
setting a new `mV4L2IsStateless` member. The follow-up patches in
this series (2/4, 3/4, 4/4) consume that member to route through the
libavcodec v4l2_request hwaccel (`AV_HWDEVICE_TYPE_DRM`) instead of
the v4l2m2m codec wrapper used for stateful boards.
Bug 1969297.
diff --git a/widget/gtk/GfxInfo.h b/widget/gtk/GfxInfo.h
--- a/widget/gtk/GfxInfo.h
+++ b/widget/gtk/GfxInfo.h
@@ -127,6 +127,10 @@
mozilla::Maybe<bool> mIsVAAPISupported;
int mVAAPISupportedCodecs = 0;
mozilla::Maybe<bool> mIsV4L2Supported;
+ // firefox-fourier: true when probe matched at least one stateless
+ // V4L2 fourcc (S264 / S265 / VP9F). Drives libavcodec v4l2_request
+ // hwaccel routing in FFmpegVideoDecoder.cpp.
+ bool mV4L2IsStateless = false;
int mV4L2SupportedCodecs = 0;
static int sGLXTestPipe;
diff --git a/widget/gtk/GfxInfo.cpp b/widget/gtk/GfxInfo.cpp
--- a/widget/gtk/GfxInfo.cpp
+++ b/widget/gtk/GfxInfo.cpp
@@ -852,6 +852,29 @@ void GfxInfo::V4L2ProbeDevice(nsCString& dev) {
media::MCSInfo::AddSupport(media::MediaCodecsSupport::HEVCHardwareDecode);
mV4L2SupportedCodecs |= CODEC_HW_DEC_HEVC;
}
+ // firefox-fourier: V4L2 stateless (request API) fourccs. Mainline
+ // Rockchip rkvdec / hantro / rkvdec2 expose these instead of the
+ // V4L2-M2M-stateful fourccs above. Decoding routes through
+ // libavcodec's v4l2_request hwaccel (AV_HWDEVICE_TYPE_DRM) rather
+ // than the *_v4l2m2m codec wrappers — see FFmpegVideoDecoder.cpp.
+ if (outFormats.Contains("S264")) {
+ mIsV4L2Supported = Some(true);
+ mV4L2IsStateless = true;
+ media::MCSInfo::AddSupport(media::MediaCodecsSupport::H264HardwareDecode);
+ mV4L2SupportedCodecs |= CODEC_HW_DEC_H264;
+ }
+ if (outFormats.Contains("S265")) {
+ mIsV4L2Supported = Some(true);
+ mV4L2IsStateless = true;
+ media::MCSInfo::AddSupport(media::MediaCodecsSupport::HEVCHardwareDecode);
+ mV4L2SupportedCodecs |= CODEC_HW_DEC_HEVC;
+ }
+ if (outFormats.Contains("VP9F")) {
+ mIsV4L2Supported = Some(true);
+ mV4L2IsStateless = true;
+ media::MCSInfo::AddSupport(media::MediaCodecsSupport::VP9HardwareDecode);
+ mV4L2SupportedCodecs |= CODEC_HW_DEC_VP9;
+ }
}
const nsTArray<RefPtr<GfxDriverInfo>>& GfxInfo::GetGfxDriverInfo() {
@@ -0,0 +1,52 @@
From: Markus Fritsche <mfritsche@reauktion.de>
Subject: [PATCH 2/4] dom/media/platforms/ffmpeg: wrap
av_hwdevice_ctx_create
Date: 2026-04-27
Background
----------
`FFmpegLibWrapper` already wraps `av_hwdevice_ctx_alloc` (no device
path) and `av_hwdevice_ctx_init`, used by the VAAPI codepath which
discovers the DRM device implicitly. The v4l2_request hwaccel needs
the *path-aware* constructor `av_hwdevice_ctx_create`, which lets the
caller pass `"/dev/dri/renderD128"` (or similar) directly when
creating an `AV_HWDEVICE_TYPE_DRM` context. libavcodec then binds the
v4l2_request hwaccel internally based on the codec's `hw_configs`.
This patch adds the function pointer + the `AV_FUNC_OPTION_SILENT`
registration. Same versioning as the other `av_hwdevice_ctx_*`
wrappers (libavutil 5862). No callers yet — patch 3/4
(FFmpegVideoDecoder routing) consumes it.
Bug 1969297.
diff --git a/dom/media/platforms/ffmpeg/FFmpegLibWrapper.h b/dom/media/platforms/ffmpeg/FFmpegLibWrapper.h
--- a/dom/media/platforms/ffmpeg/FFmpegLibWrapper.h
+++ b/dom/media/platforms/ffmpeg/FFmpegLibWrapper.h
@@ -177,6 +177,11 @@
// libavutil >= 58
AVBufferRef* (*av_hwdevice_ctx_alloc)(int);
int (*av_hwdevice_ctx_init)(AVBufferRef* ref);
+ // firefox-fourier: device-path-aware constructor needed to bind a
+ // DRM hwdevice (AV_HWDEVICE_TYPE_DRM) to /dev/dri/renderD* for the
+ // libavcodec v4l2_request hwaccel.
+ int (*av_hwdevice_ctx_create)(AVBufferRef** device_ctx, int type,
+ const char* device, void* opts, int flags);
AVBufferRef* (*av_hwframe_ctx_alloc)(AVBufferRef* device_ctx);
int (*av_hwframe_ctx_init)(AVBufferRef* ref);
AVBufferRef* (*av_buffer_ref)(AVBufferRef* buf);
diff --git a/dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp b/dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp
--- a/dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp
@@ -293,6 +293,11 @@ FFmpegLibWrapper::LinkResult FFmpegLibWrapper::Link() {
AV_FUNC_AVUTIL_58 | AV_FUNC_AVUTIL_59 |
AV_FUNC_AVUTIL_60 | AV_FUNC_AVUTIL_61 |
AV_FUNC_AVUTIL_62)
+ // firefox-fourier: see comment in FFmpegLibWrapper.h
+ AV_FUNC_OPTION_SILENT(av_hwdevice_ctx_create,
+ AV_FUNC_AVUTIL_58 | AV_FUNC_AVUTIL_59 |
+ AV_FUNC_AVUTIL_60 | AV_FUNC_AVUTIL_61 |
+ AV_FUNC_AVUTIL_62)
AV_FUNC_OPTION_SILENT(
av_buffer_ref, AV_FUNC_AVUTIL_58 | AV_FUNC_AVUTIL_59 | AV_FUNC_AVUTIL_60 |
AV_FUNC_AVUTIL_61 | AV_FUNC_AVUTIL_62)
@@ -0,0 +1,208 @@
From: Markus Fritsche <mfritsche@reauktion.de>
Subject: [PATCH 3/4] dom/media/platforms/ffmpeg: route through libavcodec
v4l2_request hwaccel for V4L2 stateless boards
Date: 2026-04-27
Background
----------
Firefox's existing V4L2 init (`InitV4L2Decoder`) finds the codec by
suffix lookup (`FindVideoHardwareAVCodec(mLib, mCodecID)`), which on
Linux resolves to the **stateful** V4L2-M2M wrapper codec
(`h264_v4l2m2m` etc). Mainline-Linux Rockchip boards (RK3399 rkvdec,
RK3566/RK3588 hantro, RK3588 rkvdec2) only expose **stateless**
V4L2 fourccs (`S264`, `S265`, `VP9F`); the stateful wrapper codec
fails to open, Firefox falls all the way through to software.
This patch adds a sibling init path, `InitV4L2RequestDecoder`, that:
* uses the **generic** codec (e.g. plain `h264`, returned by
`avcodec_find_decoder(AV_CODEC_ID_H264)`) rather than the stateful
wrapper;
* sanity-checks the codec's `hw_configs` for an `AV_HWDEVICE_TYPE_DRM`
entry — that's how libavcodec surfaces the v4l2_request hwaccel
upstream (no dedicated `_V4L2REQUEST` device type exists);
* creates an `AV_HWDEVICE_TYPE_DRM` hwdevice context bound to
`/dev/dri/renderD128` via the new `av_hwdevice_ctx_create` wrapper
(patch 2/4) and attaches it to the codec context;
* reuses the existing `ChooseV4L2PixelFormat` get-format callback
(already returns `AV_PIX_FMT_DRM_PRIME`) and the existing
`apply_cropping = 0` constraint.
`InitV4L2RequestDecoder` is invoked **before** `InitV4L2Decoder` in
`InitHWDecoderIfAllowed`. On Rockchip mainline it succeeds. On Pi4 /
Mediatek / vendor-MPP-stateful boards the codec's `hw_configs` lacks
a DRM entry (the V4L2-M2M codecs don't register one), the sanity
check fails, and the existing stateful `InitV4L2Decoder` runs as
before. No regression of stateful boards.
`mDRMDeviceContext` is unconditionally `av_buffer_unref`'d in
`ProcessShutdown` (no-op when null). Gated behind
`media.ffmpeg.v4l2-request.enabled` from patch 4/4.
Bug 1969297.
diff --git a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h
--- a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h 2026-03-18 19:22:14.000000000 +0000
+++ b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h 2026-04-27 20:43:39.347992674 +0000
@@ -225,7 +225,12 @@
bool IsLinuxHDR() const;
MediaResult InitVAAPIDecoder();
MediaResult InitV4L2Decoder();
+ // firefox-fourier: V4L2 stateless (request API) decode path. Uses
+ // libavcodec's v4l2_request hwaccel, which it surfaces via
+ // AV_HWDEVICE_TYPE_DRM rather than a dedicated _V4L2REQUEST type.
+ MediaResult InitV4L2RequestDecoder();
bool CreateVAAPIDeviceContext();
+ bool CreateV4L2RequestDeviceContext();
bool GetVAAPISurfaceDescriptor(VADRMPRIMESurfaceDescriptor* aVaDesc);
void AddAcceleratedFormats(nsTArray<AVCodecID>& aCodecList,
AVCodecID aCodecID, AVVAAPIHWConfig* hwconfig);
@@ -239,7 +244,10 @@
void AdjustHWDecodeLogging();
AVBufferRef* mVAAPIDeviceContext = nullptr;
+ // firefox-fourier: DRM hwdevice ctx for the v4l2_request hwaccel.
+ AVBufferRef* mDRMDeviceContext = nullptr;
bool mUsingV4L2 = false;
+ bool mUsingV4L2Request = false;
// If video overlay is used we want to upload SW decoded frames to
// DMABuf and present it as a external texture to rendering pipeline.
bool mUploadSWDecodeToDMABuf = false;
diff --git a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
--- a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp 2026-03-18 19:22:14.000000000 +0000
+++ b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp 2026-04-27 20:44:33.280766228 +0000
@@ -406,6 +406,105 @@
return NS_OK;
}
+// firefox-fourier: V4L2 stateless (request API) DRM hwdevice context.
+// libavcodec's v4l2_request hwaccel binds via AV_HWDEVICE_TYPE_DRM —
+// no dedicated _V4L2REQUEST type exists upstream.
+bool FFmpegVideoDecoder<LIBAV_VER>::CreateV4L2RequestDeviceContext() {
+ if (!mLib->av_hwdevice_ctx_create) {
+ FFMPEG_LOG(" av_hwdevice_ctx_create not available (libavutil too old)");
+ return false;
+ }
+ const char* drmDevice = "/dev/dri/renderD128";
+ if (mLib->av_hwdevice_ctx_create(&mDRMDeviceContext,
+ AV_HWDEVICE_TYPE_DRM, drmDevice,
+ nullptr, 0) < 0) {
+ FFMPEG_LOG(" av_hwdevice_ctx_create(DRM, %s) failed", drmDevice);
+ return false;
+ }
+ mCodecContext->hw_device_ctx = mLib->av_buffer_ref(mDRMDeviceContext);
+ FFMPEG_LOG(" DRM hwdevice ctx created on %s", drmDevice);
+ return true;
+}
+
+// firefox-fourier: try V4L2 stateless decode via libavcodec's
+// v4l2_request hwaccel. Distinct from InitV4L2Decoder which uses the
+// stateful h264_v4l2m2m wrapper codec. On Rockchip mainline boards
+// (rkvdec / hantro / rkvdec2) only the stateless path exists.
+MediaResult FFmpegVideoDecoder<LIBAV_VER>::InitV4L2RequestDecoder() {
+ FFMPEG_LOG("Initialising V4L2 stateless (request API) FFmpeg decoder");
+
+ StaticMutexAutoLock mon(sMutex);
+
+ // Use the GENERIC codec (not the _v4l2m2m wrapper). libavcodec picks
+ // the v4l2_request hwaccel internally by walking the codec's
+ // hw_configs once an AV_HWDEVICE_TYPE_DRM ctx is attached.
+ AVCodec* codec = mLib->avcodec_find_decoder(mCodecID);
+ if (!codec) {
+ FFMPEG_LOG(" generic codec for ID %d not found", mCodecID);
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Sanity-check: refuse when libavcodec was built without the
+ // v4l2_request hwaccel (no DRM hwaccel registered against this
+ // codec). Distro packagers occasionally drop --enable-v4l2-request.
+ bool hasDrmHwaccel = false;
+ for (int i = 0;; i++) {
+ const AVCodecHWConfig* cfg = mLib->avcodec_get_hw_config(codec, i);
+ if (!cfg) break;
+ if (cfg->device_type == AV_HWDEVICE_TYPE_DRM) {
+ hasDrmHwaccel = true;
+ break;
+ }
+ }
+ if (!hasDrmHwaccel) {
+ FFMPEG_LOG(" codec %s has no DRM hwaccel — libavcodec built "
+ "without --enable-v4l2-request?", codec->name);
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ FFMPEG_LOG(" V4L2 stateless: codec %s : %s", codec->name, codec->long_name);
+
+ if (!(mCodecContext = mLib->avcodec_alloc_context3(codec))) {
+ FFMPEG_LOG(" couldn't init HW ffmpeg context");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mCodecContext->opaque = this;
+
+ // Reuse the existing V4L2 init helpers: pixel-format selector returns
+ // AV_PIX_FMT_DRM_PRIME, cropping disabled (FFmpeg can't crop opaque
+ // DRM buffers). Same constraints as the stateful V4L2 path.
+ InitHWCodecContext(ContextType::V4L2);
+ mCodecContext->apply_cropping = 0;
+
+ auto releaseDecoder = MakeScopeExit(
+ [&]() MOZ_NO_THREAD_SAFETY_ANALYSIS { ReleaseCodecContext(); });
+
+ if (!CreateV4L2RequestDeviceContext()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ MediaResult ret = AllocateExtraData();
+ if (NS_FAILED(ret)) {
+ return ret;
+ }
+
+ if (mLib->avcodec_open2(mCodecContext, codec, nullptr) < 0) {
+ FFMPEG_LOG(" Couldn't open V4L2 stateless decoder");
+ return NS_ERROR_DOM_MEDIA_FATAL_ERR;
+ }
+
+ if (mAcceleratedFormats.IsEmpty()) {
+ mAcceleratedFormats.AppendElement(mCodecID);
+ }
+
+ AdjustHWDecodeLogging();
+
+ FFMPEG_LOG(" V4L2 stateless FFmpeg init successful");
+ mUsingV4L2 = true;
+ mUsingV4L2Request = true;
+ releaseDecoder.release();
+ return NS_OK;
+}
+
MediaResult FFmpegVideoDecoder<LIBAV_VER>::InitV4L2Decoder() {
FFMPEG_LOG("Initialising V4L2-DRM FFmpeg decoder");
@@ -659,6 +758,16 @@
# endif // MOZ_ENABLE_VAAPI
# ifdef MOZ_ENABLE_V4L2
+ // firefox-fourier: try V4L2 stateless (request API) first. On
+ // mainline-Linux Rockchip boards (RK3399 rkvdec, RK3566/RK3588
+ // hantro, RK3588 rkvdec2) the kernel exposes only stateless
+ // fourccs, so the stateful path below would fail anyway. On
+ // stateful boards (Pi4 / vendor MPP) this gracefully falls
+ // through (no DRM hwaccel registered for the codec).
+ if (StaticPrefs::media_ffmpeg_v4l2_request_enabled() &&
+ NS_SUCCEEDED(InitV4L2RequestDecoder())) {
+ return;
+ }
// VAAPI didn't work or is disabled, so try V4L2 with DRM
if (NS_SUCCEEDED(InitV4L2Decoder())) {
return;
@@ -2046,6 +2155,11 @@
if (IsHardwareAccelerated()) {
mLib->av_buffer_unref(&mVAAPIDeviceContext);
}
+ // firefox-fourier: release the DRM hwdevice ctx for the v4l2_request
+ // hwaccel. Always safe — av_buffer_unref no-ops on a null pointer.
+ if (mDRMDeviceContext) {
+ mLib->av_buffer_unref(&mDRMDeviceContext);
+ }
#endif
#ifdef MOZ_ENABLE_D3D11VA
if (IsHardwareAccelerated()) {
@@ -0,0 +1,34 @@
From: Markus Fritsche <mfritsche@reauktion.de>
Subject: [PATCH 4/4] modules/libpref: add media.ffmpeg.v4l2-request.enabled
Date: 2026-04-27
Background
----------
Toggle for the V4L2 stateless (request API) decode path introduced
in patch 3/4. Defaults on for Linux, mirroring the
`media.ffmpeg.vaapi.enabled`-style shape. Users can flip to false to
force the existing stateful `InitV4L2Decoder` (or VAAPI / software
fallbacks) without rebuilding.
Bug 1969297.
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -12159,6 +12159,16 @@
type: uint32_t
value: 2
mirror: once
+
+# firefox-fourier: route V4L2 stateless (request API) decode through
+# libavcodec's v4l2_request hwaccel (AV_HWDEVICE_TYPE_DRM). Required
+# for mainline-Linux Rockchip rkvdec / hantro / rkvdec2. On stateful
+# boards (Pi4 / vendor MPP) the codec's hw_configs lacks a DRM entry
+# and the path silently falls back to InitV4L2Decoder.
+- name: media.ffmpeg.v4l2-request.enabled
+ type: RelaxedAtomicBool
+ value: true
+ mirror: always
#endif # MOZ_WIDGET_GTK
# Set to true in marionette tests to disable the sanity test