diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c index 4715c6f0..47d4e585 100644 --- a/backend/drm/atomic.c +++ b/backend/drm/atomic.c @@ -61,13 +61,14 @@ static void atomic_begin(struct atomic *atom) { } static bool atomic_commit(struct atomic *atom, - struct wlr_drm_connector *conn, uint32_t flags) { + struct wlr_drm_connector *conn, struct wlr_drm_page_flip *page_flip, + uint32_t flags) { struct wlr_drm_backend *drm = conn->backend; if (atom->failed) { return false; } - int ret = drmModeAtomicCommit(drm->fd, atom->req, flags, drm); + int ret = drmModeAtomicCommit(drm->fd, atom->req, flags, page_flip); if (ret != 0) { wlr_drm_conn_log_errno(conn, (flags & DRM_MODE_ATOMIC_TEST_ONLY) ? WLR_DEBUG : WLR_ERROR, @@ -257,8 +258,8 @@ static void set_plane_props(struct atomic *atom, struct wlr_drm_backend *drm, } static bool atomic_crtc_commit(struct wlr_drm_connector *conn, - const struct wlr_drm_connector_state *state, uint32_t flags, - bool test_only) { + const struct wlr_drm_connector_state *state, + struct wlr_drm_page_flip *page_flip, uint32_t flags, bool test_only) { struct wlr_drm_backend *drm = conn->backend; struct wlr_output *output = &conn->output; struct wlr_drm_crtc *crtc = conn->crtc; @@ -367,7 +368,7 @@ static bool atomic_crtc_commit(struct wlr_drm_connector *conn, } } - bool ok = atomic_commit(&atom, conn, flags); + bool ok = atomic_commit(&atom, conn, page_flip, flags); atomic_finish(&atom); if (ok && !test_only) { diff --git a/backend/drm/drm.c b/backend/drm/drm.c index e8a6f5c6..92feed6c 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -412,15 +412,32 @@ static struct wlr_drm_layer *get_or_create_layer(struct wlr_drm_backend *drm, return layer; } +static void drm_connector_set_pending_page_flip(struct wlr_drm_connector *conn, + struct wlr_drm_page_flip *page_flip) { + if (conn->pending_page_flip != NULL) { + conn->pending_page_flip->conn = NULL; + } + conn->pending_page_flip = page_flip; +} + static bool drm_crtc_commit(struct wlr_drm_connector *conn, const struct wlr_drm_connector_state *state, uint32_t flags, bool test_only) { // Disallow atomic-only flags assert((flags & ~DRM_MODE_PAGE_FLIP_FLAGS) == 0); + struct wlr_drm_page_flip *page_flip = NULL; + if (flags & DRM_MODE_PAGE_FLIP_EVENT) { + page_flip = calloc(1, sizeof(*page_flip)); + if (page_flip == NULL) { + return false; + } + page_flip->conn = conn; + } + struct wlr_drm_backend *drm = conn->backend; struct wlr_drm_crtc *crtc = conn->crtc; - bool ok = drm->iface->crtc_commit(conn, state, flags, test_only); + bool ok = drm->iface->crtc_commit(conn, state, page_flip, flags, test_only); if (ok && !test_only) { drm_fb_clear(&crtc->primary->queued_fb); if (state->primary_fb != NULL) { @@ -434,6 +451,8 @@ static bool drm_crtc_commit(struct wlr_drm_connector *conn, wl_list_for_each(layer, &crtc->layers, link) { drm_fb_move(&layer->queued_fb, &layer->pending_fb); } + + drm_connector_set_pending_page_flip(conn, page_flip); } else { // The set_cursor() hook is a bit special: it's not really synchronized // to commit() or test(). Once set_cursor() returns true, the new @@ -446,6 +465,8 @@ static bool drm_crtc_commit(struct wlr_drm_connector *conn, wl_list_for_each(layer, &crtc->layers, link) { drm_fb_clear(&layer->pending_fb); } + + free(page_flip); } return ok; } @@ -732,16 +753,6 @@ bool drm_connector_commit_state(struct wlr_drm_connector *conn, if (!drm_connector_state_update_primary_fb(conn, &pending)) { goto out; } - - // wlr_drm_interface.crtc_commit will perform either a non-blocking - // page-flip, either a blocking modeset. When performing a blocking modeset - // we'll wait for all queued page-flips to complete, so we don't need this - // safeguard. - if (conn->pending_page_flip_crtc && !pending.modeset) { - wlr_drm_conn_log(conn, WLR_ERROR, "Failed to page-flip output: " - "a page-flip is already pending"); - goto out; - } } if (pending.base->committed & WLR_OUTPUT_STATE_LAYERS) { if (!drm_connector_set_pending_layer_fbs(conn, pending.base)) { @@ -759,7 +770,19 @@ bool drm_connector_commit_state(struct wlr_drm_connector *conn, } } - uint32_t flags = pending.active ? DRM_MODE_PAGE_FLIP_EVENT : 0; + uint32_t flags = 0; + if (pending.active) { + flags |= DRM_MODE_PAGE_FLIP_EVENT; + // wlr_drm_interface.crtc_commit will perform either a non-blocking + // page-flip, either a blocking modeset. When performing a blocking modeset + // we'll wait for all queued page-flips to complete, so we don't need this + // safeguard. + if (conn->pending_page_flip != NULL && !pending.modeset) { + wlr_drm_conn_log(conn, WLR_ERROR, "Failed to page-flip output: " + "a page-flip is already pending"); + goto out; + } + } if (pending.base->tearing_page_flip) { flags |= DRM_MODE_PAGE_FLIP_ASYNC; } @@ -777,9 +800,6 @@ bool drm_connector_commit_state(struct wlr_drm_connector *conn, conn->cursor_enabled = false; conn->crtc = NULL; } - if (flags & DRM_MODE_PAGE_FLIP_EVENT) { - conn->pending_page_flip_crtc = conn->crtc->id; - } out: drm_connector_state_finish(&pending); @@ -1029,7 +1049,7 @@ static void drm_connector_destroy_output(struct wlr_output *output) { dealloc_crtc(conn); conn->status = DRM_MODE_DISCONNECTED; - conn->pending_page_flip_crtc = 0; + drm_connector_set_pending_page_flip(conn, NULL); struct wlr_drm_mode *mode, *mode_tmp; wl_list_for_each_safe(mode, mode_tmp, &conn->output.modes, wlr_mode.link) { @@ -1660,22 +1680,19 @@ static int mhz_to_nsec(int mhz) { static void handle_page_flip(int fd, unsigned seq, unsigned tv_sec, unsigned tv_usec, unsigned crtc_id, void *data) { - struct wlr_drm_backend *drm = data; + struct wlr_drm_page_flip *page_flip = data; - bool found = false; - struct wlr_drm_connector *conn; - wl_list_for_each(conn, &drm->connectors, link) { - if (conn->pending_page_flip_crtc == crtc_id) { - found = true; - break; - } + struct wlr_drm_connector *conn = page_flip->conn; + if (conn != NULL) { + conn->pending_page_flip = NULL; } - if (!found) { - wlr_log(WLR_DEBUG, "Unexpected page-flip event for CRTC %u", crtc_id); + free(page_flip); + + if (conn == NULL) { return; } - conn->pending_page_flip_crtc = 0; + struct wlr_drm_backend *drm = conn->backend; if (conn->status != DRM_MODE_CONNECTED || conn->crtc == NULL) { wlr_drm_conn_log(conn, WLR_DEBUG, diff --git a/backend/drm/legacy.c b/backend/drm/legacy.c index ee64f78c..7663c0be 100644 --- a/backend/drm/legacy.c +++ b/backend/drm/legacy.c @@ -59,7 +59,7 @@ static bool legacy_crtc_test(struct wlr_drm_connector *conn, static bool legacy_crtc_commit(struct wlr_drm_connector *conn, const struct wlr_drm_connector_state *state, - uint32_t flags, bool test_only) { + struct wlr_drm_page_flip *page_flip, uint32_t flags, bool test_only) { if (!legacy_crtc_test(conn, state)) { return false; } @@ -181,7 +181,7 @@ static bool legacy_crtc_commit(struct wlr_drm_connector *conn, } if (drmModePageFlip(drm->fd, crtc->id, fb_id, - page_flags, drm)) { + page_flags, page_flip)) { wlr_drm_conn_log_errno(conn, WLR_ERROR, "drmModePageFlip failed"); return false; } diff --git a/backend/drm/libliftoff.c b/backend/drm/libliftoff.c index 5b828e17..d7f01d2a 100644 --- a/backend/drm/libliftoff.c +++ b/backend/drm/libliftoff.c @@ -301,8 +301,8 @@ static void update_layer_feedback(struct wlr_drm_backend *drm, } static bool crtc_commit(struct wlr_drm_connector *conn, - const struct wlr_drm_connector_state *state, uint32_t flags, - bool test_only) { + const struct wlr_drm_connector_state *state, + struct wlr_drm_page_flip *page_flip, uint32_t flags, bool test_only) { struct wlr_drm_backend *drm = conn->backend; struct wlr_output *output = &conn->output; struct wlr_drm_crtc *crtc = conn->crtc; @@ -455,7 +455,7 @@ static bool crtc_commit(struct wlr_drm_connector *conn, goto out; } - ret = drmModeAtomicCommit(drm->fd, req, flags, drm); + ret = drmModeAtomicCommit(drm->fd, req, flags, page_flip); if (ret != 0) { wlr_drm_conn_log_errno(conn, test_only ? WLR_DEBUG : WLR_ERROR, "Atomic commit failed"); diff --git a/include/backend/drm/drm.h b/include/backend/drm/drm.h index c7dd70f8..b121b3bc 100644 --- a/include/backend/drm/drm.h +++ b/include/backend/drm/drm.h @@ -129,6 +129,22 @@ struct wlr_drm_connector_state { struct wlr_drm_fb *primary_fb; }; +/** + * Per-page-flip tracking struct. + * + * We've asked for a state change in the kernel, and yet to receive a + * notification for its completion. Currently, the kernel only has a queue + * length of 1, and no way to modify your submissions after they're sent. + * + * However, we might have multiple in-flight page-flip events, for instance + * when performing a non-blocking commit followed by a blocking commit. In + * that case, conn will be set to NULL on the non-blocking commit to indicate + * that it's been superseded. + */ +struct wlr_drm_page_flip { + struct wlr_drm_connector *conn; +}; + struct wlr_drm_connector { struct wlr_output output; // only valid if status != DISCONNECTED @@ -153,14 +169,8 @@ struct wlr_drm_connector { struct wl_list link; // wlr_drm_backend.connectors - /* CRTC ID if a page-flip is pending, zero otherwise. - * - * We've asked for a state change in the kernel, and yet to receive a - * notification for its completion. Currently, the kernel only has a - * queue length of 1, and no way to modify your submissions after - * they're sent. - */ - uint32_t pending_page_flip_crtc; + // Last committed page-flip + struct wlr_drm_page_flip *pending_page_flip; }; struct wlr_drm_backend *get_drm_backend_from_backend( diff --git a/include/backend/drm/iface.h b/include/backend/drm/iface.h index 9916f383..dbe4c908 100644 --- a/include/backend/drm/iface.h +++ b/include/backend/drm/iface.h @@ -12,6 +12,7 @@ struct wlr_drm_connector; struct wlr_drm_crtc; struct wlr_drm_connector_state; struct wlr_drm_fb; +struct wlr_drm_page_flip; // Used to provide atomic or legacy DRM functions struct wlr_drm_interface { @@ -19,8 +20,8 @@ struct wlr_drm_interface { void (*finish)(struct wlr_drm_backend *drm); // Commit all pending changes on a CRTC. bool (*crtc_commit)(struct wlr_drm_connector *conn, - const struct wlr_drm_connector_state *state, uint32_t flags, - bool test_only); + const struct wlr_drm_connector_state *state, + struct wlr_drm_page_flip *page_flip, uint32_t flags, bool test_only); }; extern const struct wlr_drm_interface atomic_iface;