* Copyright © 2008-2011 Kristian Høgsberg
* Copyright © 2011 Intel Corporation
* Copyright © 2017, 2018 Collabora, Ltd.
* Copyright © 2017, 2018 General Electric Company
* Copyright (c) 2018 DisplayLink (UK) Ltd.
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/vt.h>
#include <assert.h>
#include <sys/mman.h>
#include <time.h>
#include <poll.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <libweston/libweston.h>
#include <libweston/weston-log.h>
#include "drm-internal.h"
#include "shared/hash.h"
#include "shared/timespec-util.h"
#include "shared/string-helpers.h"
#include "output-capture.h"
#include "pixman-renderer.h"
#include "libinput-seat.h"
#include "vaapi-recorder.h"
#include "presentation-time-server-protocol.h"
#include "linux-dmabuf-unstable-v1-server-protocol.h"
#include "linux-explicit-synchronization.h"
static const char default_seat[] = "seat0";
drm_backend_create_faked_zpos(struct drm_device *device)
struct drm_backend *b = device->backend;
struct drm_plane *plane, *tmp;
struct wl_list tmp_list;
uint64_t zpos = 0ULL;
uint64_t zpos_min_primary;
uint64_t zpos_min_overlay;
uint64_t zpos_min_cursor;
/* if the property is there, bail out sooner */
wl_list_for_each(plane, &device->plane_list, link) {
if (plane->props[WDRM_PLANE_ZPOS].prop_id != 0)
return;
drm_debug(b, "[drm-backend] zpos property not found. "
"Using invented immutable zpos values:\n");
wl_list_init(&tmp_list);
wl_list_insert_list(&tmp_list, &device->plane_list);
wl_list_init(&device->plane_list);
zpos_min_primary = zpos;
wl_list_for_each_safe(plane, tmp, &tmp_list, link) {
if (plane->type != WDRM_PLANE_TYPE_PRIMARY)
continue;
plane->zpos_min = zpos_min_primary;
plane->zpos_max = zpos_min_primary;
wl_list_remove(&plane->link);
wl_list_insert(&device->plane_list, &plane->link);
drm_debug(b, "\t[plane] %s plane %d, zpos_min %"PRIu64", "
"zpos_max %"PRIu64"\n",
drm_output_get_plane_type_name(plane),
plane->plane_id, plane->zpos_min, plane->zpos_max);
}
zpos_min_overlay = zpos;
wl_list_for_each_safe(plane, tmp, &tmp_list, link) {
if (plane->type != WDRM_PLANE_TYPE_OVERLAY)
continue;
plane->zpos_min = zpos_min_overlay;
plane->zpos_max = zpos_min_overlay;
wl_list_remove(&plane->link);
wl_list_insert(&device->plane_list, &plane->link);
drm_debug(b, "\t[plane] %s plane %d, zpos_min %"PRIu64", "
"zpos_max %"PRIu64"\n",
drm_output_get_plane_type_name(plane),
plane->plane_id, plane->zpos_min, plane->zpos_max);
}
zpos_min_cursor = zpos;
wl_list_for_each_safe(plane, tmp, &tmp_list, link) {
if (plane->type != WDRM_PLANE_TYPE_CURSOR)
continue;
plane->zpos_min = zpos_min_cursor;
plane->zpos_max = zpos_min_cursor;
wl_list_remove(&plane->link);
wl_list_insert(&device->plane_list, &plane->link);
zpos++;
drm_debug(b, "\t[plane] %s plane %d, zpos_min %"PRIu64", "
"zpos_max %"PRIu64"\n",
drm_output_get_plane_type_name(plane),
plane->plane_id, plane->zpos_min, plane->zpos_max);
}
assert(wl_list_empty(&tmp_list));
static int
pageflip_timeout(void *data) {
/*
* Our timer just went off, that means we're not receiving drm
* page flip events anymore for that output. Let's gracefully exit
* weston with a return value so devs can debug what's going on.
*/
struct drm_output *output = data;
struct weston_compositor *compositor = output->base.compositor;
weston_log("Pageflip timeout reached on output %s, your "
"driver is probably buggy! Exiting.\n",
output->base.name);
weston_compositor_exit_with_code(compositor, EXIT_FAILURE);
return 0;
}
/* Creates the pageflip timer. Note that it isn't armed by default */
static int
drm_output_pageflip_timer_create(struct drm_output *output)
{
struct wl_event_loop *loop = NULL;
struct weston_compositor *ec = output->base.compositor;
loop = wl_display_get_event_loop(ec->wl_display);
assert(loop);
output->pageflip_timer = wl_event_loop_add_timer(loop,
pageflip_timeout,
output);
if (output->pageflip_timer == NULL) {
weston_log("creating drm pageflip timer failed: %s\n",
strerror(errno));
return -1;
}
return 0;
}
/**
* Returns true if the plane can be used on the given output for its current
* repaint cycle.
*/
drm_plane_is_available(struct drm_plane *plane, struct drm_output *output)
assert(plane->state_cur);
if (output->is_virtual)
/* The plane still has a request not yet completed by the kernel. */
if (!plane->state_cur->complete)
return false;
/* The plane is still active on another output. */
if (plane->state_cur->output && plane->state_cur->output != output)
return false;
/* Check whether the plane can be used with this CRTC; possible_crtcs
* is a bitmask of CRTC indices (pipe), rather than CRTC object ID. */
return !!(plane->possible_crtcs & (1 << output->crtc->pipe));
struct drm_crtc *
drm_crtc_find(struct drm_device *device, uint32_t crtc_id)
struct drm_crtc *crtc;
wl_list_for_each(crtc, &device->crtc_list, link) {
if (crtc->crtc_id == crtc_id)
return crtc;
drm_head_find_by_connector(struct drm_backend *backend, struct drm_device *device, uint32_t connector_id)
{
struct weston_head *base;
struct drm_head *head;
wl_list_for_each(base,
&backend->compositor->head_list, compositor_link) {
head = to_drm_head(base);
if (!head)
continue;
if (head->connector.device != device)
continue;
if (head->connector.connector_id != connector_id)
continue;
return head;
}
return NULL;
}
static struct drm_writeback *
drm_writeback_find_by_connector(struct drm_device *device, uint32_t connector_id)
{
struct drm_writeback *writeback;
wl_list_for_each(writeback, &device->writeback_connector_list, link) {
if (writeback->connector.connector_id == connector_id)
return writeback;
}
return NULL;
}
/**
* Get output state to disable output
*
* Returns a pointer to an output_state object which can be used to disable
* an output (e.g. DPMS off).
*
* @param pending_state The pending state object owning this update
* @param output The output to disable
* @returns A drm_output_state to disable the output
*/
static struct drm_output_state *
drm_output_get_disable_state(struct drm_pending_state *pending_state,
struct drm_output *output)
{
struct drm_output_state *output_state;
output_state = drm_output_state_duplicate(output->state_cur,
pending_state,
DRM_OUTPUT_STATE_CLEAR_PLANES);
output_state->dpms = WESTON_DPMS_OFF;
output_state->protection = WESTON_HDCP_DISABLE;
return output_state;
}
static int
drm_output_apply_mode(struct drm_output *output);
/**
* Mark a drm_output_state (the output's last state) as complete. This handles
* any post-completion actions such as updating the repaint timer, disabling the
* output, and finally freeing the state.
*/
drm_output_update_complete(struct drm_output *output, uint32_t flags,
unsigned int sec, unsigned int usec)
{
struct drm_device *device = output->device;
struct drm_plane_state *ps;
struct timespec ts;
/* Stop the pageflip timer instead of rearming it here */
if (output->pageflip_timer)
wl_event_source_timer_update(output->pageflip_timer, 0);
wl_list_for_each(ps, &output->state_cur->plane_list, link)
ps->complete = true;
drm_output_state_free(output->state_last);
output->state_last = NULL;
if (output->destroy_pending) {
output->destroy_pending = false;
output->disable_pending = false;
output->dpms_off_pending = false;
output->mode_switch_pending = false;
drm_output_destroy(&output->base);
return;
} else if (output->disable_pending) {
output->disable_pending = false;
output->dpms_off_pending = false;
output->mode_switch_pending = false;
weston_output_disable(&output->base);
return;
} else if (output->dpms_off_pending) {
struct drm_pending_state *pending = drm_pending_state_alloc(device);
output->dpms_off_pending = false;
output->mode_switch_pending = false;
drm_output_get_disable_state(pending, output);
drm_pending_state_apply_sync(pending);
} else if (output->mode_switch_pending) {
output->mode_switch_pending = false;
drm_output_apply_mode(output);
}
if (output->state_cur->dpms == WESTON_DPMS_OFF &&
output->base.repaint_status != REPAINT_AWAITING_COMPLETION) {
/* DPMS can happen to us either in the middle of a repaint
* cycle (when we have painted fresh content, only to throw it
* away for DPMS off), or at any other random point. If the
* latter is true, then we cannot go through finish_frame,
* because the repaint machinery does not expect this. */
if (output->state_cur->tear)
flags |= WESTON_FINISH_FRAME_TEARING;
ts.tv_sec = sec;
ts.tv_nsec = usec * 1000;
if (output->state_cur->dpms != WESTON_DPMS_OFF)
weston_output_finish_frame(&output->base, &ts, flags);
else
weston_output_finish_frame(&output->base, NULL,
WP_PRESENTATION_FEEDBACK_INVALID);
/* We can't call this from frame_notify, because the output's
* repaint needed flag is cleared just after that */
if (output->recorder)
weston_output_schedule_repaint(&output->base);
}
drm_output_render_pixman(struct drm_output_state *state,
pixman_region32_t *damage)
{
struct drm_output *output = state->output;
struct weston_compositor *ec = output->base.compositor;
output->current_image ^= 1;
ec->renderer->repaint_output(&output->base, damage,
output->renderbuffer[output->current_image]);
return drm_fb_ref(output->dumb[output->current_image]);
}
drm_output_render(struct drm_output_state *state)
{
struct drm_output *output = state->output;
struct drm_device *device = output->device;
struct weston_compositor *c = output->base.compositor;
struct drm_plane_state *scanout_state;
struct drm_plane *scanout_plane = output->scanout_plane;
struct drm_property_info *damage_info =
&scanout_plane->props[WDRM_PLANE_FB_DAMAGE_CLIPS];
pixman_region32_t damage, scanout_damage;
pixman_box32_t *rects;
int n_rects;
/* If we already have a client buffer promoted to scanout, then we don't
* want to render. */
scanout_state = drm_output_state_get_plane(state, scanout_plane);
pixman_region32_init(&damage);
weston_output_flush_damage_for_primary_plane(&output->base, &damage);
/*
* If we don't have any damage on the primary plane, and we already
* have a renderer buffer active, we can reuse it; else we pass
* the damaged region into the renderer to re-render the affected
* area. But, we still have to call the renderer anyway if any screen
* capture is pending, otherwise the capture will not complete.
if (!pixman_region32_not_empty(&damage) &&
wl_list_empty(&output->base.frame_signal.listener_list) &&
!weston_output_has_renderer_capture_tasks(&output->base) &&
scanout_plane->state_cur->fb &&
(scanout_plane->state_cur->fb->type == BUFFER_GBM_SURFACE ||
scanout_plane->state_cur->fb->type == BUFFER_PIXMAN_DUMB)) {
fb = drm_fb_ref(scanout_plane->state_cur->fb);
} else if (c->renderer->type == WESTON_RENDERER_PIXMAN) {
fb = drm_output_render_pixman(state, &damage);
fb = drm_output_render_gl(state, &damage);
if (!fb) {
drm_plane_state_put_back(scanout_state);
}
scanout_state->fb = fb;
scanout_state->output = output;
scanout_state->src_x = 0;
scanout_state->src_y = 0;
scanout_state->src_w = fb->width << 16;
scanout_state->src_h = fb->height << 16;
scanout_state->dest_x = 0;
scanout_state->dest_y = 0;
scanout_state->dest_w = output->base.current_mode->width;
scanout_state->dest_h = output->base.current_mode->height;
scanout_state->zpos = scanout_plane->zpos_min;
/* Don't bother calculating plane damage if the plane doesn't support it */
if (damage_info->prop_id == 0)
pixman_region32_init(&scanout_damage);
weston_region_global_to_output(&scanout_damage,
&output->base,
assert(scanout_state->damage_blob_id == 0);
rects = pixman_region32_rectangles(&scanout_damage, &n_rects);
/*
* If this function fails, the blob id should still be 0.
* This tells the kernel there is no damage information, which means
* that it will consider the whole plane damaged. While this may
* affect efficiency, it should still produce correct results.
*/
drmModeCreatePropertyBlob(device->drm.fd, rects,
sizeof(*rects) * n_rects,
&scanout_state->damage_blob_id);
pixman_region32_fini(&scanout_damage);
out:
pixman_region32_fini(&damage);
}
static uint32_t
drm_connector_get_possible_crtcs_mask(struct drm_connector *connector)
{
struct drm_device *device = connector->device;
uint32_t possible_crtcs = 0;
drmModeConnector *conn = connector->conn;
drmModeEncoder *encoder;
int i;
for (i = 0; i < conn->count_encoders; i++) {
encoder = drmModeGetEncoder(device->drm.fd,
conn->encoders[i]);
if (!encoder)
continue;
possible_crtcs |= encoder->possible_crtcs;
drmModeFreeEncoder(encoder);
}
return possible_crtcs;
}
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)
{
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)
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;
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;
assert(output->device->atomic_modeset);
ct = weston_output_pull_capture_task(&output->base,
WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK,
width, height, pixel_format_get_info(format));
if (!ct)
return;
if (output->base.disable_planes > 0) {
msg = "drm: KMS planes usage is disabled for now, so " \
"writeback capture tasks are rejected";
goto err;
}
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);
}
#ifdef BUILD_DRM_GBM
/**
* Update the image for the current cursor surface
*
* @param plane_state DRM cursor plane state
* @param ev Source view for cursor
*/
static void
cursor_bo_update(struct drm_output *output, struct weston_view *ev)
{
struct drm_device *device = output->device;
struct gbm_bo *bo = output->gbm_cursor_fb[output->current_cursor]->bo;
struct weston_buffer *buffer = ev->surface->buffer_ref.buffer;
uint32_t buf[device->cursor_width * device->cursor_height];
uint8_t *s;
int i;
assert(buffer && buffer->shm_buffer);
assert(buffer->width <= device->cursor_width);
assert(buffer->height <= device->cursor_height);
memset(buf, 0, sizeof buf);
s = wl_shm_buffer_get_data(buffer->shm_buffer);
wl_shm_buffer_begin_access(buffer->shm_buffer);
for (i = 0; i < buffer->height; i++)
memcpy(buf + i * device->cursor_width,
s + i * buffer->stride,
buffer->width * 4);
wl_shm_buffer_end_access(buffer->shm_buffer);
if (bo) {
if (gbm_bo_write(bo, buf, sizeof buf) < 0)
weston_log("failed update cursor: %s\n", strerror(errno));
} else {
memcpy(output->gbm_cursor_fb[output->current_cursor]->map,
buf, sizeof buf);
}
}
#else
static void
cursor_bo_update(struct drm_output *output, struct weston_view *ev)
{
}
#endif
static void
drm_output_prepare_repaint(struct weston_output *output_base)
{
struct drm_output *output = to_drm_output(output_base);
output->device->will_repaint = true;
}
drm_output_repaint(struct weston_output *output_base)
struct drm_output *output = to_drm_output(output_base);
struct drm_output_state *state = NULL;
struct drm_plane_state *scanout_state;
struct drm_plane_state *cursor_state;
struct drm_pending_state *pending_state;
assert(output);
assert(!output->is_virtual);
device = output->device;
pending_state = device->repaint_data;
assert(pending_state);
if (output->disable_pending || output->destroy_pending)
goto err;
assert(!output->state_last);
/* If planes have been disabled in the core, we might not have
* hit assign_planes at all, so might not have valid output state
* here. */
state = drm_pending_state_get_output(pending_state, output);
if (!state)
state = drm_output_state_duplicate(output->state_cur,
pending_state,
DRM_OUTPUT_STATE_CLEAR_PLANES);
state->dpms = WESTON_DPMS_ON;
cursor_state = drm_output_state_get_existing_plane(state,
output->cursor_plane);
if (cursor_state && cursor_state->fb) {
pixman_region32_t damage;
assert(cursor_state->plane == output->cursor_plane);
assert(cursor_state->fb == output->gbm_cursor_fb[0]);
pixman_region32_init(&damage);
weston_output_flush_damage_for_plane(&output->base,
&output->cursor_plane->base,
&damage);
if (pixman_region32_not_empty(&damage)) {
output->current_cursor++;
output->current_cursor =
output->current_cursor %
ARRAY_LENGTH(output->gbm_cursor_fb);
cursor_bo_update(output, output->cursor_view);
}
pixman_region32_fini(&damage);
cursor_state->fb = drm_fb_ref(output->gbm_cursor_fb[output->current_cursor]);
drm_fb_unref(output->gbm_cursor_fb[0]);
}
if (output_base->allow_protection)
state->protection = output_base->desired_protection;
else
state->protection = WESTON_HDCP_DISABLE;
if (drm_output_ensure_hdr_output_metadata_blob(output) < 0)
goto err;
if (device->atomic_modeset)
drm_output_pick_writeback_capture_task(output);
drm_output_render(state);
scanout_state = drm_output_state_get_plane(state,
output->scanout_plane);
if (!scanout_state || !scanout_state->fb)
goto err;
return 0;
err:
drm_output_state_free(state);
return -1;
/* Determine the type of vblank synchronization to use for the output.
* The pipe parameter indicates which CRTC is in use. Knowing this, we
* can determine which vblank sequence type to use for it. Traditional
* cards had only two CRTCs, with CRTC 0 using no special flags, and
* CRTC 1 using DRM_VBLANK_SECONDARY. The first bit of the pipe
* parameter indicates this.
* Bits 1-5 of the pipe parameter are 5 bit wide pipe number between
* 0-31. If this is non-zero it indicates we're dealing with a
* multi-gpu situation and we need to calculate the vblank sync
* using DRM_BLANK_HIGH_CRTC_MASK.
*/
static unsigned int
drm_waitvblank_pipe(struct drm_crtc *crtc)
if (crtc->pipe > 1)
return (crtc->pipe << DRM_VBLANK_HIGH_CRTC_SHIFT) &
DRM_VBLANK_HIGH_CRTC_MASK;
else if (crtc->pipe > 0)
return DRM_VBLANK_SECONDARY;
else
return 0;
}
drm_output_start_repaint_loop(struct weston_output *output_base)
struct drm_output *output = to_drm_output(output_base);
struct drm_pending_state *pending_state;
struct drm_plane *scanout_plane = output->scanout_plane;
struct drm_device *device = output->device;
struct drm_backend *backend = device->backend;
struct weston_compositor *compositor = backend->compositor;
struct timespec ts, tnow;
struct timespec vbl2now;
int64_t refresh_nsec;
uint32_t flags = WP_PRESENTATION_FEEDBACK_INVALID;
int ret;
drmVBlank vbl = {
.request.type = DRM_VBLANK_RELATIVE,
.request.sequence = 0,
.request.signal = 0,
};
if (output->disable_pending || output->destroy_pending)
if (!scanout_plane->state_cur->fb) {
/* We can't page flip if there's no mode set */
goto finish_frame;
/* Need to smash all state in from scratch; current timings might not
* be what we want, page flip might not work, etc.
*/
assert(scanout_plane->state_cur->output == output);
/* If we're tearing, we've been generating timestamps from the
* presentation clock that don't line up with the msc timestamps,
* and could be more recent than the latest msc, which would cause
* an assert() later.
*/
if (output->state_cur->tear) {
flags |= WESTON_FINISH_FRAME_TEARING;
goto finish_frame;
}
/* Try to get current msc and timestamp via instant query */
vbl.request.type |= drm_waitvblank_pipe(output->crtc);
ret = drmWaitVBlank(device->drm.fd, &vbl);
/* Error ret or zero timestamp means failure to get valid timestamp */
if ((ret == 0) && (vbl.reply.tval_sec > 0 || vbl.reply.tval_usec > 0)) {
ts.tv_sec = vbl.reply.tval_sec;
ts.tv_nsec = vbl.reply.tval_usec * 1000;
/* Valid timestamp for most recent vblank - not stale?
* Stale ts could happen on Linux 3.17+, so make sure it
* is not older than 1 refresh duration since now.
*/
weston_compositor_read_presentation_clock(compositor,
&tnow);
timespec_sub(&vbl2now, &tnow, &ts);
refresh_nsec =
millihz_to_nsec(output->base.current_mode->refresh);
if (timespec_to_nsec(&vbl2now) < refresh_nsec) {
drm_output_update_msc(output, vbl.reply.sequence);
weston_output_finish_frame(output_base, &ts, flags);
/* Immediate query didn't provide valid timestamp.
* Use pageflip fallback.
*/
assert(!output->page_flip_pending);
assert(!output->state_last);
pending_state = drm_pending_state_alloc(device);
drm_output_state_duplicate(output->state_cur, pending_state,
DRM_OUTPUT_STATE_PRESERVE_PLANES);
ret = drm_pending_state_apply(pending_state);
if (ret != 0) {
weston_log("applying repaint-start state failed: %s\n",
strerror(errno));
if (ret == -EACCES || ret == -EBUSY)
return ret;
finish_frame:
/* if we cannot page-flip, immediately finish frame */
weston_output_finish_frame(output_base, NULL, flags);
static void
drm_repaint_begin_device(struct drm_device *device)
{
struct drm_backend *b = device->backend;
struct drm_pending_state *pending_state;
device->will_repaint = false;
pending_state = drm_pending_state_alloc(device);
device->repaint_data = pending_state;
if (weston_log_scope_is_enabled(b->debug))
drm_debug(b, "[repaint] Beginning repaint (%s); pending_state %p\n",
device->drm.filename, device->repaint_data);
}
/**
* Begin a new repaint cycle
*
* Called by the core compositor at the beginning of a repaint cycle. Creates
* a new pending_state structure to own any output state created by individual
* output repaint functions until the repaint is flushed or cancelled.
drm_repaint_begin(struct weston_backend *backend)
struct drm_backend *b = container_of(backend, struct drm_backend, base);
struct drm_device *device;
if (b->drm->will_repaint)
drm_repaint_begin_device(b->drm);
wl_list_for_each(device, &b->kms_list, link) {
if (device->will_repaint)
drm_repaint_begin_device(device);
}
if (weston_log_scope_is_enabled(b->debug)) {
char *dbg = weston_compositor_print_scene_graph(b->compositor);
drm_debug(b, "%s", dbg);
free(dbg);
}
}
drm_repaint_flush_device(struct drm_device *device)
{
struct drm_backend *b = device->backend;
struct drm_pending_state *pending_state;
struct weston_output *base;
int ret;
pending_state = device->repaint_data;
if (!pending_state)
ret = drm_pending_state_apply(pending_state);
if (ret != 0)
weston_log("repaint-flush failed: %s\n", strerror(errno));
drm_debug(b, "[repaint] flushed (%s) pending_state %p\n",
device->drm.filename, pending_state);
device->repaint_data = NULL;
if (ret == 0)
return;
wl_list_for_each(base, &b->compositor->output_list, link) {
struct drm_output *tmp = to_drm_output(base);
if (!base->will_repaint || !tmp || tmp->device != device)
continue;
weston_output_schedule_repaint_restart(base);
weston_output_schedule_repaint_reset(base);
}
/**
* Flush a repaint set
*
* Called by the core compositor when a repaint cycle has been completed
* and should be flushed. Frees the pending state, transitioning ownership
* of the output state from the pending state, to the update itself. When
* the update completes (see drm_output_update_complete), the output
* state will be freed.
drm_repaint_flush(struct weston_backend *backend)
struct drm_backend *b = container_of(backend, struct drm_backend, base);
struct drm_device *device;
drm_repaint_flush_device(b->drm);
wl_list_for_each(device, &b->kms_list, link)
drm_repaint_flush_device(device);
}
static void
drm_repaint_cancel_device(struct drm_device *device)
{