diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index 52f676d5..8dc72426 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -54,6 +54,7 @@ #include #include #include +#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 diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index a3297f9b..b5e536a5 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -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) diff --git a/libweston/backend-drm/kms.c b/libweston/backend-drm/kms.c index 17185fe2..5b499ced 100644 --- a/libweston/backend-drm/kms.c +++ b/libweston/backend-drm/kms.c @@ -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) diff --git a/libweston/backend-drm/state-propose.c b/libweston/backend-drm/state-propose.c index eb7e5b27..ff08ae16 100644 --- a/libweston/backend-drm/state-propose.c +++ b/libweston/backend-drm/state-propose.c @@ -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