summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/drmmode_display.c107
-rw-r--r--src/nouveau_dri2.c89
-rw-r--r--src/nv_proto.h5
3 files changed, 191 insertions, 10 deletions
diff --git a/src/drmmode_display.c b/src/drmmode_display.c
index 75ef6dd..9e15c29 100644
--- a/src/drmmode_display.c
+++ b/src/drmmode_display.c
@@ -83,6 +83,21 @@ typedef struct {
drmmode_prop_ptr props;
} drmmode_output_private_rec, *drmmode_output_private_ptr;
+typedef struct {
+ drmmode_ptr drmmode;
+ unsigned old_fb_id;
+ int flip_count;
+ void *event_data;
+ unsigned int fe_frame;
+ unsigned int fe_tv_sec;
+ unsigned int fe_tv_usec;
+} drmmode_flipdata_rec, *drmmode_flipdata_ptr;
+
+typedef struct {
+ drmmode_flipdata_ptr flipdata;
+ Bool dispatch_me;
+} drmmode_flipevtcarrier_rec, *drmmode_flipevtcarrier_ptr;
+
static void drmmode_output_dpms(xf86OutputPtr output, int mode);
static drmmode_ptr
@@ -1245,13 +1260,17 @@ drmmode_cursor_init(ScreenPtr pScreen)
}
Bool
-drmmode_page_flip(DrawablePtr draw, PixmapPtr back, void *priv)
+drmmode_page_flip(DrawablePtr draw, PixmapPtr back, void *priv,
+ unsigned int ref_crtc_hw_id)
{
ScrnInfoPtr scrn = xf86Screens[draw->pScreen->myNum];
xf86CrtcConfigPtr config = XF86_CRTC_CONFIG_PTR(scrn);
drmmode_crtc_private_ptr crtc = config->crtc[0]->driver_private;
drmmode_ptr mode = crtc->drmmode;
int ret, i, old_fb_id;
+ int emitted = 0;
+ drmmode_flipdata_ptr flipdata;
+ drmmode_flipevtcarrier_ptr flipcarrier;
old_fb_id = mode->fb_id;
ret = drmModeAddFB(mode->fd, scrn->virtualX, scrn->virtualY,
@@ -1264,24 +1283,64 @@ drmmode_page_flip(DrawablePtr draw, PixmapPtr back, void *priv)
return FALSE;
}
+ flipdata = calloc(1, sizeof(drmmode_flipdata_rec));
+ if (!flipdata) {
+ xf86DrvMsg(scrn->scrnIndex, X_WARNING,
+ "flip queue: data alloc failed.\n");
+ goto error_undo;
+ }
+
+ flipdata->event_data = priv;
+ flipdata->drmmode = mode;
+
for (i = 0; i < config->num_crtc; i++) {
crtc = config->crtc[i]->driver_private;
if (!config->crtc[i]->enabled)
continue;
+ flipdata->flip_count++;
+
+ flipcarrier = calloc(1, sizeof(drmmode_flipevtcarrier_rec));
+ if (!flipcarrier) {
+ xf86DrvMsg(scrn->scrnIndex, X_WARNING,
+ "flip queue: carrier alloc failed.\n");
+ if (emitted == 0)
+ free(flipdata);
+ goto error_undo;
+ }
+
+ /* Only the reference crtc will finally deliver its page flip
+ * completion event. All other crtc's events will be discarded.
+ */
+ flipcarrier->dispatch_me = ((1 << i) == ref_crtc_hw_id);
+ flipcarrier->flipdata = flipdata;
+
ret = drmModePageFlip(mode->fd, crtc->mode_crtc->crtc_id,
- mode->fb_id, 0, priv);
+ mode->fb_id, DRM_MODE_PAGE_FLIP_EVENT,
+ flipcarrier);
if (ret) {
xf86DrvMsg(scrn->scrnIndex, X_WARNING,
"flip queue failed: %s\n", strerror(errno));
- return FALSE;
+
+ free(flipcarrier);
+ if (emitted == 0)
+ free(flipdata);
+ goto error_undo;
}
+
+ emitted++;
}
- drmModeRmFB(mode->fd, old_fb_id);
+ /* Will release old fb after all crtc's completed flip. */
+ flipdata->old_fb_id = old_fb_id;
return TRUE;
+
+error_undo:
+ drmModeRmFB(mode->fd, mode->fb_id);
+ mode->fb_id = old_fb_id;
+ return FALSE;
}
#ifdef HAVE_LIBUDEV
@@ -1347,6 +1406,42 @@ drmmode_uevent_fini(ScrnInfoPtr scrn)
}
static void
+drmmode_flip_handler(int fd, unsigned int frame, unsigned int tv_sec,
+ unsigned int tv_usec, void *event_data)
+{
+ drmmode_flipevtcarrier_ptr flipcarrier = event_data;
+ drmmode_flipdata_ptr flipdata = flipcarrier->flipdata;
+ drmmode_ptr drmmode = flipdata->drmmode;
+
+ /* Is this the event whose info shall be delivered to higher level? */
+ if (flipcarrier->dispatch_me) {
+ /* Yes: Cache msc, ust for later delivery. */
+ flipdata->fe_frame = frame;
+ flipdata->fe_tv_sec = tv_sec;
+ flipdata->fe_tv_usec = tv_usec;
+ }
+ free(flipcarrier);
+
+ /* Last crtc completed flip? */
+ flipdata->flip_count--;
+ if (flipdata->flip_count > 0)
+ return;
+
+ /* Release framebuffer */
+ drmModeRmFB(drmmode->fd, flipdata->old_fb_id);
+
+ if (flipdata->event_data == NULL) {
+ free(flipdata);
+ return;
+ }
+
+ /* Deliver cached msc, ust from reference crtc to flip event handler */
+ nouveau_dri2_flip_event_handler(flipdata->fe_frame, flipdata->fe_tv_sec,
+ flipdata->fe_tv_usec, flipdata->event_data);
+ free(flipdata);
+}
+
+static void
drmmode_wakeup_handler(pointer data, int err, pointer p)
{
ScrnInfoPtr scrn = data;
@@ -1376,6 +1471,10 @@ drmmode_screen_init(ScreenPtr pScreen)
/* Plug in a vblank event handler */
drmmode->event_context.version = DRM_EVENT_CONTEXT_VERSION;
drmmode->event_context.vblank_handler = nouveau_dri2_vblank_handler;
+
+ /* Plug in a pageflip completion event handler */
+ drmmode->event_context.page_flip_handler = drmmode_flip_handler;
+
AddGeneralSocket(drmmode->fd);
/* Register a wakeup handler to get informed on DRM events */
diff --git a/src/nouveau_dri2.c b/src/nouveau_dri2.c
index 5b62425..acef08a 100644
--- a/src/nouveau_dri2.c
+++ b/src/nouveau_dri2.c
@@ -140,6 +140,7 @@ struct nouveau_dri2_vblank_state {
DRI2BufferPtr src;
DRI2SwapEventPtr func;
void *data;
+ unsigned int frame;
};
static Bool
@@ -225,6 +226,18 @@ nouveau_dri2_finish_swap(DrawablePtr draw, unsigned int frame,
REGION_INIT(0, &reg, (&(BoxRec){ 0, 0, draw->width, draw->height }), 0);
REGION_TRANSLATE(0, &reg, draw->x, draw->y);
+ /* Main crtc for this drawable shall finally deliver pageflip event. */
+ unsigned int ref_crtc_hw_id = nv_window_belongs_to_crtc(scrn, draw->x,
+ draw->y,
+ draw->width,
+ draw->height);
+
+ /* Whenever first crtc is involved, choose it as reference, as
+ * its vblank event triggered this swap.
+ */
+ if (ref_crtc_hw_id & 1)
+ ref_crtc_hw_id = 1;
+
/* Throttle on the previous frame before swapping */
nouveau_bo_map(dst_bo, NOUVEAU_BO_RD);
nouveau_bo_unmap(dst_bo);
@@ -249,7 +262,7 @@ nouveau_dri2_finish_swap(DrawablePtr draw, unsigned int frame,
if (DRI2CanFlip(draw)) {
type = DRI2_FLIP_COMPLETE;
- ret = drmmode_page_flip(draw, src_pix, s);
+ ret = drmmode_page_flip(draw, src_pix, s, ref_crtc_hw_id);
if (!ret)
goto out;
}
@@ -258,6 +271,10 @@ nouveau_dri2_finish_swap(DrawablePtr draw, unsigned int frame,
SWAP(nouveau_pixmap(dst_pix)->bo, nouveau_pixmap(src_pix)->bo);
DamageRegionProcessPending(draw);
+
+ /* If it is a page flip, finish it in the flip event handler. */
+ if (type == DRI2_FLIP_COMPLETE)
+ return;
} else {
type = DRI2_BLIT_COMPLETE;
@@ -292,7 +309,7 @@ nouveau_dri2_schedule_swap(ClientPtr client, DrawablePtr draw,
DRI2SwapEventPtr func, void *data)
{
struct nouveau_dri2_vblank_state *s;
- CARD64 current_msc;
+ CARD64 current_msc, expect_msc;
int ret;
/* Initialize a swap structure */
@@ -301,7 +318,7 @@ nouveau_dri2_schedule_swap(ClientPtr client, DrawablePtr draw,
return FALSE;
*s = (struct nouveau_dri2_vblank_state)
- { SWAP, client, draw->id, dst, src, func, data };
+ { SWAP, client, draw->id, dst, src, func, data, 0 };
if (can_sync_to_vblank(draw)) {
/* Get current sequence */
@@ -319,10 +336,10 @@ nouveau_dri2_schedule_swap(ClientPtr client, DrawablePtr draw,
ret = nouveau_wait_vblank(draw, DRM_VBLANK_ABSOLUTE |
DRM_VBLANK_EVENT,
max(current_msc, *target_msc - 1),
- NULL, NULL, s);
+ &expect_msc, NULL, s);
if (ret)
goto fail;
-
+ s->frame = (unsigned int) expect_msc & 0xffffffff;
} else {
/* We can't/don't want to sync to vblank, just swap. */
nouveau_dri2_finish_swap(draw, 0, 0, 0, s);
@@ -426,6 +443,68 @@ nouveau_dri2_vblank_handler(int fd, unsigned int frame,
}
}
+void
+nouveau_dri2_flip_event_handler(unsigned int frame, unsigned int tv_sec,
+ unsigned int tv_usec, void *event_data)
+{
+ struct nouveau_dri2_vblank_state *flip = event_data;
+ DrawablePtr draw;
+ ScreenPtr screen;
+ ScrnInfoPtr scrn;
+ int status;
+ PixmapPtr pixmap;
+
+ status = dixLookupDrawable(&draw, flip->draw, serverClient,
+ M_ANY, DixWriteAccess);
+ if (status != Success) {
+ free(flip);
+ return;
+ }
+
+ screen = draw->pScreen;
+ scrn = xf86Screens[screen->myNum];
+
+ pixmap = screen->GetScreenPixmap(screen);
+ xf86DrvMsgVerb(scrn->scrnIndex, X_INFO, 4,
+ "%s: flipevent : width %d x height %d : msc %d : ust = %d.%06d\n",
+ __func__, pixmap->drawable.width, pixmap->drawable.height,
+ frame, tv_sec, tv_usec);
+
+ /* We assume our flips arrive in order, so we don't check the frame */
+ switch (flip->action) {
+ case SWAP:
+ /* Check for too small vblank count of pageflip completion,
+ * taking wraparound into account. This usually means some
+ * defective kms pageflip completion, causing wrong (msc, ust)
+ * return values and possible visual corruption.
+ * Skip test for frame == 0, as this is a valid constant value
+ * reported by all Linux kernels at least up to Linux 3.0.
+ */
+ if ((frame != 0) &&
+ (frame < flip->frame) && (flip->frame - frame < 5)) {
+ xf86DrvMsg(scrn->scrnIndex, X_WARNING,
+ "%s: Pageflip has impossible msc %d < target_msc %d\n",
+ __func__, frame, flip->frame);
+ /* All-Zero values signal failure of (msc, ust)
+ * timestamping to client.
+ */
+ frame = tv_sec = tv_usec = 0;
+ }
+
+ DRI2SwapComplete(flip->client, draw, frame, tv_sec, tv_usec,
+ DRI2_FLIP_COMPLETE, flip->func,
+ flip->data);
+ break;
+ default:
+ xf86DrvMsg(scrn->scrnIndex, X_WARNING,
+ "%s: unknown vblank event received\n", __func__);
+ /* Unknown type */
+ break;
+ }
+
+ free(flip);
+}
+
Bool
nouveau_dri2_init(ScreenPtr pScreen)
{
diff --git a/src/nv_proto.h b/src/nv_proto.h
index 0b8e513..8bf2fc1 100644
--- a/src/nv_proto.h
+++ b/src/nv_proto.h
@@ -7,7 +7,8 @@ void drmmode_adjust_frame(ScrnInfoPtr pScrn, int x, int y, int flags);
void drmmode_remove_fb(ScrnInfoPtr pScrn);
Bool drmmode_cursor_init(ScreenPtr pScreen);
void drmmode_fbcon_copy(ScreenPtr pScreen);
-Bool drmmode_page_flip(DrawablePtr draw, PixmapPtr back, void *priv);
+Bool drmmode_page_flip(DrawablePtr draw, PixmapPtr back, void *priv,
+ unsigned int ref_crtc_hw_id);
void drmmode_screen_init(ScreenPtr pScreen);
void drmmode_screen_fini(ScreenPtr pScreen);
@@ -26,6 +27,8 @@ Bool nouveau_allocate_surface(ScrnInfoPtr scrn, int width, int height,
void nouveau_dri2_vblank_handler(int fd, unsigned int frame,
unsigned int tv_sec, unsigned int tv_usec,
void *event_data);
+void nouveau_dri2_flip_event_handler(unsigned int frame, unsigned int tv_sec,
+ unsigned int tv_usec, void *event_data);
Bool nouveau_dri2_init(ScreenPtr pScreen);
void nouveau_dri2_fini(ScreenPtr pScreen);