drm-backend: add writeback connector screenshooter to DRM-backend

In this patch, we add the writeback connector screenshooter to the
DRM-backend.

This will be useful to create plane composition tests that will run in
our CI, as VKMS already supports writeback connectors.

Signed-off-by: Leandro Ribeiro <leandro.ribeiro@collabora.com>
This commit is contained in:
Leandro Ribeiro 2023-02-14 22:33:25 -03:00
parent 2d70bdfdcd
commit 5b04895835
4 changed files with 344 additions and 5 deletions

View File

@ -54,6 +54,7 @@
#include <libweston/libweston.h>
#include <libweston/backend-drm.h>
#include <libweston/weston-log.h>
#include "output-capture.h"
#include "shared/helpers.h"
#include "shared/weston-drm-fourcc.h"
#include "libinput-seat.h"
@ -197,6 +198,8 @@ enum wdrm_connector_property {
WDRM_CONNECTOR_DPMS,
WDRM_CONNECTOR_CRTC_ID,
WDRM_CONNECTOR_WRITEBACK_PIXEL_FORMATS,
WDRM_CONNECTOR_WRITEBACK_FB_ID,
WDRM_CONNECTOR_WRITEBACK_OUT_FENCE_PTR,
WDRM_CONNECTOR_NON_DESKTOP,
WDRM_CONNECTOR_CONTENT_PROTECTION,
WDRM_CONNECTOR_HDCP_CONTENT_TYPE,
@ -553,6 +556,35 @@ struct drm_connector {
struct drm_property_info props[WDRM_CONNECTOR__COUNT];
};
enum writeback_screenshot_state {
/* No writeback connector screenshot ongoing. */
DRM_OUTPUT_WB_SCREENSHOT_OFF,
/* Screenshot client just triggered a writeback connector screenshot.
* Now we need to prepare an atomic commit that will make DRM perform
* the writeback operation. */
DRM_OUTPUT_WB_SCREENSHOT_PREPARE_COMMIT,
/* The atomic commit with writeback setup has been committed. After the
* commit is handled by DRM it will give us a sync fd that gets
* signalled when the writeback is done. */
DRM_OUTPUT_WB_SCREENSHOT_CHECK_FENCE,
};
struct drm_writeback_state {
struct drm_writeback *wb;
struct drm_output *output;
enum writeback_screenshot_state state;
struct weston_capture_task *ct;
struct drm_fb *fb;
int32_t out_fence_fd;
/* Reference to fb's being used by the writeback job. These are all the
* framebuffers in every drm_plane_state of the output state that we've
* used to request the writeback job */
struct wl_array referenced_fbs;
};
struct drm_writeback {
/* drm_device::writeback_connector_list */
struct wl_list link;
@ -632,6 +664,9 @@ struct drm_output {
* yet acknowledged completion of state_cur. */
struct drm_output_state *state_last;
/* only set when a writeback screenshot is ongoing */
struct drm_writeback_state *wb_state;
struct drm_fb *dumb[2];
struct weston_renderbuffer *renderbuffer[2];
int current_image;
@ -660,6 +695,17 @@ to_drm_head(struct weston_head *base)
return container_of(base, struct drm_head, base);
}
void
drm_writeback_reference_planes(struct drm_writeback_state *state,
struct wl_list *plane_state_list);
bool
drm_writeback_has_finished(struct drm_writeback_state *state);
void
drm_writeback_fail_screenshot(struct drm_writeback_state *state,
const char *err_msg);
enum writeback_screenshot_state
drm_output_get_writeback_state(struct drm_output *output);
void
drm_output_destroy(struct weston_output *output_base);
void

View File

@ -41,6 +41,7 @@
#include <assert.h>
#include <sys/mman.h>
#include <time.h>
#include <poll.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
@ -444,11 +445,91 @@ drm_output_render(struct drm_output_state *state, pixman_region32_t *damage)
pixman_region32_fini(&scanout_damage);
}
static uint32_t
drm_connector_get_possible_crtcs_mask(struct drm_connector *connector);
static struct drm_writeback *
drm_output_find_compatible_writeback(struct drm_output *output)
{
struct drm_crtc *crtc;
struct drm_writeback *wb;
bool in_use;
uint32_t possible_crtcs;
wl_list_for_each(wb, &output->device->writeback_connector_list, link) {
/* Another output may be using the writeback connector. */
in_use = false;
wl_list_for_each(crtc, &output->device->crtc_list, link) {
if (crtc->output && crtc->output->wb_state &&
crtc->output->wb_state->wb == wb) {
in_use = true;
break;
}
}
if (in_use)
continue;
/* Is the writeback connector compatible with the CRTC? */
possible_crtcs =
drm_connector_get_possible_crtcs_mask(&wb->connector);
if (!(possible_crtcs & (1 << output->crtc->pipe)))
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 NULL;
}
static struct drm_writeback_state *
drm_writeback_state_alloc(void)
{
struct drm_writeback_state *state;
state = zalloc(sizeof *state);
if (!state)
return NULL;
state->state = DRM_OUTPUT_WB_SCREENSHOT_OFF;
state->out_fence_fd = -1;
wl_array_init(&state->referenced_fbs);
return state;
}
static void
drm_writeback_state_free(struct drm_writeback_state *state)
{
struct drm_fb *fb;
if (state->out_fence_fd >= 0)
close(state->out_fence_fd);
/* Unref framebuffer that was given to save the content of the writeback */
if (state->fb)
drm_fb_unref(state->fb);
/* Unref framebuffers that were in use in the same commit of the one with
* the writeback setup */
wl_array_for_each(fb, &state->referenced_fbs)
drm_fb_unref(fb);
wl_array_release(&state->referenced_fbs);
free(state);
}
static void
drm_output_pick_writeback_capture_task(struct drm_output *output)
{
struct weston_capture_task *ct;
const char *msg = "drm: writeback screenshot not supported yet";
struct weston_buffer *buffer;
struct drm_writeback *wb;
const char *msg;
int32_t width = output->base.current_mode->width;
int32_t height = output->base.current_mode->height;
uint32_t format = output->format->format;
@ -461,6 +542,40 @@ drm_output_pick_writeback_capture_task(struct drm_output *output)
assert(output->device->atomic_modeset);
wb = drm_output_find_compatible_writeback(output);
if (!wb) {
msg = "drm: could not find writeback connector for output";
goto err;
}
buffer = weston_capture_task_get_buffer(ct);
assert(buffer->width == width);
assert(buffer->height == height);
assert(buffer->pixel_format->format == output->format->format);
output->wb_state = drm_writeback_state_alloc();
if (!output->wb_state) {
msg = "drm: failed to allocate memory for writeback state";
goto err;
}
output->wb_state->fb = drm_fb_create_dumb(output->device, width, height, format);
if (!output->wb_state->fb) {
msg = "drm: failed to create dumb buffer for writeback state";
goto err_fb;
}
output->wb_state->output = output;
output->wb_state->wb = wb;
output->wb_state->state = DRM_OUTPUT_WB_SCREENSHOT_PREPARE_COMMIT;
output->wb_state->ct = ct;
return;
err_fb:
drm_writeback_state_free(output->wb_state);
output->wb_state = NULL;
err:
weston_capture_task_retire_failed(ct, msg);
}
@ -1572,6 +1687,15 @@ drm_connector_get_possible_crtcs_mask(struct drm_connector *connector)
return possible_crtcs;
}
enum writeback_screenshot_state
drm_output_get_writeback_state(struct drm_output *output)
{
if (!output->wb_state)
return DRM_OUTPUT_WB_SCREENSHOT_OFF;
return output->wb_state->state;
}
/** Pick a CRTC that might be able to drive all attached connectors
*
* @param output The output whose attached heads to include.
@ -2476,6 +2600,115 @@ drm_output_create(struct weston_backend *backend, const char *name)
return &output->base;
}
static void
pixman_copy_screenshot(uint32_t *dst, uint32_t *src, int dst_stride,
int src_stride, int pixman_format, int width, int height)
{
pixman_image_t *pixman_dst;
pixman_image_t *pixman_src;
pixman_src = pixman_image_create_bits(pixman_format,
width, height,
src, src_stride);
pixman_dst = pixman_image_create_bits(pixman_format,
width, height,
dst, dst_stride);
assert(pixman_src);
assert(pixman_dst);
pixman_image_composite32(PIXMAN_OP_SRC,
pixman_src, /* src */
NULL, /* mask */
pixman_dst, /* dst */
0, 0, /* src_x, src_y */
0, 0, /* mask_x, mask_y */
0, 0, /* dst_x, dst_y */
width, height); /* width, height */
pixman_image_unref(pixman_src);
pixman_image_unref(pixman_dst);
}
static void
drm_writeback_success_screenshot(struct drm_writeback_state *state)
{
struct drm_output *output = state->output;
struct weston_buffer *buffer =
weston_capture_task_get_buffer(state->ct);
int width, height;
int dst_stride, src_stride;
uint32_t *src, *dst;
src = state->fb->map;
src_stride = state->fb->strides[0];
dst = wl_shm_buffer_get_data(buffer->shm_buffer);
dst_stride = wl_shm_buffer_get_stride(buffer->shm_buffer);
width = state->fb->width;
height = state->fb->height;
wl_shm_buffer_begin_access(buffer->shm_buffer);
pixman_copy_screenshot(dst, src, dst_stride, src_stride,
buffer->pixel_format->pixman_format,
width, height);
wl_shm_buffer_end_access(buffer->shm_buffer);
weston_capture_task_retire_complete(state->ct);
drm_writeback_state_free(state);
output->wb_state = NULL;
}
void
drm_writeback_fail_screenshot(struct drm_writeback_state *state,
const char *err_msg)
{
struct drm_output *output = state->output;
weston_capture_task_retire_failed(state->ct, err_msg);
drm_writeback_state_free(state);
output->wb_state = NULL;
}
bool
drm_writeback_has_finished(struct drm_writeback_state *state)
{
struct pollfd pollfd;
int ret;
pollfd.fd = state->out_fence_fd;
pollfd.events = POLLIN;
while ((ret = poll(&pollfd, 1, 0)) == -1 && errno == EINTR)
continue;
if (ret < 0) {
drm_writeback_fail_screenshot(state, "drm: polling wb fence failed");
return true;
} else if (ret > 0) {
/* fence already signaled, simply save the screenshot */
drm_writeback_success_screenshot(state);
return true;
}
/* poll() returned 0, what means that out fence was not signalled yet */
return false;
}
void
drm_writeback_reference_planes(struct drm_writeback_state *state,
struct wl_list *plane_state_list)
{
struct drm_plane_state *plane_state;
struct drm_fb **fb;
wl_list_for_each(plane_state, plane_state_list, link) {
if (!plane_state->fb)
continue;
fb = wl_array_add(&state->referenced_fbs, sizeof(*fb));
*fb = drm_fb_ref(plane_state->fb);
}
}
static int
drm_writeback_populate_formats(struct drm_writeback *wb)

View File

@ -166,6 +166,8 @@ const struct drm_property_info connector_props[] = {
},
[WDRM_CONNECTOR_CRTC_ID] = { .name = "CRTC_ID", },
[WDRM_CONNECTOR_WRITEBACK_PIXEL_FORMATS] = { .name = "WRITEBACK_PIXEL_FORMATS", },
[WDRM_CONNECTOR_WRITEBACK_FB_ID] = { .name = "WRITEBACK_FB_ID", },
[WDRM_CONNECTOR_WRITEBACK_OUT_FENCE_PTR] = { .name = "WRITEBACK_OUT_FENCE_PTR", },
[WDRM_CONNECTOR_NON_DESKTOP] = { .name = "non-desktop", },
[WDRM_CONNECTOR_CONTENT_PROTECTION] = {
.name = "Content Protection",
@ -1160,6 +1162,9 @@ drm_output_apply_state_atomic(struct drm_output_state *state,
struct drm_plane_state *plane_state;
struct drm_mode *current_mode = to_drm_mode(output->base.current_mode);
struct drm_head *head;
struct drm_writeback_state *wb_state = output->wb_state;
enum writeback_screenshot_state wb_screenshot_state =
drm_output_get_writeback_state(output);
int ret = 0;
drm_debug(b, "\t\t[atomic] %s output %lu (%s) state\n",
@ -1171,6 +1176,11 @@ drm_output_apply_state_atomic(struct drm_output_state *state,
*flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
}
if (wb_screenshot_state == DRM_OUTPUT_WB_SCREENSHOT_PREPARE_COMMIT) {
drm_debug(b, "\t\t\t[atomic] Writeback connector screenshot requested, modeset OK\n");
*flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
}
if (state->dpms == WESTON_DPMS_ON) {
ret = drm_mode_ensure_blob(device, current_mode);
if (ret != 0)
@ -1196,10 +1206,29 @@ drm_output_apply_state_atomic(struct drm_output_state *state,
WDRM_CONNECTOR_CRTC_ID,
crtc->crtc_id);
}
if (wb_screenshot_state == DRM_OUTPUT_WB_SCREENSHOT_PREPARE_COMMIT) {
ret |= connector_add_prop(req, &wb_state->wb->connector,
WDRM_CONNECTOR_CRTC_ID,
crtc->crtc_id);
ret |= connector_add_prop(req, &wb_state->wb->connector,
WDRM_CONNECTOR_WRITEBACK_FB_ID,
wb_state->fb->fb_id);
ret |= connector_add_prop(req, &wb_state->wb->connector,
WDRM_CONNECTOR_WRITEBACK_OUT_FENCE_PTR,
(uintptr_t)&wb_state->out_fence_fd);
if (!(*flags & DRM_MODE_ATOMIC_TEST_ONLY))
wb_state->state = DRM_OUTPUT_WB_SCREENSHOT_CHECK_FENCE;
}
} else {
ret |= crtc_add_prop(req, crtc, WDRM_CRTC_MODE_ID, 0);
ret |= crtc_add_prop(req, crtc, WDRM_CRTC_ACTIVE, 0);
if (wb_screenshot_state == DRM_OUTPUT_WB_SCREENSHOT_PREPARE_COMMIT) {
drm_debug(b, "\t\t\t[atomic] Writeback connector screenshot requested but CRTC is off\n");
drm_writeback_fail_screenshot(wb_state, "drm: CRTC is off");
}
/* No need for the DPMS property, since it is implicit in
* routing and CRTC activity. */
wl_list_for_each(head, &output->base.head_list, base.output_link)
@ -1457,6 +1486,10 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state,
}
if (ret != 0) {
wl_list_for_each(output_state, &pending_state->output_list, link)
if (drm_output_get_writeback_state(output_state->output) != DRM_OUTPUT_WB_SCREENSHOT_OFF)
drm_writeback_fail_screenshot(output_state->output->wb_state,
"drm: atomic commit failed");
weston_log("atomic: couldn't commit new state: %s\n",
strerror(errno));
goto out;
@ -1715,8 +1748,20 @@ int
on_drm_input(int fd, uint32_t mask, void *data)
{
struct drm_device *device = data;
struct drm_writeback_state *state;
struct drm_crtc *crtc;
drmEventContext evctx;
/* If we have a pending writeback job for this output, we can't continue
* with the repaint loop. The KMS UAPI docs says that we need to wait
* until the writeback is over before we send a new atomic commit that
* uses the KMS objects (CRTC, planes, etc) in use by the writeback. */
wl_list_for_each(crtc, &device->crtc_list, link) {
state = crtc->output ? crtc->output->wb_state : NULL;
if (state && !drm_writeback_has_finished(state))
drm_writeback_fail_screenshot(state, "drm: out fence not signalled yet");
}
memset(&evctx, 0, sizeof evctx);
evctx.version = 3;
if (device->atomic_modeset)

View File

@ -960,6 +960,7 @@ drm_assign_planes(struct weston_output *output_base)
struct drm_pending_state *pending_state = device->repaint_data;
struct drm_output_state *state = NULL;
struct drm_plane_state *plane_state;
struct drm_writeback_state *wb_state = output->wb_state;
struct weston_paint_node *pnode;
struct weston_plane *primary = &output_base->compositor->primary_plane;
enum drm_output_propose_state_mode mode = DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY;
@ -980,18 +981,29 @@ drm_assign_planes(struct weston_output *output_base)
pending_state,
mode);
}
if (!state) {
drm_debug(b, "\t[repaint] could not build mixed-mode "
"state, trying renderer-only\n");
}
} else {
drm_debug(b, "\t[state] no overlay plane support\n");
}
/* We can enter this block in two situations:
* 1. If we didn't enter the last block (for some reason we can't use planes)
* 2. If we entered but both the planes-only and the mixed modes didn't work */
if (!state) {
drm_debug(b, "\t[repaint] could not build state with planes, "
"trying renderer-only\n");
mode = DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY;
state = drm_output_propose_state(output_base, pending_state,
mode);
/* If renderer only mode failed and we are in a writeback
* screenshot, let's abort the writeback screenshot and try
* again. */
if (!state && drm_output_get_writeback_state(output) != DRM_OUTPUT_WB_SCREENSHOT_OFF) {
drm_debug(b, "\t[repaint] could not build renderer-only "
"state, trying without writeback setup\n");
drm_writeback_fail_screenshot(wb_state, "drm: failed to propose state");
state = drm_output_propose_state(output_base, pending_state,
mode);
}
}
assert(state);
@ -1079,6 +1091,9 @@ drm_assign_planes(struct weston_output *output_base)
if (!plane_state || !plane_state->fb)
drm_output_set_cursor_view(output, NULL);
}
if (drm_output_get_writeback_state(output) == DRM_OUTPUT_WB_SCREENSHOT_PREPARE_COMMIT)
drm_writeback_reference_planes(wb_state, &state->plane_list);
}
static void