diff --git a/phase2_iter1_findings.md b/phase2_iter1_findings.md index 4324a70..a929c07 100644 --- a/phase2_iter1_findings.md +++ b/phase2_iter1_findings.md @@ -77,7 +77,9 @@ So the green frame is **not caused by mpv or ffmpeg's descriptor construction**. 2. **KWin's wl_dmabuf import logic deduplicates the dup'd fds incorrectly.** If KWin sees two fds and detects (via `dma_buf_id` or kcmp(2)) that they're the same backing object, but then mishandles the per-plane offsets, this'd produce green. Testable: read KWin source `src/wayland/linuxdmabufv1clientbuffer.cpp` and compositor backend's EGL import path. -3. **hantro kernel driver exports a `dma_buf` with `size` < full allocation.** If hantro caps the exported fd's mappable size to just the Y plane (2,088,960 bytes), offset 2,088,960 is past EOF — read fails silently, returns zeros. Testable: `ssh ohm "fcntl(F_GETSIZE) on EXPBUF fd"` or just `lseek(fd, 0, SEEK_END)` in a small probe. +3. ~~**hantro kernel driver exports a `dma_buf` with `size` < full allocation.**~~ **RULED OUT 2026-05-08** by `/tmp/expbuf_probe.c` on ohm. Driver `hantro-vpu` on `rk3568-vpu-dec` reports `CAPTURE: NV12 1920x1088 num_planes=1 sizeimage=3655712`; `VIDIOC_EXPBUF` yields fd whose `lseek(fd, 0, SEEK_END) = 3,657,728` (page-rounded up from 3,655,712). Offset 2,088,960 (plane 1 base) is firmly inside the exported size. Kernel is innocent. + + Side observation worth recording: `sizeimage = 3,655,712` is bigger than naïve NV12's 1920×1088×1.5 = 3,133,440. The 522,272-byte excess sits **past** the UV plane (Y at [0, 2,088,960), UV at [2,088,960, 3,133,440), trailing padding at [3,133,440, 3,655,712)). On Rockchip codecs that tail commonly holds per-frame motion-vector / decoder-context data. Confirms ffmpeg's hardcoded `planes[1].offset = pitch*height = 2,088,960` is correct. 4. **kwin-fourier 0001 still has effect we missed.** Even though we ruled out kwin-fourier as a compositor-replacement A/B, that test was on an earlier kernel/Mesa combo. Worth verifying the test environment is fully reset. @@ -93,6 +95,7 @@ d. **Update `marfrit/dmabuf-modifier-triage#1`** with this revised analysis. The ## Status -- iter1 phase 2 ongoing. Pivot from "fix is in mpv" to "real layer is unclear, need more diagnostics." +- iter1 phase 2 closed 2026-05-08. Hypotheses 3 (hantro size cap) and ad-hoc 5 (offset-mismatch from sizeimage surprise) both ruled out via `/tmp/expbuf_probe.c` runtime measurement on ohm. Real layer narrowed to hypothesis 1 (panfrost EGL_dma_buf_import for non-zero offset) and hypothesis 2 (KWin wl_dmabuf import). Hypothesis 4 still latent. - Acceptance criterion (`screenshots/frame10_expected.png`) is unchanged. -- Delivery vehicle (`mpv-fourier-1:0.41.0-8`) is still the right shipping path **if** the fix turns out to be a defensive workaround in mpv. Otherwise the patch lands in ffmpeg-v4l2-request-fourier (unlikely per this analysis), KWin, kernel hantro, or a Mesa-panfrost user-mode driver patch. +- Delivery vehicle (`mpv-fourier-1:0.41.0-8`) is still the right shipping path **if** the fix turns out to be a defensive workaround in mpv. With kernel + ffmpeg + mpv all exonerated by source-read + runtime probe, the most likely landing layers are now Mesa-panfrost (`vulkan-panfrost` package — already in marfrit) or KWin (`kwin-fourier` package — already in marfrit). +- Next probe: hypothesis 2 source-read of KWin (cheaper, ~30 min, no hardware) before hypothesis 1 EGL importer harness (~1-2h C code). diff --git a/probes/expbuf_probe.c b/probes/expbuf_probe.c new file mode 100644 index 0000000..28f74fc --- /dev/null +++ b/probes/expbuf_probe.c @@ -0,0 +1,124 @@ +/* + * iter1 hypothesis 3 probe — lseek(SEEK_END) on a hantro CAPTURE EXPBUF fd + * + * Question: does hantro export the dma_buf with size = full sizeimage + * (3,655,712 = 1920*1088*1.5) or capped to Y-plane only (2,088,960 = 1920*1088)? + * + * Decides hypothesis 3 from ~/src/dmabuf-modifier-triage/phase2_iter1_findings.md. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEV "/dev/video1" +#define WIDTH 1920 +#define HEIGHT 1088 + +static int xioctl(int fd, unsigned long req, void *arg, const char *name) +{ + int r = ioctl(fd, req, arg); + if (r < 0) + fprintf(stderr, "ioctl %s: %s (errno=%d)\n", name, strerror(errno), errno); + return r; +} + +int main(void) +{ + int fd = open(DEV, O_RDWR); + if (fd < 0) { perror("open " DEV); return 1; } + + struct v4l2_capability cap = {0}; + if (xioctl(fd, VIDIOC_QUERYCAP, &cap, "QUERYCAP") < 0) return 2; + printf("driver: %s\n", cap.driver); + printf("card: %s\n", cap.card); + printf("bus_info: %s\n", cap.bus_info); + printf("caps: 0x%08x (M2M_MPLANE=%d)\n", + cap.device_caps, + !!(cap.device_caps & V4L2_CAP_VIDEO_M2M_MPLANE)); + + /* Set OUTPUT (compressed bitstream) format first. + * Hantro derives CAPTURE size from OUTPUT pixel format + dimensions. + */ + struct v4l2_format ofmt = {0}; + ofmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + ofmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_H264_SLICE; + ofmt.fmt.pix_mp.width = WIDTH; + ofmt.fmt.pix_mp.height = HEIGHT; + ofmt.fmt.pix_mp.num_planes = 1; + ofmt.fmt.pix_mp.plane_fmt[0].sizeimage = 1024*1024; /* compressed buf size */ + if (xioctl(fd, VIDIOC_S_FMT, &ofmt, "S_FMT(OUTPUT)") < 0) return 3; + + /* Now CAPTURE format. Driver should default to NV12 1920x1088. */ + struct v4l2_format cfmt = {0}; + cfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + if (xioctl(fd, VIDIOC_G_FMT, &cfmt, "G_FMT(CAPTURE)") < 0) return 4; + printf("CAPTURE: pixfmt=0x%08x (%c%c%c%c) %ux%u num_planes=%u\n", + cfmt.fmt.pix_mp.pixelformat, + cfmt.fmt.pix_mp.pixelformat & 0xff, + (cfmt.fmt.pix_mp.pixelformat >> 8) & 0xff, + (cfmt.fmt.pix_mp.pixelformat >> 16) & 0xff, + (cfmt.fmt.pix_mp.pixelformat >> 24) & 0xff, + cfmt.fmt.pix_mp.width, cfmt.fmt.pix_mp.height, + cfmt.fmt.pix_mp.num_planes); + for (unsigned i = 0; i < cfmt.fmt.pix_mp.num_planes; ++i) { + printf(" plane[%u]: sizeimage=%u bytesperline=%u\n", i, + cfmt.fmt.pix_mp.plane_fmt[i].sizeimage, + cfmt.fmt.pix_mp.plane_fmt[i].bytesperline); + } + + /* Try setting NV12 explicitly in case driver wants confirmation. */ + cfmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12; + if (xioctl(fd, VIDIOC_S_FMT, &cfmt, "S_FMT(CAPTURE NV12)") < 0) return 5; + + /* REQBUFS CAPTURE — DMABUF memory so we can EXPBUF later. */ + struct v4l2_requestbuffers rb = {0}; + rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + rb.memory = V4L2_MEMORY_MMAP; + rb.count = 4; + if (xioctl(fd, VIDIOC_REQBUFS, &rb, "REQBUFS(CAPTURE)") < 0) return 6; + printf("REQBUFS allocated %u buffers\n", rb.count); + + /* QUERYBUF buffer 0 to learn its plane layout. */ + struct v4l2_buffer qb = {0}; + struct v4l2_plane planes[VIDEO_MAX_PLANES] = {0}; + qb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + qb.memory = V4L2_MEMORY_MMAP; + qb.index = 0; + qb.length = VIDEO_MAX_PLANES; + qb.m.planes = planes; + if (xioctl(fd, VIDIOC_QUERYBUF, &qb, "QUERYBUF") < 0) return 7; + printf("QUERYBUF buf 0: %u planes\n", qb.length); + for (unsigned i = 0; i < qb.length; ++i) { + printf(" plane[%u]: length=%u data_offset=%u m.mem_offset=0x%x\n", + i, planes[i].length, planes[i].data_offset, planes[i].m.mem_offset); + } + + /* THE PROBE: VIDIOC_EXPBUF + lseek(SEEK_END). */ + for (unsigned i = 0; i < qb.length; ++i) { + struct v4l2_exportbuffer eb = {0}; + eb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + eb.index = 0; + eb.plane = i; + eb.flags = O_CLOEXEC | O_RDWR; + if (xioctl(fd, VIDIOC_EXPBUF, &eb, "EXPBUF") < 0) { + fprintf(stderr, " (plane %u EXPBUF failed)\n", i); + continue; + } + off_t end = lseek(eb.fd, 0, SEEK_END); + printf("\n *** plane[%u] EXPBUF fd=%d lseek(SEEK_END)=%lld ***\n", + i, eb.fd, (long long)end); + printf(" Y-plane-only would be: %d\n", WIDTH * HEIGHT); + printf(" full sizeimage would be: %d\n", WIDTH * HEIGHT * 3 / 2); + printf(" QUERYBUF reported: %u\n", planes[i].length); + close(eb.fd); + } + + close(fd); + return 0; +}