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.
22 KiB
chromium-fourier — first-build status (2026-04-26 00:42 UTC)
Where we are
Build environment: chromium-builder LXD container on boltzmann
(8 cores, 28 GB RAM cap, 824 GB NVMe, Beryllium OS rkr3 host kernel).
Source: chromium-147.0.7727.116 release tarball extracted at
/build/chromium/src (25 GB extracted).
gn gen out/Default succeeds with our 7Ji-style args
(use_v4l2_codec=true use_v4lplugin=true use_linux_v4l2_only=true use_vaapi=false, system toolchain via unbundle:default, system clang
at /usr/bin/clang, version-symlink /usr/lib/clang/23 →
/usr/lib/clang/22, compiler-rt-adjust-paths style suffix patch
manually applied). 28057 targets generated.
ninja -C out/Default chrome fails immediately with two distinct walls:
Wall 1 — clang version mismatch (chromium 147 ↔ Arch clang 22)
Chromium 147's compile flags include
-fno-lifetime-dse and
-fsanitize-ignore-for-ubsan-feature=array-bounds. Both are clang 23+
features. Arch Linux ARM ships clang 22.1.3 (extra repo). Every
single C++ compile fails with clang++: error: unknown argument.
Resolutions, in order of effort:
- (a) Wait for Arch ARM to bump clang to 23. Tracking package upstream — happens whenever LLVM 23 lands in extra. Days to weeks.
- (b) Use chromium's bundled clang via
tools/clang/scripts/update.py. That hits the same CIPD/gs:// "linux-arm64 isn't a first-class target" issue we saw withgclient syncearlier — chromium's clang prebuilts are x86_64-only for many platforms. - (c) Fork an older chromium (e.g., 132 or 138) that compiles cleanly with clang 22. 7Ji's chromium-mpp PKGBUILD targets 132 and builds clean on Arch ARM today. Loses 15 versions of upstream chromium evolution but ships fast.
- (d) Patch chromium 147 to drop the offending flags
(
build/config/compiler/BUILD.gnhas the cflags lists). 50–200 line patch, brittle across version bumps but tractable. Fights every rebase.
Wall 2 — bundled x86_64 esbuild under qemu
After Wall 1 (or independently for Action targets):
qemu-x86_64-static: Could not open '/lib64/ld-linux-x86-64.so.2'
when chromium runs the bundled x86_64 esbuild from
third_party/devtools-frontend/.../scripts/build/typescript/ts_library.py.
Same shape as the bundled node-linux-x64 issue we already fixed (we
symlinked system node into that path). esbuild needs the same
treatment — install system esbuild via npm install -g esbuild and
symlink it into the path chromium expects. Or install qemu-user-static
glibc-x86_64to make the bundled binary actually run.
Wall 2 is much smaller than Wall 1 — a handful of bundled-x86_64 binaries to identify and replace, vs. fundamental clang version mismatch.
What worked
- LXD container provisioning on boltzmann via his recommendation — the host environment is right.
- Tarball-instead-of-gclient approach — sidesteps CIPD-doesn't-have- linux-arm64 problem for source acquisition, leaves only a few bundled binary issues at build time.
- Wall 1 / Wall 2 are both identifiable and bounded. We're past the "is this even doable" phase; this is now down to grinding the patches.
Options — needs your call
- Grind through Wall 1 with patches — patch
build/config/compiler/BUILD.gnto drop flags clang 22 doesn't know. Iterate per build error. Estimated 5–15 patch-and-retry cycles to compile clean. Then 6–10 h actual build. - Pin to chromium 132 — match 7Ji's known-working version on Arch ARM. Drop our STUDY focus on "current upstream Chromium" and ship a 1-year-old binary. Build should work much sooner.
- Pin to chromium 138 or 140 — middle ground. Likely uses clang 22 features and not 23. Some research needed to find the cutover.
- Use chromium's bundled clang — not viable on linux-arm64 without extensive sysroot setup; same CIPD issue as gclient sync.
- Wait for Arch ARM clang 23 — passive, days-to-weeks horizon.
Recommended (FWIW): start with (3) — find the latest chromium
version that builds clean against clang 22 (probably 138-141 range),
ship that as chromium-fourier, then bump as Arch ARM bumps clang.
That gives us a working browser in a few hours rather than days, on
mainline Linux + Wayland + V4L2 unlock — which is the actual goal.
The "current upstream Chromium" requirement was nice-to-have, not
essential.
State of the build host (preserved)
- Container:
chromium-builderon boltzmann (running, idle) - Source:
/build/chromium/src(extracted tarball, 25 GB) - Build dir:
/build/chromium/src/out/Default(gn-gen'd, no artifacts) - Tools installed: gn, ninja, clang 22, lld, gperf, nodejs (system), rust, qt5/6, all the gtk/wayland/va/v4l deps from the long pacman shopping list
- Patches applied to source:
compiler-rt-adjust-pathsstyle (manual) - Symlinks:
/usr/lib/clang/23→/usr/lib/clang/22,third_party/node/linux/node-linux-x64/bin/node→/usr/bin/node - Service unit history:
chromium-fetch.service(one-shot, succeeded on tarball + extract);chromium-build.service(one-shot, three failed attempts above).
Discard the container and start over with option 2 if you pick that direction; otherwise iterate from current state.
Pivot 2026-04-26 — cross-compile from x86_64
After the analysis above, the framing shifted. The real wall isn't
"Arch ARM clang 22 vs LLVM 23" — Arch x86_64 is also on llvm 22.1.3, no
LLVM 23 anywhere in extra/staging. The flags chromium 147 emits
(-fno-lifetime-dse, -fsanitize-ignore-for-ubsan-feature=array-bounds,
the /usr/lib/clang/23/... path) come from chromium's clang fork,
not upstream LLVM 23. Chromium ships its own LLVM with chromium-specific
passes; the "23" in the path is chromium-internal versioning.
Implication: PKGBUILD'ing clang 23 is the wrong tree. The right tree is
either pin to an older chromium (option 2 above) or cross-compile from
an x86_64 host so chromium's x86_64 bundled clang prebuilt is
reachable and target_cpu="arm64" produces the aarch64 binary cleanly.
Cross-compile sidesteps every wall we hit:
- CIPD has full
linux-amd64prebuilts (the gap waslinux-arm64) - Chromium's bundled clang downloads cleanly on x86_64
- No qemu-x86_64-static dance for tools (host IS x86_64)
tools/clang/scripts/update.pyworks as Google intendsgclient syncworks; no DEPS surgery needed
his provisioned a cross-build host for this on 2026-04-26:
- CT 220
chromium-builder-x86on data, x86_64 Ryzen 7 1700, 14 cores, 32 GiB RAM + 8 GiB swap, 200 GiB ZFS rootfs. - Reach via
mcp__hub-tools__remote_shell host=hertz→ssh root@192.168.88.30(data) →pct exec 220 -- ... builderuser uid 1001, NOPASSWD sudo,DisableSandboxin pacman.conf.
Source fetch started 2026-04-26 05:45 UTC as transient unit
chromium-fetch.service on CT 220. Estimated 1-2 h for fetch --no-history chromium over the LAN. Then tools/clang/scripts/update.py
to install chromium's bundled clang (x86_64 host, arm64 sysroot), then
gn gen with target_cpu="arm64" + cross-compile flags, then build.
The boltzmann chromium-builder LXD container is preserved as fallback
but no longer the active build host. If cross-compile pans out, that
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)
- Rebuild lands → repackage with all four GL libs +
chrome_crashpad_handler+ chrome → ship to ohm. - Validate via
chrome --use-gl=egl --ozone-platform=wayland--enable-features=AcceleratedVideoDecoder(no ANGLE shim) and confirmchrome://gpureportsNative GpuMemoryBuffers: trueandsupports_nv12_gl_native_pixmap=true. Target CPU during 1080p30 H.264 playback: under 30 % combined renderer + gpu. - If (2) passes, declare V1 of chromium-fourier shippable on ohm.
- Add a
chromium-fourierlauncher shim under/usr/bin/that defaults to--use-gl=egl --ozone-platform=wayland. - 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).
- 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/srcpatched with bothenable-v4l2-decoder-default.patchandwayland-allow-direct-egl-gles2.patch(applied directly withpatch -p1mid-rebuild; ninja picks up the mtime change). chromium-rebuild.servicerunning 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.mp4plays 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: S264confirmed 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 5–10× 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)
- Patch 4 lands publicly: bump PKGBUILD
source=andprepare(), commit + tag achromium-fourier-149-r4release ongit@github.com:marfrit/chromium-fourier. - KWin pivot — see
KWIN_PIVOT.md(separate doc) for the plan to identify and patch theglTexImage2D(GL_ALPHA)site, since ohm is the only board on hand and every wayland video client is affected. - Replication on ampere (RK3588, panthor + rkvdec2 + hantro multiplanar) — needs ampere woken; currently DOWN.
- firefox-fourier 150 build —
firefox-fourier-150.0.1-1-aarch64.pkg.tar.zstis built (95 MB on workstation:/tmp/, sha256 acbf1870…), pending fresnel power-on for V4L2 stateless validation on RK3399.