output-capture: Support writeback connector formats

Writeback connectors often support multiple formats, fully independent
of the input framebuffer. Wire up support for querying formats and send
them to clients.

Signed-off-by: Robert Mader <robert.mader@collabora.com>
This commit is contained in:
Robert Mader 2025-09-03 17:57:19 +02:00
parent 07a5ec8157
commit 9ea205e007
7 changed files with 139 additions and 44 deletions

View file

@ -634,11 +634,6 @@ drm_output_find_compatible_writeback(struct drm_output *output)
if (!(possible_crtcs & (1 << output->crtc->pipe))) if (!(possible_crtcs & (1 << output->crtc->pipe)))
continue; continue;
/* Does the writeback connector support the output gbm format? */
if (!weston_drm_format_array_find_format(&wb->formats,
output->format->format))
continue;
return wb; return wb;
} }
@ -691,13 +686,14 @@ drm_output_pick_writeback_capture_task(struct drm_output *output)
const char *msg; const char *msg;
int32_t width = output->base.current_mode->width; int32_t width = output->base.current_mode->width;
int32_t height = output->base.current_mode->height; int32_t height = output->base.current_mode->height;
uint32_t format = output->format->format; const struct weston_drm_format_array *writeback_formats =
weston_output_get_writeback_formats(&output->base);
assert(output->device->atomic_modeset); assert(output->device->atomic_modeset);
ct = weston_output_pull_capture_task(&output->base, ct = weston_output_pull_capture_task(&output->base,
WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK, WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK,
width, height, pixel_format_get_info(format)); width, height, NULL, writeback_formats);
if (!ct) if (!ct)
return; return;
@ -716,7 +712,6 @@ drm_output_pick_writeback_capture_task(struct drm_output *output)
buffer = weston_capture_task_get_buffer(ct); buffer = weston_capture_task_get_buffer(ct);
assert(buffer->width == width); assert(buffer->width == width);
assert(buffer->height == height); assert(buffer->height == height);
assert(buffer->pixel_format->format == output->format->format);
output->wb_state = drm_writeback_state_alloc(); output->wb_state = drm_writeback_state_alloc();
if (!output->wb_state) { if (!output->wb_state) {
@ -724,7 +719,8 @@ drm_output_pick_writeback_capture_task(struct drm_output *output)
goto err; goto err;
} }
output->wb_state->fb = drm_fb_create_dumb(output->device, width, height, format); output->wb_state->fb = drm_fb_create_dumb(output->device, width, height,
buffer->pixel_format->format);
if (!output->wb_state->fb) { if (!output->wb_state->fb) {
msg = "drm: failed to create dumb buffer for writeback state"; msg = "drm: failed to create dumb buffer for writeback state";
goto err_fb; goto err_fb;
@ -1234,7 +1230,8 @@ drm_output_apply_mode(struct drm_output *output)
WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK, WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK,
output->base.current_mode->width, output->base.current_mode->width,
output->base.current_mode->height, output->base.current_mode->height,
pixel_format_get_info(output->format->format)); NULL,
weston_output_get_writeback_formats(&output->base));
return 0; return 0;
} }
@ -2728,10 +2725,12 @@ drm_output_enable(struct weston_output *base)
output->base.switch_mode = drm_output_switch_mode; output->base.switch_mode = drm_output_switch_mode;
if (device->atomic_modeset) if (device->atomic_modeset)
weston_output_update_capture_info(base, WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK, weston_output_update_capture_info(base,
WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK,
base->current_mode->width, base->current_mode->width,
base->current_mode->height, base->current_mode->height,
pixel_format_get_info(output->format->format)); NULL,
weston_output_get_writeback_formats(&output->base));
weston_log("Output %s (crtc %d) video modes:\n", weston_log("Output %s (crtc %d) video modes:\n",
output->base.name, output->crtc->crtc_id); output->base.name, output->crtc->crtc_id);

View file

@ -1092,7 +1092,8 @@ out:
while ((ct = weston_output_pull_capture_task(base, while ((ct = weston_output_pull_capture_task(base,
WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER, WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER,
width, height, width, height,
ec->read_format))) ec->read_format,
NULL)))
weston_capture_task_retire_failed(ct, "No pipewire buffer"); weston_capture_task_retire_failed(ct, "No pipewire buffer");
} }

View file

@ -120,7 +120,12 @@ struct weston_output_capture_source_info {
int width; int width;
int height; int height;
/* Format used for non-writeback capture source */
uint32_t drm_format; uint32_t drm_format;
/* Formats supported for writeback capture source */
struct weston_drm_format_array writeback_formats;
}; };
/** Capture records for an output */ /** Capture records for an output */
@ -150,8 +155,10 @@ weston_output_capture_info_create(void)
* Initialize to no sources available by leaving * Initialize to no sources available by leaving
* width, height and drm_format as zero. * width, height and drm_format as zero.
*/ */
for (i = 0; i < ARRAY_LENGTH(ci->source_info); i++) for (i = 0; i < ARRAY_LENGTH(ci->source_info); i++) {
ci->source_info[i].pixel_source = i; ci->source_info[i].pixel_source = i;
weston_drm_format_array_init(&ci->source_info[i].writeback_formats);
}
return ci; return ci;
} }
@ -162,6 +169,7 @@ weston_output_capture_info_destroy(struct weston_output_capture_info **cip)
{ {
struct weston_output_capture_info *ci = *cip; struct weston_output_capture_info *ci = *cip;
struct weston_capture_source *csrc, *tmp; struct weston_capture_source *csrc, *tmp;
unsigned i;
assert(ci); assert(ci);
@ -178,6 +186,9 @@ weston_output_capture_info_destroy(struct weston_output_capture_info **cip)
assert(wl_list_empty(&ci->pending_capture_list)); assert(wl_list_empty(&ci->pending_capture_list));
for (i = 0; i < ARRAY_LENGTH(ci->source_info); i++)
weston_drm_format_array_fini(&ci->source_info[i].writeback_formats);
free(ci); free(ci);
*cip = NULL; *cip = NULL;
} }
@ -198,8 +209,14 @@ weston_output_capture_info_repaint_done(struct weston_output_capture_info *ci)
static bool static bool
source_info_is_available(const struct weston_output_capture_source_info *csi) source_info_is_available(const struct weston_output_capture_source_info *csi)
{ {
return csi->width > 0 && csi->height > 0 && if (csi->pixel_source == WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK &&
csi->drm_format != DRM_FORMAT_INVALID; weston_drm_format_array_count_pairs(&csi->writeback_formats) == 0)
return false;
else if (csi->pixel_source != WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK &&
csi->drm_format == DRM_FORMAT_INVALID)
return false;
return csi->width > 0 && csi->height > 0;
} }
static void static void
@ -209,13 +226,28 @@ capture_info_send_source_info(struct weston_output_capture_info *ci,
struct weston_capture_source *csrc; struct weston_capture_source *csrc;
wl_list_for_each(csrc, &ci->capture_source_list, link) { wl_list_for_each(csrc, &ci->capture_source_list, link) {
bool send_done;
if (csrc->pixel_source != csi->pixel_source) if (csrc->pixel_source != csi->pixel_source)
continue; continue;
send_done = wl_resource_get_version(csrc->resource) >=
WESTON_CAPTURE_SOURCE_V1_FORMATS_DONE_SINCE_VERSION;
if (csi->pixel_source == WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK) {
struct weston_drm_format *fmt;
wl_array_for_each(fmt, &csi->writeback_formats.arr) {
weston_capture_source_v1_send_format(csrc->resource,
fmt->format);
if (!send_done)
break;
}
} else {
weston_capture_source_v1_send_format(csrc->resource, weston_capture_source_v1_send_format(csrc->resource,
csi->drm_format); csi->drm_format);
if (wl_resource_get_version(csrc->resource) >= }
WESTON_CAPTURE_SOURCE_V1_FORMATS_DONE_SINCE_VERSION) if (send_done)
weston_capture_source_v1_send_formats_done(csrc->resource); weston_capture_source_v1_send_formats_done(csrc->resource);
weston_capture_source_v1_send_size(csrc->resource, weston_capture_source_v1_send_size(csrc->resource,
@ -246,7 +278,9 @@ capture_info_get_csi(struct weston_output_capture_info *ci,
* \param src The source type on the output. * \param src The source type on the output.
* \param width The new buffer width. * \param width The new buffer width.
* \param height The new buffer height. * \param height The new buffer height.
* \param format The new pixel format. * \param format The new pixel format or %NULL for writeback sources.
* \param writeback_formats An array of the supported pixel formats or %NULL for
* non-writeback sources.
* *
* If any one of width, height or format is zero/NULL, the source becomes * If any one of width, height or format is zero/NULL, the source becomes
* unavailable to clients. Otherwise the source becomes available. * unavailable to clients. Otherwise the source becomes available.
@ -257,21 +291,47 @@ WL_EXPORT void
weston_output_update_capture_info(struct weston_output *output, weston_output_update_capture_info(struct weston_output *output,
enum weston_output_capture_source src, enum weston_output_capture_source src,
int width, int height, int width, int height,
const struct pixel_format_info *format) const struct pixel_format_info *format,
const struct weston_drm_format_array *writeback_formats)
{ {
struct weston_output_capture_info *ci = output->capture_info; struct weston_output_capture_info *ci = output->capture_info;
struct weston_output_capture_source_info *csi; struct weston_output_capture_source_info *csi;
bool formats_equal;
csi = capture_info_get_csi(ci, src); csi = capture_info_get_csi(ci, src);
if (src == WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK) {
assert(!format);
if (writeback_formats) {
formats_equal =
weston_drm_format_array_equal(&csi->writeback_formats,
writeback_formats);
} else {
formats_equal = csi->writeback_formats.arr.size == 0;
}
} else {
assert(format && !writeback_formats);
formats_equal = (format->format == csi->drm_format);
}
if (csi->width == width && if (csi->width == width &&
csi->height == height && csi->height == height &&
csi->drm_format == format->format) formats_equal)
return; return;
csi->width = width; csi->width = width;
csi->height = height; csi->height = height;
if (src == WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK) {
if (writeback_formats) {
weston_drm_format_array_replace(&csi->writeback_formats,
writeback_formats);
} else {
weston_drm_format_array_fini(&csi->writeback_formats);
weston_drm_format_array_init(&csi->writeback_formats);
}
} else {
csi->drm_format = format->format; csi->drm_format = format->format;
}
if (source_info_is_available(csi)) { if (source_info_is_available(csi)) {
capture_info_send_source_info(ci, csi); capture_info_send_source_info(ci, csi);
@ -305,9 +365,19 @@ static bool
buffer_is_compatible(struct weston_buffer *buffer, buffer_is_compatible(struct weston_buffer *buffer,
struct weston_output_capture_source_info *csi) struct weston_output_capture_source_info *csi)
{ {
bool format_supported = false;
if (csi->pixel_source == WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK) {
format_supported =
weston_drm_format_array_find_format(&csi->writeback_formats,
buffer->pixel_format->format) != NULL;
} else {
format_supported = (buffer->pixel_format->format == csi->drm_format);
}
return buffer->width == csi->width && return buffer->width == csi->width &&
buffer->height == csi->height && buffer->height == csi->height &&
buffer->pixel_format->format == csi->drm_format && format_supported &&
buffer->format_modifier == DRM_FORMAT_MOD_LINEAR; buffer->format_modifier == DRM_FORMAT_MOD_LINEAR;
} }
@ -401,7 +471,8 @@ WL_EXPORT struct weston_capture_task *
weston_output_pull_capture_task(struct weston_output *output, weston_output_pull_capture_task(struct weston_output *output,
enum weston_output_capture_source src, enum weston_output_capture_source src,
int width, int height, int width, int height,
const struct pixel_format_info *format) const struct pixel_format_info *format,
const struct weston_drm_format_array *writeback_formats)
{ {
struct weston_output_capture_info *ci = output->capture_info; struct weston_output_capture_info *ci = output->capture_info;
struct weston_output_capture_source_info *csi; struct weston_output_capture_source_info *csi;
@ -416,7 +487,13 @@ weston_output_pull_capture_task(struct weston_output *output,
csi = capture_info_get_csi(ci, src); csi = capture_info_get_csi(ci, src);
assert(csi->width == width); assert(csi->width == width);
assert(csi->height == height); assert(csi->height == height);
if (src == WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK) {
assert(weston_drm_format_array_equal(&csi->writeback_formats,
writeback_formats));
} else {
assert(csi->drm_format == format->format); assert(csi->drm_format == format->format);
}
wl_list_for_each_safe(ct, tmp, &ci->pending_capture_list, link) { wl_list_for_each_safe(ct, tmp, &ci->pending_capture_list, link) {
assert(ct->owner->output == output); assert(ct->owner->output == output);

View file

@ -66,7 +66,8 @@ void
weston_output_update_capture_info(struct weston_output *output, weston_output_update_capture_info(struct weston_output *output,
enum weston_output_capture_source src, enum weston_output_capture_source src,
int width, int height, int width, int height,
const struct pixel_format_info *format); const struct pixel_format_info *format,
const struct weston_drm_format_array *writeback_formats);
bool bool
weston_output_has_renderer_capture_tasks(struct weston_output *output); weston_output_has_renderer_capture_tasks(struct weston_output *output);
@ -77,7 +78,8 @@ struct weston_capture_task *
weston_output_pull_capture_task(struct weston_output *output, weston_output_pull_capture_task(struct weston_output *output,
enum weston_output_capture_source src, enum weston_output_capture_source src,
int width, int height, int width, int height,
const struct pixel_format_info *format); const struct pixel_format_info *format,
const struct weston_drm_format_array *writeback_formats);
struct weston_buffer * struct weston_buffer *
weston_capture_task_get_buffer(struct weston_capture_task *ct); weston_capture_task_get_buffer(struct weston_capture_task *ct);

View file

@ -609,9 +609,11 @@ pixman_renderer_do_capture_tasks(struct weston_output *output,
int height = pixman_image_get_height(from); int height = pixman_image_get_height(from);
struct weston_capture_task *ct; struct weston_capture_task *ct;
assert(source != WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK);
while ((ct = weston_output_pull_capture_task(output, source, while ((ct = weston_output_pull_capture_task(output, source,
width, height, width, height,
pfmt))) { pfmt, NULL))) {
struct weston_buffer *buffer = weston_capture_task_get_buffer(ct); struct weston_buffer *buffer = weston_capture_task_get_buffer(ct);
assert(buffer->width == width); assert(buffer->width == width);
@ -1036,7 +1038,8 @@ pixman_renderer_resize_output(struct weston_output *output,
WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER, WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER,
po->fb_size.width, po->fb_size.width,
po->fb_size.height, po->fb_size.height,
po->hw_format); po->hw_format,
NULL);
} }
/* Discard renderbuffers as a last step in order to emit discarded /* Discard renderbuffers as a last step in order to emit discarded
@ -1059,7 +1062,8 @@ pixman_renderer_resize_output(struct weston_output *output,
WESTON_OUTPUT_CAPTURE_SOURCE_BLENDING, WESTON_OUTPUT_CAPTURE_SOURCE_BLENDING,
po->fb_size.width, po->fb_size.width,
po->fb_size.height, po->fb_size.height,
po->shadow_format); po->shadow_format,
NULL);
return !!po->shadow_image; return !!po->shadow_image;
} }
@ -1177,7 +1181,8 @@ pixman_renderer_output_set_buffer(struct weston_output *output,
WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER, WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER,
po->fb_size.width, po->fb_size.width,
po->fb_size.height, po->fb_size.height,
po->hw_format); po->hw_format,
NULL);
} }
static int static int
@ -1214,7 +1219,8 @@ pixman_renderer_output_create(struct weston_output *output,
weston_output_update_capture_info(output, weston_output_update_capture_info(output,
WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER, WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER,
area.width, area.height, area.width, area.height,
options->format); options->format,
NULL);
return 0; return 0;
} }

View file

@ -1274,7 +1274,8 @@ gl_renderer_do_capture_tasks(struct gl_renderer *gr,
} }
while ((ct = weston_output_pull_capture_task(output, source, rect.width, while ((ct = weston_output_pull_capture_task(output, source, rect.width,
rect.height, format))) { rect.height, format,
NULL))) {
struct weston_buffer *buffer = weston_capture_task_get_buffer(ct); struct weston_buffer *buffer = weston_capture_task_get_buffer(ct);
assert(buffer->width == rect.width); assert(buffer->width == rect.width);
@ -4193,12 +4194,14 @@ gl_renderer_resize_output(struct weston_output *output,
weston_output_update_capture_info(output, weston_output_update_capture_info(output,
WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER, WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER,
area->width, area->height, area->width, area->height,
output->compositor->read_format); output->compositor->read_format,
NULL);
weston_output_update_capture_info(output, weston_output_update_capture_info(output,
WESTON_OUTPUT_CAPTURE_SOURCE_FULL_FRAMEBUFFER, WESTON_OUTPUT_CAPTURE_SOURCE_FULL_FRAMEBUFFER,
fb_size->width, fb_size->height, fb_size->width, fb_size->height,
output->compositor->read_format); output->compositor->read_format,
NULL);
/* Discard renderbuffers as a last step in order to emit discarded /* Discard renderbuffers as a last step in order to emit discarded
* callbacks once the renderer has correctly been updated. */ * callbacks once the renderer has correctly been updated. */

View file

@ -1450,7 +1450,8 @@ vulkan_renderer_do_capture_tasks(struct vulkan_renderer *vr,
} }
while ((ct = weston_output_pull_capture_task(output, source, rect.width, while ((ct = weston_output_pull_capture_task(output, source, rect.width,
rect.height, pixel_format))) { rect.height, pixel_format,
NULL))) {
struct weston_buffer *buffer = weston_capture_task_get_buffer(ct); struct weston_buffer *buffer = weston_capture_task_get_buffer(ct);
assert(buffer->width == rect.width); assert(buffer->width == rect.width);
@ -3270,12 +3271,14 @@ vulkan_renderer_resize_output(struct weston_output *output,
weston_output_update_capture_info(output, weston_output_update_capture_info(output,
WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER, WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER,
area->width, area->height, area->width, area->height,
output->compositor->read_format); output->compositor->read_format,
NULL);
weston_output_update_capture_info(output, weston_output_update_capture_info(output,
WESTON_OUTPUT_CAPTURE_SOURCE_FULL_FRAMEBUFFER, WESTON_OUTPUT_CAPTURE_SOURCE_FULL_FRAMEBUFFER,
fb_size->width, fb_size->height, fb_size->width, fb_size->height,
output->compositor->read_format); output->compositor->read_format,
NULL);
if (!vulkan_renderer_discard_renderbuffers(vo, false)) if (!vulkan_renderer_discard_renderbuffers(vo, false))
return false; return false;
@ -3630,12 +3633,14 @@ vulkan_renderer_output_surface_create(struct weston_output *output,
weston_output_update_capture_info(output, weston_output_update_capture_info(output,
WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER, WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER,
area->width, area->height, area->width, area->height,
output->compositor->read_format); output->compositor->read_format,
NULL);
weston_output_update_capture_info(output, weston_output_update_capture_info(output,
WESTON_OUTPUT_CAPTURE_SOURCE_FULL_FRAMEBUFFER, WESTON_OUTPUT_CAPTURE_SOURCE_FULL_FRAMEBUFFER,
fb_size->width, fb_size->height, fb_size->width, fb_size->height,
output->compositor->read_format); output->compositor->read_format,
NULL);
vulkan_renderer_create_output_frames(output, fb_size, area, MAX_CONCURRENT_FRAMES); vulkan_renderer_create_output_frames(output, fb_size, area, MAX_CONCURRENT_FRAMES);
@ -3670,12 +3675,14 @@ vulkan_renderer_output_surfaceless_create(struct weston_output *output,
weston_output_update_capture_info(output, weston_output_update_capture_info(output,
WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER, WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER,
area->width, area->height, area->width, area->height,
output->compositor->read_format); output->compositor->read_format,
NULL);
weston_output_update_capture_info(output, weston_output_update_capture_info(output,
WESTON_OUTPUT_CAPTURE_SOURCE_FULL_FRAMEBUFFER, WESTON_OUTPUT_CAPTURE_SOURCE_FULL_FRAMEBUFFER,
fb_size->width, fb_size->height, fb_size->width, fb_size->height,
output->compositor->read_format); output->compositor->read_format,
NULL);
vulkan_renderer_create_output_frames(output, fb_size, area, MAX_CONCURRENT_FRAMES); vulkan_renderer_create_output_frames(output, fb_size, area, MAX_CONCURRENT_FRAMES);