mpv vo_dmabuf_wayland: plane-semantics mismatch — different fds per plane combined with single-allocation offset for plane 1 (root cause of the green on ohm) #1
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
WAYLAND_DEBUG=1 capture of
mpv --hwdec=v4l2request --vo=dmabuf-waylandagainst hantro-decoded NV12 H.264 on ohm (RK3566 / Mali-G52 / kernel 7.0.danctnix1-1-pinetab2-danctnix-besser, KWin 6.6.4 OpenGL backend / Mesa-panfrost 26.0.6) shows the mpv-side wl_dmabuf protocol message is internally inconsistent. Thezwp_linux_buffer_params_v1.add()calls combine per-plane fds (V4L2 MPLANE export semantics) with a single-allocation offset for plane 1 — KWin imports plane 1 from the wrong byte address and reads zeros for the UV chroma plane, producing the dark-green frames the user sees.Discovered as the layer-isolation conclusion of the Phase 0 work in this repo. All other suspects (kwin-fourier 0001 patch, Mesa 26.0.5 vs 26.0.6, libva, decoder content correctness, color tagging, Wayland/KWin generally, kernel 6.19.10 vs 7.0, KWin Vulkan vs OpenGL backend) ruled out by A/B testing. See
phase0_findings.mdfor the elimination ladder.Trace (verbatim from ohm session, 2026-05-08)
All subsequent frames follow the same pattern: plane 0 uses fd N at offset 0, plane 1 uses fd N+1 at offset 2088960.
fd 41and plane 1 usesfd 42— different file descriptors. V4L2 MPLANE EXPBUF returns one fd per plane, so fd 41 is the Y plane's fd and fd 42 is the UV plane's fd. Both can map the same backing memory but at byte offset 0.add(fd 42, plane=1, offset=2088960, ...): KWin imports plane 1 from fd 42 starting at offset 2088960. If fd 42 maps only the UV plane (size ~1MB for 1920×1080 NV12), offset 2088960 is past EOF → KWin reads zeros for UV → all-zero NV12 buffer.Why this produces dark green specifically
NV12 chroma is biased: U=128 + V=128 = "no color" (luma-only grayscale). When U=V=0 (instead of 128):
(0, 70, 0)— dark green.Matches the user's observed symptom exactly.
Why mpv
--vo=gpudoesn't trigger thismpv's
vo=gpuimports the dmabuf via libva'svaExportSurfaceHandleor ffmpeg'sav_hwframe_transfer_datadirectly into mpv's own EGL context. Those APIs return the correct per-plane structure (each plane fd maps the corresponding plane at offset 0). mpv-side EGL_EXT_image_dma_buf_import then samples both planes correctly. The bug only manifests in mpv's vo_dmabuf_wayland code path.Why mpv
--vo=wlshmdoesn't trigger thiswlshm goes through CPU memcpy into a wl_shm buffer. Bypasses the dmabuf protocol entirely. No plane-semantics translation issue.
Where the bug lives
mpv's
video/out/vo_dmabuf_wayland.c(or its drm_prime helper, e.g.video/out/hwdec/dmabuf_interop_pl.cif libplacebo is involved). The translation from the producer's plane info (AVDRMFrameDescriptorfor ffmpeg path,VADRMPRIMESurfaceDescriptorfor libva path) to wl_dmabuf.add()calls is mishandling one of two cases:Case A: producer uses single-fd-multiple-offsets convention
The producer's plane info has all planes referencing the same fd, with offsets pointing to where each plane lives in that one allocation. mpv translation should pass
add(producer.fd, i, producer.plane[i].offset, producer.plane[i].pitch, ...)for every plane — same fd repeated, with offsets.Case B: producer uses multi-fd-zero-offset convention
The producer's plane info has each plane with its own fd, each fd already pointing at the start of its own plane's allocation. mpv translation should pass
add(producer.plane[i].fd, i, 0, producer.plane[i].pitch, ...)for every plane — different fds, offset always 0.The trace shows mpv mixes both: different fds (Case B) and non-zero offset for plane 1 (Case A). One of the two reads from the producer is wrong — either the plane fds are being assigned right but offsets are wrong, or the offsets are being assigned right but fds should all be the same.
Suggested fix path
vo_dmabuf_wayland.cextracts plane fd + offset fromAVDRMFrameDescriptor(ffmpeg path) andVADRMPRIMESurfaceDescriptor(libva path). Check whether mpv is reading the wrong field.add(fd 41, 0, 0, 1920, 0, 0); add(fd 41, 1, 2088960, 1920, 0, 0)— same fd for both planesadd(fd 41, 0, 0, 1920, 0, 0); add(fd 42, 1, 0, 1920, 0, 0)— different fds, both offset 0Decisive verifier (deferred — needs libva-multiplanar iter9 fix first)
Re-run the WAYLAND_DEBUG capture with
--hwdec=vaapi(libva path) oncelibva-v4l2-request-fourier#1is fixed and the cap_pool/REQBUFS cascade no longer prevents libva playback. If the libva path produces the same wrong .add() pattern → bug is in mpv VO. If it produces a different wrong pattern → bug is in the producer (libva or ffmpeg V4L2 hwaccel respectively).Cross-references
~/src/dmabuf-modifier-triage/phase0_findings.md.Environment
Reference screenshot — frame 10
What the bug looks like on ohm. Captured 2026-05-08 via
mpv --hwdec=v4l2request --vo=dmabuf-wayland --pause --start=00:00:00.42 --fullscreen fourier-test/bbb_1080p30_h264.mp4+spectacle -b -f -n.Uniform dark green ≈ RGB(0, 75, 0) — black bars top/bottom are letterboxing from fullscreen presentation on PineTab2's 1280×800 display vs the 1920×1080 / 16:9 source, not part of the bug. The exact green hue matches the predicted output of an all-zero NV12 buffer through BT.601/709 → RGB conversion (Y=0 + U=0 + V=0 ≈ RGB(0,70,0)), confirming the diagnosis: KWin reads UV plane past-EOF on fd 42, returns zeros for chroma.
Committed at
screenshots/frame10_dmabuf_green.png(commite293078).Phase 2 source-read overturns the original root-cause analysis (2026-05-08)
When the iter1 phase 2 source-read started against mpv 0.41.0 + Kwiboo's ffmpeg fork at commit
b57fbbe(the_commitpin inmarfrit-packages/arch/ffmpeg-v4l2-request-fourier/PKGBUILD), the original conclusion in this issue body — that mpv mixes per-plane fds with a single-allocation offset for plane 1 — did not survive contact with the actual code. The earlier diagnosis was wrong. Recording the revision here so future readers don't act on it.What the source actually says
mpv's
video/out/vo_dmabuf_wayland.c::drmprime_dmabuf_importer(lines 250-277):No
dup(), no rewriting. mpv passes the producer'sAVDRMFrameDescriptorthrough unchanged.Kwiboo's
libavutil/hwcontext_v4l2request.c::v4l2request_set_drm_descriptor(lines 138-198):Per this code, mpv should produce two
.add()calls with identical fd values — both pull fromdesc->objects[0].fd.What the runtime probe says
v4l2-ctl --get-fmt-video-mplane-capon ohm/dev/video1:strace -e trace=ioctl mpv ...confirms ffmpeg does oneVIDIOC_EXPBUFper CAPTURE buffer, returning a single fd.nb_objects = 1matches.Why the WAYLAND_DEBUG trace shows different fds
The original analysis read the trace literally:
Most likely explanation: libwayland's
wl_closure_marshaldup_cloexec's the fd at protocol-marshal time, andWAYLAND_DEBUGprints the post-dup value. Both.add()calls pass the same source fd into libwayland; libwayland creates two dups (consecutive integers in the fd table), sends each viaSCM_RIGHTSto the compositor, and prints the dup'd values. The two dups refer to the same underlyingdma_bufobject.This means the wl_dmabuf message is not internally inconsistent:
objects[0].size = 3655712is the full allocation size — well above plane 1's offsetNothing in the producer chain looks wrong by static analysis.
Where the bug actually lives — new hypothesis space
EGL_EXT_image_dma_buf_import_modifiersregression for NV12 with non-zero plane offset. The driver may sample plane 1 from offset 0 of the imported fd instead of offset 2088960, returning zero-fill UV. Testable: a minimal EGL importer C program against a known NV12 dmabuf with offsets, read back viaglReadPixels.linux-dmabuf-v1import deduplicates the dup'd fds incorrectly. KWin may detect (viakcmp(2)ordma_buf_get_unique_idor similar) that the two received fds reference the same dma_buf, then mishandle the per-plane offsets. Source path:src/wayland/linuxdmabufv1clientbuffer.cppin KDE Plasma 6 + the OpenGL compositor backend's EGL import.dma_bufsize to just the Y plane (2088960 bytes). If true, KWin's read at offset 2088960 falls past EOF → silent zero-fill → green frame. Testable in 30 minutes with a small C program on ohm:lseek(EXPBUF_fd, 0, SEEK_END)and check whether it returns 2,088,960 or 3,655,712.Recommended decisive next probe
Hypothesis 3 (kernel-side fd size) is the cheapest to falsify. If
lseekon the EXPBUF fd reports 3,655,712 (full alloc), drop to 1/2/4. If it reports 2,088,960 (Y plane only), the bug is in hantro's kerneldma_bufexport code and the fix lands in the kernel driver, not in any user-space component.Cross-references
~/src/dmabuf-modifier-triage/phase2_iter1_findings.md(commiteddd9efon origin).screenshots/frame10_expected.pngis unchanged regardless of which layer turns out to be at fault.mpv-fourier-1:0.41.0-8) is still right if the fix turns out to be a defensive workaround in mpv. Otherwise the patch lands in ffmpeg / KWin / Mesa-panfrost / kernel hantro per which hypothesis fires.iter1 phase 2 — hypothesis 3 RULED OUT (probe run 2026-05-08 on ohm)
Wrote
/tmp/expbuf_probe.c— minimal V4L2 harness that opens/dev/video1, sets OUTPUT format toV4L2_PIX_FMT_H264_SLICE1920×1088, retrieves CAPTURE format, REQBUFS 4 buffers, thenVIDIOC_EXPBUFon buffer 0 plane 0 andlseek(fd, 0, SEEK_END).Result:
Kernel exports the dma_buf at the full sizeimage (3,655,712), page-rounded up to 3,657,728. Offset 2,088,960 (where ffmpeg places UV plane) is well inside. Hantro is innocent.
Bonus observation, also ruling out a candidate hypothesis 5 I was about to file: the reported sizeimage is bigger than naïve NV12's 1920×1088×1.5 = 3,133,440. Difference is 522,272 bytes of trailing padding. But the math works out cleanly:
Meaning ffmpeg's hardcoded
planes[1].offset = pitch*height = 2,088,960is correct — UV starts exactly there.Remaining hypothesis space:
Next probe: hypothesis 2 source-read of KWin 6.6.4
src/wayland/linuxdmabufv1clientbuffer.cpp+ EGL backend import path (~30 min, no hardware). If that doesn't show a clear bug, fall back to writing the EGL importer harness for hypothesis 1 (~1-2h).iter1 phase 2 — hypothesis 2 RULED OUT (source-read of KWin 6.6.4 on 2026-05-08)
Shallow-cloned KWin 6.6.4 to
references/kwin-6.6.4/. Read paths:src/wayland/linuxdmabufv1clientbuffer.cpp— wl_linux_dmabuf protocol handlersrc/opengl/eglbackend.cpp—testImportBuffer()decision logicsrc/opengl/egldisplay.cpp— actualeglCreateImagecallNo fd dedup, no offset transformation:
LinuxDmaBufParamsV1::zwp_linux_buffer_params_v1_add(line 106) stores each fd/offset/pitch inm_attrs.fd[i],m_attrs.offset[i],m_attrs.pitch[i]per plane index. No comparison of fds across calls. Two.add()invocations with two dup'd-same-fd values produce two independent slots.LinuxDmaBufParamsV1::test()(line 233) doeslseek(fd[i], 0, SEEK_END)per plane and validatesoffset[i] < size,offset[i] + pitch[i] <= size, modifier consistency. With our exported size 3,657,728 vs offset[1]=2,088,960 + pitch=1920, all checks pass.EglDisplay::importDmaBufAsImage(both forms — combined multi-plane and per-plane single-format) passesdmabuf.fd[i],dmabuf.offset[i],dmabuf.pitch[i]straight toeglCreateImage(EGL_LINUX_DMA_BUF_EXT, ...)with no transformation.EglBackend::testImportBuffer(eglbackend.cpp:338) picks between two paths:EGL_DMA_BUF_PLANE0_*+EGL_DMA_BUF_PLANE1_*attribs. Used if NV12+LINEAR is innonExternalOnlySupportedDrmFormats().Either path forwards
offset = 2,088,960for plane 1 to the driver. KWin is innocent.Live hypothesis is now solely H1: panfrost's
EGL_DMA_BUF_PLANE*_OFFSET_EXThandling for LINEAR NV12 (or per-plane RG88 with non-zero offset).The per-plane path on Mali-G52 is most likely (Mesa-panfrost typically reports YUV+LINEAR as external-only) — meaning UV gets imported as a stand-alone RG88 1920×544 EGL_image with
PLANE0_OFFSET = 2,088,960. If panfrost's KMS/EGL Mesa code samples from offset 0 of the underlying fd instead of honoring 2,088,960, UV would read zero-fill = green frame.Next probe: minimal EGL importer harness on ohm. Synthesize a known NV12 buffer in CPU memory (Y = 0x80 fill, UV = 0x40/0xC0 alternating), write to a udmabuf, do
eglCreateImagewith PLANE0_OFFSET=2088960 + format RG88, render via fullscreen quad withsamplerExternalOES,glReadPixels. If output reads zeros, panfrost confirmed culprit. ~1-2h. Then file an upstream Mesa bug + handle in vulkan-panfrost or kwin-fourier.iter1 phase 2 — Mesa-panfrost source-read + green-color math (2026-05-08)
Shallow-cloned Mesa 26.0.6 (matching ohm's installed
mesa 1:26.0.6-1andvulkan-panfrost 1:26.0.6-1). Read the EGL/DRI/gallium/panfrost stack along the path KWin's per-plane import takes.Path through Mesa for our case:
pan_screen.c:443reportsexternal_only=truefor any YUV format (including NV12+LINEAR). KWin'stestImportBufferthus takes the per-plane path (Y as R8, UV as DRM_FORMAT_GR88).loader_dri_helper.c:43mapsDRM_FORMAT_GR88 ↔ PIPE_FORMAT_RG88_UNORM— sampling returns byte 0 as.r, byte 1 as.g. NV12 chroma byte 0=Cb, byte 1=Cr. So.r=U=Cb, .g=V=Cr.result = vec4(sampler.x, sampler1.rg, 1.0)thenyuvToRgb * result.rgba. Soresult.y=U,result.z=V. Matches what Mesa returns. Chroma swap is NOT the bug.pan_resource.c:354-358captureswhandle->offsetintoexplicit_layout.offset_B.pan_mod.c:663-667(linear modifier slice-init) honorslayout_constraints->offset_Bdirectly with only an alignment check; 2,088,960 is page-aligned so passes.pan_texture.c:361,561,660,773,817set the texture descriptor's GPU base toplane->base + slayout->offset_B. Sampling reads frombo_gpu + 2,088,960. ✓Conclusion of source-read: panfrost looks innocent at the offset-handling layer. KWin looks innocent. Format mapping looks consistent. H1 demoted from leading candidate to less-likely (cannot be conclusively ruled out without runtime EGL probe but the obvious places are clean).
Green-color math points elsewhere:
BT.601 limited-range YUV(0,0,0) → RGB conversion:
→ RGB(0, 135, 0) — exactly the green tone in
frame10_dmabuf_green.png.That means panfrost is reading zero-fill bytes despite hantro having written real YUV data. Not an offset bug, not a format bug — a synchronization or cache-coherency bug.
New leading hypotheses:
H6 — DMA cache coherency between hantro VPU and Mali GPU: V4L2 doesn't attach implicit fences to CAPTURE buffers on DQBUF (this is exactly the gap our
vb2_dma_resvRFC addresses upstream — see~/.claude/projects/-home-mfritsche-src-fourier/memory/project_vb2_dma_resv_v2_state.md). Mali begins sampling before hantro's writes have flushed to coherent memory. Mali sees zero-fill backing.H7 — Panfrost dma_buf import path lacks GPU-side cache invalidation at attach/map time. Even after data lands in DRAM, Mali's MMU/cache may serve stale reads.
Next probe options (ranked):
Cache-sync ioctl workaround test (~30 min cost, decisive on H6/H7): patch
mpv-fourier'svo_dmabuf_wayland.cto callDMA_BUF_IOCTL_SYNC(DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ)on each EXPBUF fd before submitting to KWin. If green goes away → H6/H7 confirmed, workaround is shippable, root-cause kernel bug filed separately.EGL importer harness (~1-2h): synthesize known NV12 buffer in CPU memory → udmabuf → eglCreateImage with PLANE0_OFFSET=2088960 → render → glReadPixels. Decides H1 definitively. Synthesizing in CPU memory rules out the producer-side cache-coherency variable.
Mesa debug log (~15 min):
MESA_DEBUG=1 PAN_MESA_DEBUG=trace,bofor cheap visibility into panfrost's actual buffer/format/offset choices.Leaning toward option 1 — cheapest probe AND a viable workaround if it works.
iter1 phase 3 — H6 RULED OUT (test run 2026-05-08)
Patch deployed:
mpv-fourier-1:0.41.0-9addsDMA_BUF_IOCTL_SYNC(SYNC_START|SYNC_RW)+ matchingSYNC_ENDon each EXPBUF fd in bothvaapi_dmabuf_importerANDdrmprime_dmabuf_importer(the patch covers VAAPI and DRMPrime paths symmetrically). Built via Gitea Actions run #80, installed on ohm.Test:
mpv --hwdec=v4l2request --vo=dmabuf-wayland --fullscreen --pause --start=00:00:00.42 --quiet ~/fourier-test/bbb_1080p30_h264.mp4v4l2-request: cap_pool_init: 24 slots readyx3VO: [dmabuf-wayland] 1920x1080 vaapi[nv12]Result: md5
c8c8e9b88521a0069f709d483451c3d4— byte-identical to baselineframe10_dmabuf_green.png. Visual inspection confirms: same solid dark green ≈ RGB(0, 77, 0) (BT.709 limited-range YUV(0,0,0)).Conclusion: the userspace
DMA_BUF_IOCTL_SYNCcache-coherency workaround does not fix the green. H6 is dead. Either hantro'sdma_buf_ops->begin_cpu_accessis a no-op for the buffer type used by the V4L2 stateless decoder (likely on Rockchip), OR the gap is on the GPU consumer side where CPU cache state is irrelevant.Critical phase-3 observation:
--hwdec=v4l2request --vo=gpu(CPU-mmap → glTexSubImage2D upload path) is known-working — renders correctly. So the buffer DOES contain valid YUV data, the CPU CAN read it, and the decoder is producing legit content. Only the zero-copy dma_buf-to-Mali path renders zeros. This rules out "data isn't there" entirely and concentrates the hypothesis on the dma_buf → Mali GPU import/translation step itself.Live hypothesis space narrows further:
H1, H2, H3, H5, H6all ruled outNext probe options ranked:
drivers/gpu/drm/panfrost/panfrost_gem.c,panfrost_gem_prime_import_sg_table, MMU mapping for cache attributes) — ~45 min, no hardwareudmabuf— distinguishes "hantro-allocated buffer specifically" vs "general panfrost dma_buf import bug" (~1-2h)MESA_DEBUG=verbose PAN_MESA_DEBUG=sync,tracelog of the buggy run — cheap recon (~15 min)Leaning #1 first (cheapest source-read), #2 if #1 turns up nothing.
mpv-fourier-1:0.41.0-9 keeps the no-op patch installed (harmless). Will be replaced or removed in the next iteration.
iter1 phase 4 — H7 CONFIRMED via panfrost kernel source-read (2026-05-08)
Read the panfrost kernel driver at Linux 6.12 (
~/src/linux-rfc/drivers/gpu/drm/panfrost/). Smoking gun chain:panfrost_gem.c:262—obj->base.map_wc = !pfdev->coherent;— sets write-combine (uncached) CPU mapping when device isn't coherent. Applies to imports too (drm_gem_shmem_prime_import_sg_tablecalls back intopanfrost_gem_create_object).panfrost_drv.c:625—pfdev->coherent = device_get_dma_attr(&pdev->dev) == DEV_DMA_COHERENT;— i.e., from DTdma-coherentproperty on the panfrost node.On ohm RK3566 PineTab2 besser-7.0: NO
dma-coherentproperty anywhere in/sys/firmware/devicetree/base/(verified). Sopfdev->coherent = false.panfrost_mmu.c:330—int prot = IOMMU_READ | IOMMU_WRITE;— NOIOMMU_CACHE. Imported BOs are mapped into Mali's IOMMU as non-snooping. Mali reads directly from DRAM, bypassing CPU caches entirely.KWin caches EGL_images per-fd in
m_importedBuffers(eglbackend.cpp:282). For each of the rotating ~15 buffers, EGL_image is created once and reused. The only cache sync is the one atdma_buf_map_attachmenttime during initial import.No per-frame cache sync mechanism exists. V4L2 doesn't attach
dma_resvfences to CAPTURE buffers on DQBUF.Architectural picture is now clear: hantro writes through CPU L1/L2/L3 caches → Mali reads through non-snooping IOMMU → DRAM-direct → sees stale or zero-fill data. Result: green frames on every frame after the first cache-flushed one.
Counter-validation that confirms the diagnosis:
--vo=gpuworks correctly. CPU-mmap of dma_buf triggers cache sync viabegin_cpu_access. ThenglTexSubImage2Dcopies to a Mali-private (cached/coherent) BO. Per-frame implicit cache sync.--vo=dmabuf-waylandfails. Zero-copy import → no per-frame sync.Why DMA_BUF_IOCTL_SYNC (phase 3) didn't help: it invokes
begin/end_cpu_access— CPU-side cache management. Doesn't propagate to GPU IOMMU mapping. Mali still reads through its non-snooping mapping.ROOT CAUSE
The dmabuf-wayland green is structurally the same bug that our
vb2_dma_resvRFC v2 addresses upstream on linux-media. With V4L2 attaching adma_resvfence on CAPTURE DQBUF, mesa-panfrost's implicit fence-wait at sample time will block until hantro signals — and the fence signaling enforces cache writeback. The RFC v2 is the fix for this bug.Proposed iter2 / phase 5 path
Build
linux-pinetab2-danctnix-besser7.0 with thevb2_dma_resvRFC v2 patches applied. Install on ohm. Retest the green-frame case. If green goes away:linux-pinetab2-fourier(or similar)Estimated cost: ~2-3 hours (rebase patches against besser-7.0 → distcc kernel build → reboot ohm → retest).
H7 alive sub-cases (probably moot now, recorded for completeness)
If the kernel-rebuild path is blocked, fallback validation paths:
Status of mpv-fourier-1:0.41.0-9
The harmless no-op DMA_BUF_IOCTL_SYNC patch stays installed. When the kernel-rebuild path works, the patch gets reverted in the next mpv-fourier rev.
Still reproducible on ohm, 2026-05-18. Keeping this issue open — it's the canonical home for the root-cause investigation. Closing the sibling symptom report marfrit/libva-multiplanar#1 as duplicate, pointing here.
Re-verification (mpv-fourier 1:0.41.0-10, libva-v4l2-request-fourier iter39
cf8cd9d)Byte-identical to the broken pattern in the original report:
The bug lives in mpv's
vo_dmabuf_wayland.cplane-info translation. mpv upstream has not fixed this between 2026-05-08 and 2026-05-18 (mpv-fourier still tracks 0.41.0).Suggested next steps (in priority order)
video/out/vo_dmabuf_wayland.c(or the drm_prime helper) that fixes the producer-side read — see "Suggested fix path" §1 above. Ship asmpv-fourierpatch in marfrit-packages.Cross-references unchanged: depends on the producer-side analysis in this issue; gated by neither libva-v4l2-request-fourier#1 (now closed) nor any other fleet issue.
Closing 2026-05-18 — bug is fixed by the cache-sync workaround already shipped in mpv-fourier 0.41.0-10. The plane-semantics diagnosis in the issue body was a misdiagnosis.
Empirical re-verification today
Visual test on ohm with current stack (
mpv-fourier 0.41.0-10,libva-v4l2-request-fourier 1:1.0.0.r361.cf8cd9d-1,ffmpeg-v4l2-request-fourier 2:8.1.r123329.b57fbbe-3):→ Real Big Buck Bunny content displayed (operator visually confirmed, 2026-05-18 ~09:50 UTC). No green frames, no zero UV plane, no protocol rejection.
Why the original diagnosis was wrong
The issue body interpreted this WAYLAND_DEBUG pattern as broken:
…claiming fd 40 and fd 47 are V4L2 per-plane EXPBUF fds (Case B convention) being incorrectly combined with a single-allocation offset (Case A convention).
But strace of mpv on ohm 2026-05-18 shows:
plane=0for every EXPBUF, every CAPTURE buffer index. The hantro driver on RK3566 reportsMPLANEbut exposesnum_planes = 1for NV12 — Y and UV are packed into a single allocation. So FFmpeg'shwcontext_v4l2request::v4l2request_set_drm_descriptorcorrectly sets:planes[0].object_index = 0, offset = 0(Y at start of buffer)planes[1].object_index = 0, offset = pitch × aligned_height = 1920 × 1088 = 2088960(UV at offset)…and BOTH planes reference the same single object (= same fd). The wayland fds 40 and 47 are SCM_RIGHTS dups of that single underlying dmabuf, not separate per-plane fds. KWin's import is structurally correct.
The TRUE bug, fixed earlier: missing implicit-fence on V4L2 CAPTURE DQBUF caused the GPU import to read from physical memory before the producer's writes flushed → all-zero UV → BT.601 limited-range YUV(0,0,0) → RGB(0, 135, 0) = solid dark green. Same class as
reference_dmabuf_resv_blocker(RK3399 hantro CAPTURE all-zero readback). The cache-sync patch issues explicitDMA_BUF_IOCTL_SYNC(SYNC_RW)on each unique import fd beforezwp_linux_buffer_params_v1_add(), invoking the producer driver'sbegin_cpu_access/end_cpu_accesswhich flushes write buffers on ARM SoCs.Sibling marfrit/libva-multiplanar#1 — closed earlier today
That sibling was closed as duplicate-of-this-issue with a (now-incorrect) note saying "still reproducible." It was actually no-longer-reproducible at the time of that comment — I'd only verified the wayland trace pattern, not the visual output. Posting a follow-up correction to the sibling for the record.
Root-cause attribution for future-me
reference_dmabuf_resv_blockerfamily).DMA_BUF_IOCTL_SYNCbefore compositor import (mpv-fourier0001-vo_dmabuf_wayland-explicit-cache-sync-on-import-fd.patch).vb2_dma_resvopt-in producer fences in the kernel (tracked at marfrit/dmabuf-modifier-triage#3, operator-driven upstream work).Closing.