forked from enlightenment/efl
ecore_drm: Change page flipping logic so we can't tear
Summary: Previously if we ever tried to queue up two page flips in less than a retrace interval (which can easily happen since the evas clock isn't based on vblank) we'd give up on ever using page flips again, and tear on every screen update. This fixes that by using a vblank callback for custom ticks and using page flips whenever possible. If a page flip fails it means a page flip raced with the vblank ticker, so we need to queue up that frame when the current page flip completes. This ensures that while we might drop interim frames, we will never lose the most recent. Now it should only be possible to tear if two ticks fire during the wait for a page flip to complete. This would result in rendering taking place in the front buffer. I don't think this can happen, but an error is logged if it does. Reviewers: zmike, devilhorns Subscribers: cedric, jpeg Differential Revision: https://phab.enlightenment.org/D3594
This commit is contained in:
parent
1f576da49e
commit
5d7271683b
|
@ -671,10 +671,12 @@ EAPI void ecore_drm_fb_dirty(Ecore_Drm_Fb *fb, Eina_Rectangle *rects, unsigned i
|
|||
* @param dev The Ecore_Drm_Device to use
|
||||
* @param fb The Ecore_Drm_Fb to make the current framebuffer
|
||||
*
|
||||
* @deprecated just call ecore_drm_fb_send() instead.
|
||||
*
|
||||
* @ingroup Ecore_Drm_Fb_Group
|
||||
* @since 1.14
|
||||
*/
|
||||
EAPI void ecore_drm_fb_set(Ecore_Drm_Device *dev, Ecore_Drm_Fb *fb);
|
||||
EINA_DEPRECATED EAPI void ecore_drm_fb_set(Ecore_Drm_Device *dev, Ecore_Drm_Fb *fb);
|
||||
|
||||
/**
|
||||
* Send an Ecore_Drm_Fb to the Ecore_Drm_Device
|
||||
|
|
|
@ -10,93 +10,86 @@
|
|||
((x) >= (xx)) && ((y) >= (yy)))
|
||||
|
||||
static Eina_List *drm_devices;
|
||||
static int flip_count = 0;
|
||||
static int ticking = 0;
|
||||
|
||||
static void
|
||||
_ecore_drm_tick_schedule(Ecore_Drm_Device *dev)
|
||||
{
|
||||
drmVBlank vbl;
|
||||
|
||||
if (!ticking) return;
|
||||
|
||||
vbl.request.type = (DRM_VBLANK_RELATIVE | DRM_VBLANK_EVENT);
|
||||
vbl.request.sequence = 1;
|
||||
vbl.request.signal = (unsigned long)dev;
|
||||
drmWaitVBlank(dev->drm.fd, &vbl);
|
||||
}
|
||||
|
||||
static void
|
||||
_ecore_drm_tick_begin(void *data)
|
||||
{
|
||||
ticking = 1;
|
||||
_ecore_drm_tick_schedule(data);
|
||||
}
|
||||
|
||||
static void
|
||||
_ecore_drm_tick_end(void *data EINA_UNUSED)
|
||||
{
|
||||
ticking = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
_ecore_drm_tick_source_set(Ecore_Drm_Device *dev)
|
||||
{
|
||||
ecore_animator_custom_source_tick_begin_callback_set
|
||||
(_ecore_drm_tick_begin, dev);
|
||||
ecore_animator_custom_source_tick_end_callback_set
|
||||
(_ecore_drm_tick_end, dev);
|
||||
ecore_animator_source_set(ECORE_ANIMATOR_SOURCE_CUSTOM);
|
||||
}
|
||||
|
||||
static void
|
||||
_ecore_drm_device_cb_page_flip(int fd EINA_UNUSED, unsigned int frame EINA_UNUSED, unsigned int sec EINA_UNUSED, unsigned int usec EINA_UNUSED, void *data)
|
||||
{
|
||||
Ecore_Drm_Pageflip_Callback *cb;
|
||||
Ecore_Drm_Output *output = data;
|
||||
Ecore_Drm_Fb *next;
|
||||
|
||||
/* DBG("Drm Page Flip Event"); */
|
||||
|
||||
if (!(cb = data)) return;
|
||||
|
||||
flip_count++;
|
||||
if (flip_count < cb->count) return;
|
||||
|
||||
cb->dev->current = cb->dev->next;
|
||||
cb->dev->next = NULL;
|
||||
|
||||
flip_count = 0;
|
||||
if (cb->func) cb->func(cb->data);
|
||||
/* free(cb); */
|
||||
|
||||
/* Ecore_Drm_Output *output; */
|
||||
|
||||
/* DBG("Drm Page Flip Event"); */
|
||||
|
||||
/* if (!(output = data)) return; */
|
||||
|
||||
/* if (output->pending_flip) */
|
||||
/* { */
|
||||
/* if (output->dev->current) */
|
||||
/* ecore_drm_output_fb_release(output, output->dev->current); */
|
||||
/* output->dev->current = output->dev->next; */
|
||||
/* output->dev->next = NULL; */
|
||||
/* } */
|
||||
|
||||
/* output->pending_flip = EINA_FALSE; */
|
||||
/* if (output->pending_destroy) */
|
||||
/* { */
|
||||
/* output->pending_destroy = EINA_FALSE; */
|
||||
/* ecore_drm_output_free(output); */
|
||||
/* } */
|
||||
/* else if (!output->pending_vblank) */
|
||||
/* ecore_drm_output_repaint(output); */
|
||||
if (output->pending_destroy)
|
||||
{
|
||||
ecore_drm_output_free(output);
|
||||
return;
|
||||
}
|
||||
/* We were unable to queue a page on the last flip attempt, so we'll
|
||||
* try again now. */
|
||||
next = output->next;
|
||||
if (next)
|
||||
{
|
||||
output->next = NULL;
|
||||
ecore_drm_fb_send(output->dev, next, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_ecore_drm_device_cb_vblank(int fd EINA_UNUSED, unsigned int frame EINA_UNUSED, unsigned int sec EINA_UNUSED, unsigned int usec EINA_UNUSED, void *data)
|
||||
{
|
||||
Ecore_Drm_Sprite *sprite;
|
||||
Ecore_Drm_Output *output;
|
||||
|
||||
/* DBG("Drm VBlank Event"); */
|
||||
|
||||
if (!(sprite = data)) return;
|
||||
|
||||
output = sprite->output;
|
||||
output->pending_vblank = EINA_FALSE;
|
||||
|
||||
ecore_drm_output_fb_release(output, sprite->current_fb);
|
||||
sprite->current_fb = sprite->next_fb;
|
||||
sprite->next_fb = NULL;
|
||||
|
||||
if (!output->pending_flip) _ecore_drm_output_frame_finish(output);
|
||||
ecore_animator_custom_tick();
|
||||
if (ticking) _ecore_drm_tick_schedule(data);
|
||||
}
|
||||
|
||||
#if 0
|
||||
static Eina_Bool
|
||||
_ecore_drm_device_cb_idle(void *data)
|
||||
_cb_drm_event_handle(void *data, Ecore_Fd_Handler *hdlr EINA_UNUSED)
|
||||
{
|
||||
Ecore_Drm_Device *dev;
|
||||
Ecore_Drm_Output *output;
|
||||
Eina_List *l;
|
||||
Ecore_Drm_Device *dev = data;
|
||||
int err;
|
||||
|
||||
if (!(dev = data)) return ECORE_CALLBACK_CANCEL;
|
||||
|
||||
if (!dev->active) return ECORE_CALLBACK_RENEW;
|
||||
|
||||
EINA_LIST_FOREACH(dev->outputs, l, output)
|
||||
err = drmHandleEvent(dev->drm.fd, &dev->drm_ctx);
|
||||
if (err)
|
||||
{
|
||||
if ((!output->enabled) || (!output->need_repaint)) continue;
|
||||
if (output->repaint_scheduled) continue;
|
||||
_ecore_drm_output_repaint_start(output);
|
||||
ERR("drmHandleEvent failed to read an event");
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
return ECORE_CALLBACK_RENEW;
|
||||
return EINA_TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
_ecore_drm_device_cb_output_event(const char *device EINA_UNUSED, Eeze_Udev_Event event EINA_UNUSED, void *data, Eeze_Udev_Watch *watch EINA_UNUSED)
|
||||
|
@ -379,6 +372,10 @@ ecore_drm_device_open(Ecore_Drm_Device *dev)
|
|||
eeze_udev_watch_add(EEZE_UDEV_TYPE_DRM, events,
|
||||
_ecore_drm_device_cb_output_event, dev);
|
||||
|
||||
dev->drm.hdlr =
|
||||
ecore_main_fd_handler_add(dev->drm.fd, ECORE_FD_READ,
|
||||
_cb_drm_event_handle, dev, NULL, NULL);
|
||||
|
||||
/* dev->drm.idler = */
|
||||
/* ecore_idle_enterer_add(_ecore_drm_device_cb_idle, dev); */
|
||||
|
||||
|
@ -546,6 +543,7 @@ ecore_drm_device_software_setup(Ecore_Drm_Device *dev)
|
|||
DBG("\tSize: %d", dev->dumb[i]->size);
|
||||
DBG("\tW: %d\tH: %d", dev->dumb[i]->w, dev->dumb[i]->h);
|
||||
}
|
||||
_ecore_drm_tick_source_set(dev);
|
||||
|
||||
return EINA_TRUE;
|
||||
|
||||
|
|
|
@ -167,7 +167,15 @@ ecore_drm_fb_dirty(Ecore_Drm_Fb *fb, Eina_Rectangle *rects, unsigned int count)
|
|||
}
|
||||
|
||||
EAPI void
|
||||
ecore_drm_fb_set(Ecore_Drm_Device *dev, Ecore_Drm_Fb *fb)
|
||||
ecore_drm_fb_set(Ecore_Drm_Device *dev EINA_UNUSED, Ecore_Drm_Fb *fb EINA_UNUSED)
|
||||
{
|
||||
/* ecore_drm_fb_set no longer has any functionality distinct from
|
||||
* ecore_drm_fb_send so it is reduced to NO-OP to prevent messing up state
|
||||
*/
|
||||
}
|
||||
|
||||
EAPI void
|
||||
ecore_drm_fb_send(Ecore_Drm_Device *dev, Ecore_Drm_Fb *fb, Ecore_Drm_Pageflip_Cb func EINA_UNUSED, void *data EINA_UNUSED)
|
||||
{
|
||||
Ecore_Drm_Output *output;
|
||||
Eina_List *l;
|
||||
|
@ -185,88 +193,58 @@ ecore_drm_fb_set(Ecore_Drm_Device *dev, Ecore_Drm_Fb *fb)
|
|||
}
|
||||
}
|
||||
|
||||
if (!dev->next) dev->next = fb;
|
||||
if (!dev->next) return;
|
||||
if (!dev->outputs) return;
|
||||
|
||||
EINA_LIST_FOREACH(dev->outputs, l, output)
|
||||
{
|
||||
int x = 0, y = 0;
|
||||
|
||||
if ((!output->enabled) || (!output->current_mode)) continue;
|
||||
|
||||
if (output->next) WRN("fb reused too soon, tearing may be visible");
|
||||
|
||||
/* If we changed display parameters or haven't displayed anything
|
||||
* yet we need to do a SetCrtc
|
||||
*/
|
||||
if ((!output->current) ||
|
||||
(output->current->stride != fb->stride))
|
||||
{
|
||||
int x = 0, y = 0;
|
||||
|
||||
if (!output->cloned)
|
||||
{
|
||||
x = output->x;
|
||||
y = output->y;
|
||||
}
|
||||
|
||||
if ((!dev->current) ||
|
||||
(dev->current->stride != dev->next->stride))
|
||||
{
|
||||
if (drmModeSetCrtc(dev->drm.fd, output->crtc_id, dev->next->id,
|
||||
if (drmModeSetCrtc(dev->drm.fd, output->crtc_id, fb->id,
|
||||
x, y, &output->conn_id, 1,
|
||||
&output->current_mode->info))
|
||||
{
|
||||
ERR("Failed to set Mode %dx%d for Output %s: %m",
|
||||
output->current_mode->width, output->current_mode->height,
|
||||
output->name);
|
||||
continue;
|
||||
}
|
||||
output->current = fb;
|
||||
output->next = NULL;
|
||||
|
||||
/* TODO: set dpms on ?? */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EAPI void
|
||||
ecore_drm_fb_send(Ecore_Drm_Device *dev, Ecore_Drm_Fb *fb, Ecore_Drm_Pageflip_Cb func, void *data)
|
||||
{
|
||||
Ecore_Drm_Output *output;
|
||||
Eina_List *l;
|
||||
Ecore_Drm_Pageflip_Callback *cb;
|
||||
|
||||
EINA_SAFETY_ON_NULL_RETURN(dev);
|
||||
EINA_SAFETY_ON_NULL_RETURN(fb);
|
||||
EINA_SAFETY_ON_NULL_RETURN(func);
|
||||
|
||||
if (eina_list_count(dev->outputs) < 1) return;
|
||||
|
||||
if (fb->pending_flip) return;
|
||||
|
||||
if (!(cb = calloc(1, sizeof(Ecore_Drm_Pageflip_Callback))))
|
||||
return;
|
||||
|
||||
cb->dev = dev;
|
||||
cb->func = func;
|
||||
cb->data = data;
|
||||
|
||||
EINA_LIST_FOREACH(dev->outputs, l, output)
|
||||
if (output->enabled) cb->count++;
|
||||
|
||||
EINA_LIST_FOREACH(dev->outputs, l, output)
|
||||
{
|
||||
if ((!output->enabled) || (!output->current_mode)) continue;
|
||||
|
||||
if (drmModePageFlip(dev->drm.fd, output->crtc_id, fb->id,
|
||||
DRM_MODE_PAGE_FLIP_EVENT, cb) < 0)
|
||||
{
|
||||
ERR("Cannot flip crtc %u for connector %u: %m",
|
||||
output->crtc_id, output->conn_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
fb->pending_flip = EINA_TRUE;
|
||||
}
|
||||
|
||||
while (fb->pending_flip)
|
||||
/* The normal case: We do a flip which waits for vblank and
|
||||
* posts an event.
|
||||
*/
|
||||
if (drmModePageFlip(dev->drm.fd, output->crtc_id, fb->id,
|
||||
DRM_MODE_PAGE_FLIP_EVENT, output) < 0)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = drmHandleEvent(dev->drm.fd, &dev->drm_ctx);
|
||||
if (ret < 0)
|
||||
{
|
||||
ERR("drmHandleEvent Failed");
|
||||
free(cb);
|
||||
break;
|
||||
}
|
||||
/* Failure to flip - likely there's already a flip
|
||||
* queued, and we can't cancel, so just store this
|
||||
* fb for later and it'll be queued in the flip
|
||||
* handler */
|
||||
DBG("flip crtc %u for connector %u failed, re-queued",
|
||||
output->crtc_id, output->conn_id);
|
||||
output->next = fb;
|
||||
continue;
|
||||
}
|
||||
output->current = fb;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -150,6 +150,7 @@ struct _Ecore_Drm_Output
|
|||
} edid;
|
||||
|
||||
Ecore_Drm_Backlight *backlight;
|
||||
Ecore_Drm_Fb *current, *next;
|
||||
|
||||
Eina_Bool primary : 1;
|
||||
Eina_Bool connected : 1;
|
||||
|
|
|
@ -8,23 +8,6 @@
|
|||
#define GREEN_MASK 0x00ff00
|
||||
#define BLUE_MASK 0x0000ff
|
||||
|
||||
static void
|
||||
_evas_outbuf_cb_pageflip(void *data)
|
||||
{
|
||||
Outbuf *ob;
|
||||
Ecore_Drm_Fb *fb;
|
||||
|
||||
if (!(ob = data)) return;
|
||||
|
||||
/* DBG("Outbuf Pagelip Done"); */
|
||||
|
||||
if ((fb = ob->priv.buffer[ob->priv.curr]))
|
||||
fb->pending_flip = EINA_FALSE;
|
||||
|
||||
ob->priv.last = ob->priv.curr;
|
||||
ob->priv.curr = (ob->priv.curr + 1) % ob->priv.num;
|
||||
}
|
||||
|
||||
static void
|
||||
_evas_outbuf_buffer_swap(Outbuf *ob, Eina_Rectangle *rects, unsigned int count)
|
||||
{
|
||||
|
@ -35,11 +18,11 @@ _evas_outbuf_buffer_swap(Outbuf *ob, Eina_Rectangle *rects, unsigned int count)
|
|||
/* mark the fb as dirty */
|
||||
ecore_drm_fb_dirty(buff, rects, count);
|
||||
|
||||
/* if this buffer is not valid, we need to set it */
|
||||
ecore_drm_fb_set(ob->info->info.dev, buff);
|
||||
|
||||
/* send this buffer to the crtc */
|
||||
ecore_drm_fb_send(ob->info->info.dev, buff, _evas_outbuf_cb_pageflip, ob);
|
||||
ecore_drm_fb_send(ob->info->info.dev, buff, NULL, NULL);
|
||||
|
||||
ob->priv.last = ob->priv.curr;
|
||||
ob->priv.curr = (ob->priv.curr + 1) % ob->priv.num;
|
||||
}
|
||||
|
||||
Outbuf *
|
||||
|
@ -62,8 +45,8 @@ evas_outbuf_setup(Evas_Engine_Info_Drm *info, int w, int h)
|
|||
ob->destination_alpha = info->info.destination_alpha;
|
||||
ob->vsync = info->info.vsync;
|
||||
|
||||
/* default to double-buffer */
|
||||
ob->priv.num = 2;
|
||||
/* we must triple-buffer to prevent problems with the page flip handler */
|
||||
ob->priv.num = 3;
|
||||
|
||||
/* check for buffer override */
|
||||
if ((num = getenv("EVAS_DRM_BUFFERS")))
|
||||
|
@ -98,7 +81,7 @@ evas_outbuf_setup(Evas_Engine_Info_Drm *info, int w, int h)
|
|||
}
|
||||
|
||||
/* set the front buffer to be the one on the crtc */
|
||||
ecore_drm_fb_set(info->info.dev, ob->priv.buffer[0]);
|
||||
ecore_drm_fb_send(info->info.dev, ob->priv.buffer[0], NULL, NULL);
|
||||
|
||||
return ob;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue