diff --git a/src/surface.c b/src/surface.c index f2df393..f040ce9 100644 --- a/src/surface.c +++ b/src/surface.c @@ -47,7 +47,27 @@ #include "v4l2.h" #include "video.h" -bool SET_FORMAT_OF_OUTPUT_ONCE = false; +/* + * Per-process cache of the OUTPUT format we've set. The previous + * SET_FORMAT_OF_OUTPUT_ONCE pattern was a latent bug (Sonnet Phase 5 + * review finding 7.3): mpv probes with small surfaces (e.g. 128x128) + * before requesting the real resolution (e.g. 1920x1088). The + * once-only set kept the OUTPUT — and consequently the kernel-derived + * CAPTURE — format pinned to the probe size. Subsequent + * v4l2_get_format on CAPTURE then returned the small format, the + * VADRMPRIMESurfaceDescriptor was filled with width=1920 height=1088 + * but pitch=128 offset=16384, and Mesa rejected the import with + * "WSI pitch too small." That manifested as the solid-blue render in + * mpv vaapi mode and the SW fallback in Firefox after frame 0. + * + * Fix: track (width, height) and re-set the OUTPUT format whenever + * the resolution changes. Re-setting requires REQBUFS(0) on both + * queues first because S_FMT after CREATE_BUFS is rejected by V4L2; + * we tear down and let the next allocation cycle recreate buffers + * at the new resolution. + */ +static unsigned int LAST_OUTPUT_WIDTH = 0; +static unsigned int LAST_OUTPUT_HEIGHT = 0; VAStatus RequestCreateSurfaces2(VADriverContextP context, unsigned int format, unsigned int width, unsigned int height, @@ -71,31 +91,46 @@ VAStatus RequestCreateSurfaces2(VADriverContextP context, unsigned int format, bool found; int rc; - //////////// HACK: this portion of the code should get cleaned up. - - // v4l2_set_format needs to be called BEFORE we create any buffers - // - // we originally did this for the output stream in context.c, but - // RequestCreateSurfaces2 gets called multiple times before RequestCreateContext - // to allocate & map buffers. this doesn't seem to work in recent kernel versions. - // - // we declare SET_FORMAT_OF_OUTPUT_ONCE to ensure v4l2_set_format only gets called once - // (in the first RequestCreateSurfaces2 call BEFORE any buffers are created later on) + /* + * Set the OUTPUT format on (re)allocation when the resolution + * differs from the last set value. Without this, mpv's small + * probe surfaces (128x128) pin the CAPTURE format and the + * subsequent real-resolution surface ends up with wrong pitch + * in the export descriptor — causing Mesa to reject the + * DMA-BUF import. Detail in the LAST_OUTPUT_WIDTH/HEIGHT + * comment block at the top of this file. + * + * TODO: this is still not a clean architecture — v4l2_set_format + * after CREATE_BUFS requires REQBUFS(0) first (kernel returns + * EBUSY otherwise). For mpv's pattern (probe with small, then + * allocate big) the small probe surfaces have not been streamed + * yet, so REQBUFS(0) on them works. For consumers that legitimately + * stream multiple resolutions in sequence, we'd need to STREAMOFF + * + REQBUFS(0) + new S_FMT + new CREATE_BUFS — that's a context- + * level redesign for the next iteration. + */ unsigned int pixelformat = V4L2_PIX_FMT_H264_SLICE; unsigned int output_type = v4l2_type_video_output(true); - if (!SET_FORMAT_OF_OUTPUT_ONCE) { + if (LAST_OUTPUT_WIDTH != width || LAST_OUTPUT_HEIGHT != height) { + /* + * If we've previously allocated OUTPUT buffers at a different + * resolution, tear them down before re-setting the format — + * S_FMT is rejected by V4L2 while buffers exist. + */ + if (LAST_OUTPUT_WIDTH != 0) + (void)v4l2_request_buffers(driver_data->video_fd, + output_type, 0); + rc = v4l2_set_format(driver_data->video_fd, output_type, pixelformat, - width, height); - if (rc < 0) { + width, height); + if (rc < 0) return VA_STATUS_ERROR_OPERATION_FAILED; - } - SET_FORMAT_OF_OUTPUT_ONCE = true; + LAST_OUTPUT_WIDTH = width; + LAST_OUTPUT_HEIGHT = height; } - /////////// ENDHACK - if (format != VA_RT_FORMAT_YUV420) return VA_STATUS_ERROR_UNSUPPORTED_RT_FORMAT;