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:
parent
2d70bdfdcd
commit
5b04895835
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user