<div dir="ltr">My knowledge of mesa and of the wl_display_prepare_read stuff is a bit lacking. From what I can see, it looks good.<br></div><div class="gmail_extra"><br><br><div class="gmail_quote">On Fri, Oct 25, 2013 at 9:50 AM, Neil Roberts <span dir="ltr"><<a href="mailto:neil@linux.intel.com" target="_blank">neil@linux.intel.com</a>></span> wrote:<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Ok, here is version 4 of the patch taking into account the discussion<br>
with Jason Ekstrand. The assumption is that if we have enough buffer<br>
slots then we should always get a release event immediately after one<br>
of the attaches. That means we can just rely on sending a sync request<br>
after the commit in order to get a buffer release and we don't need to<br>
bother with the request to disable the queuing mechanism.<br>
<br>
The previous version of the patch would block in a loop calling<br>
wl_dispatch_queue if it couldn't find a buffer. This is only a<br>
sensible option if we know that the compositor isn't queueing the<br>
release events. If not this loop would just block indefinitely. If the<br>
theory about getting release events is correct then we should never<br>
actually hit this loop so it probably doesn't really matter what it<br>
does. However, I didn't like the idea of having a loop there that<br>
would just block forever so I changed it to poll the compositor with a<br>
sync request every 10ms in order to force it to flush the queue. It<br>
prints a warning if this case is hit so that we will know there is a<br>
problem.<br>
<br>
I made the change to make it use 4 buffer slots in this patch and<br>
tested that it does use exactly all 4 of them when the application is<br>
fullscreen. This does work and it doesn't hit the polling path. I<br>
guess we could change to be five in order to cope with the subsurface<br>
case but I'm a bit reluctant to do that because it seems like quite a<br>
corner case and maybe it's better to just let it hit the warning path<br>
in that case.<br>
<br>
In the previous versions of the patch it would only do a sync request<br>
if the swap interval is zero. In this version I've changed it so that<br>
it always installs it. This is necessary because if an application is<br>
doing swap interval 1 but isn't installing a frame callback it would<br>
end up rendering and calling get_back_bo before we've handled any data<br>
from the compositor and it would use a redundant third buffer.<br>
<br>
Regards,<br>
- Neil<br>
<br>
------- >8 --------------- (use git am --scissors to automatically chop here)<br>
<br>
The Wayland EGL platform now respects the eglSwapInterval value. The value is<br>
clamped to either 0 or 1 because it is difficult (and probably not useful) to<br>
sync to more than 1 redraw.<br>
<br>
The main change is that if the swap interval is 0 then Mesa won't install a<br>
frame callback so that eglSwapBuffers can be executed as often as necessary.<br>
However it now always does a sync request after the swap buffers and blocks<br>
until this is complete in get_back_bo. The compositor is likely to send a<br>
release event while processing the new buffer attach and this makes sure we<br>
will receive that before deciding whether to allocate a new buffer. This is<br>
done even if the application is using swap interval 1 because otherwise if the<br>
application is not installing its own frame callback it may end up calling<br>
get_back_bo before we've handled any data from the compositor and it would end<br>
up using a redundant extra buffer.<br>
<br>
If there are no buffers available then instead of returning with an error,<br>
get_back_bo will now poll the compositor by repeatedly sending sync requests<br>
every 10ms. This is a last resort and in theory this shouldn't happen because<br>
there should be no reason for the compositor to hold on to more than three<br>
buffers. That means whenever we attach the fourth buffer we should always get<br>
an immediate release event which should come in with the notification for the<br>
first sync request that we are throttled to.<br>
<br>
When the compositor is directly scanning out from the application's buffer it<br>
may end up holding on to three buffers. These are the one that is is currently<br>
scanning out from, one that has been given to DRM as the next buffer to flip<br>
to, and one that has been attached and will be given to DRM as soon as the<br>
previous flip completes. When we attach a fourth buffer to the compositor it<br>
should replace that third buffer so we should get a release event immediately<br>
after that. This patch therefore also changes the number of buffer slots to 4<br>
so that we can accomodate that situation.<br>
<br>
If DRM eventually gets a way to cancel a pending page flip then the compositors<br>
can be changed to only need to hold on to two buffers and this value can be<br>
put back to 3.<br>
<br>
This also moves the vblank configuration defines from platform_x11.c to the<br>
common egl_dri2.h header so they can be shared by both platforms.<br>
---<br>
src/egl/drivers/dri2/egl_dri2.h | 9 +-<br>
src/egl/drivers/dri2/platform_wayland.c | 204 +++++++++++++++++++++++++++++---<br>
src/egl/drivers/dri2/platform_x11.c | 6 -<br>
3 files changed, 193 insertions(+), 26 deletions(-)<br>
<br>
diff --git a/src/egl/drivers/dri2/egl_dri2.h b/src/egl/drivers/dri2/egl_dri2.h<br>
index 7a2e098..7de5916 100644<br>
--- a/src/egl/drivers/dri2/egl_dri2.h<br>
+++ b/src/egl/drivers/dri2/egl_dri2.h<br>
@@ -174,6 +174,7 @@ struct dri2_egl_surface<br>
int dx;<br>
int dy;<br>
struct wl_callback *frame_callback;<br>
+ struct wl_callback *sync_callback;<br>
int format;<br>
#endif<br>
<br>
@@ -194,7 +195,7 @@ struct dri2_egl_surface<br>
#endif<br>
int locked;<br>
int age;<br>
- } color_buffers[3], *back, *current;<br>
+ } color_buffers[4], *back, *current;<br>
#endif<br>
<br>
#ifdef HAVE_ANDROID_PLATFORM<br>
@@ -220,6 +221,12 @@ struct dri2_egl_image<br>
__DRIimage *dri_image;<br>
};<br>
<br>
+/* From xmlpool/options.h, user exposed so should be stable */<br>
+#define DRI_CONF_VBLANK_NEVER 0<br>
+#define DRI_CONF_VBLANK_DEF_INTERVAL_0 1<br>
+#define DRI_CONF_VBLANK_DEF_INTERVAL_1 2<br>
+#define DRI_CONF_VBLANK_ALWAYS_SYNC 3<br>
+<br>
/* standard typecasts */<br>
_EGL_DRIVER_STANDARD_TYPECASTS(dri2_egl)<br>
_EGL_DRIVER_TYPECAST(dri2_egl_image, _EGLImage, obj)<br>
diff --git a/src/egl/drivers/dri2/platform_wayland.c b/src/egl/drivers/dri2/platform_wayland.c<br>
index bb8d113..1bf96d7 100644<br>
--- a/src/egl/drivers/dri2/platform_wayland.c<br>
+++ b/src/egl/drivers/dri2/platform_wayland.c<br>
@@ -34,6 +34,7 @@<br>
#include <unistd.h><br>
#include <fcntl.h><br>
#include <xf86drm.h><br>
+#include <poll.h><br>
<br>
#include "egl_dri2.h"<br>
<br>
@@ -183,8 +184,16 @@ dri2_create_window_surface(_EGLDriver *drv, _EGLDisplay *disp,<br>
_EGLConfig *conf, EGLNativeWindowType window,<br>
const EGLint *attrib_list)<br>
{<br>
- return dri2_create_surface(drv, disp, EGL_WINDOW_BIT, conf,<br>
+ struct dri2_egl_display *dri2_dpy = dri2_egl_display(disp);<br>
+ _EGLSurface *surf;<br>
+<br>
+ surf = dri2_create_surface(drv, disp, EGL_WINDOW_BIT, conf,<br>
window, attrib_list);<br>
+<br>
+ if (surf != NULL)<br>
+ drv->API.SwapInterval(drv, disp, surf, dri2_dpy->default_swap_interval);<br>
+<br>
+ return surf;<br>
}<br>
<br>
/**<br>
@@ -219,6 +228,8 @@ dri2_destroy_surface(_EGLDriver *drv, _EGLDisplay *disp, _EGLSurface *surf)<br>
<br>
if (dri2_surf->frame_callback)<br>
wl_callback_destroy(dri2_surf->frame_callback);<br>
+ if (dri2_surf->sync_callback)<br>
+ wl_callback_destroy(dri2_surf->sync_callback);<br>
<br>
if (dri2_surf->base.Type == EGL_WINDOW_BIT) {<br>
dri2_surf->wl_win->private = NULL;<br>
@@ -257,6 +268,52 @@ dri2_release_buffers(struct dri2_egl_surface *dri2_surf)<br>
}<br>
<br>
static int<br>
+poll_compositor(struct dri2_egl_display *dri2_dpy)<br>
+{<br>
+ static GLboolean seen_wait_warning = GL_FALSE;<br>
+ struct pollfd pollfd;<br>
+<br>
+ /* This path is a last resort and it implies that something has gone wrong<br>
+ * so we'll warn about it */<br>
+ if (!seen_wait_warning) {<br>
+ _eglLog(_EGL_WARNING, "waiting for the compositor to release a buffer");<br>
+ seen_wait_warning = GL_TRUE;<br>
+ }<br>
+<br>
+ /* The plan here is that we'll wait for up to 10ms for some data from the<br>
+ * compositor. If so we'll just return so that it will check if there is<br>
+ * now a buffer available. Otherwise we'll wait for up to 10ms for some new<br>
+ * data before issuing a roundtrip. The roundtrip is necessary because the<br>
+ * compositor may be queuing release events and we need to cause it to<br>
+ * flush the queue */<br>
+<br>
+ if (wl_display_prepare_read_queue(dri2_dpy->wl_dpy,<br>
+ dri2_dpy->wl_queue) != 0)<br>
+ return wl_display_dispatch_queue_pending(dri2_dpy->wl_dpy,<br>
+ dri2_dpy->wl_queue);<br>
+<br>
+ pollfd.fd = wl_display_get_fd(dri2_dpy->wl_dpy);<br>
+ pollfd.events = POLLIN;<br>
+ pollfd.revents = 0;<br>
+<br>
+ if (poll(&pollfd, 1, 10) < 0) {<br>
+ wl_display_cancel_read(dri2_dpy->wl_dpy);<br>
+ return -1;<br>
+ }<br>
+<br>
+ if (pollfd.revents) {<br>
+ if (wl_display_read_events(dri2_dpy->wl_dpy) < 0)<br>
+ return -1;<br>
+<br>
+ return wl_display_dispatch_queue_pending(dri2_dpy->wl_dpy,<br>
+ dri2_dpy->wl_queue);<br>
+ } else {<br>
+ wl_display_cancel_read(dri2_dpy->wl_dpy);<br>
+ return roundtrip(dri2_dpy);<br>
+ }<br>
+}<br>
+<br>
+static int<br>
get_back_bo(struct dri2_egl_surface *dri2_surf, __DRIbuffer *buffer)<br>
{<br>
struct dri2_egl_display *dri2_dpy =<br>
@@ -264,24 +321,46 @@ get_back_bo(struct dri2_egl_surface *dri2_surf, __DRIbuffer *buffer)<br>
__DRIimage *image;<br>
int i, name, pitch;<br>
<br>
- /* There might be a buffer release already queued that wasn't processed */<br>
- wl_display_dispatch_queue_pending(dri2_dpy->wl_dpy, dri2_dpy->wl_queue);<br>
+ if (dri2_surf->sync_callback == NULL) {<br>
+ /* There might be a buffer release already queued that wasn't processed */<br>
+ wl_display_dispatch_queue_pending(dri2_dpy->wl_dpy, dri2_dpy->wl_queue);<br>
+ } else {<br>
+ /* We always want to throttle to a sync event after the commit so that<br>
+ * we can be sure the compositor has had a chance to handle it and send<br>
+ * us a release event before we look for a free buffer */<br>
+ do {<br>
+ if (wl_display_dispatch_queue(dri2_dpy->wl_dpy,<br>
+ dri2_dpy->wl_queue) == -1)<br>
+ return EGL_FALSE;<br>
+ } while (dri2_surf->sync_callback != NULL);<br>
+ }<br>
<br>
if (dri2_surf->back == NULL) {<br>
- for (i = 0; i < ARRAY_SIZE(dri2_surf->color_buffers); i++) {<br>
- /* Get an unlocked buffer, preferrably one with a dri_buffer already<br>
- * allocated. */<br>
- if (dri2_surf->color_buffers[i].locked)<br>
- continue;<br>
- if (dri2_surf->back == NULL)<br>
- dri2_surf->back = &dri2_surf->color_buffers[i];<br>
- else if (dri2_surf->back->dri_image == NULL)<br>
- dri2_surf->back = &dri2_surf->color_buffers[i];<br>
+ while (1) {<br>
+ for (i = 0; i < ARRAY_SIZE(dri2_surf->color_buffers); i++) {<br>
+ /* Get an unlocked buffer, preferrably one with a dri_buffer<br>
+ * already allocated. */<br>
+ if (dri2_surf->color_buffers[i].locked)<br>
+ continue;<br>
+ if (dri2_surf->back == NULL)<br>
+ dri2_surf->back = &dri2_surf->color_buffers[i];<br>
+ else if (dri2_surf->back->dri_image == NULL)<br>
+ dri2_surf->back = &dri2_surf->color_buffers[i];<br>
+ }<br>
+<br>
+ if (dri2_surf->back != NULL)<br>
+ break;<br>
+<br>
+ /* If we make it here then here then all of the buffers are locked.<br>
+ * In theory this shouldn't happen because the compositor shouldn't<br>
+ * be holding on to so many buffers. As a last resort we will poll<br>
+ * the compositor at regular intervals in order to ensure we<br>
+ * eventually get a buffer */<br>
+<br>
+ poll_compositor(dri2_dpy);<br>
}<br>
}<br>
<br>
- if (dri2_surf->back == NULL)<br>
- return -1;<br>
if (dri2_surf->back->dri_image == NULL) {<br>
dri2_surf->back->dri_image =<br>
dri2_dpy->image->createImage(dri2_dpy->dri_screen,<br>
@@ -455,6 +534,21 @@ static const struct wl_callback_listener frame_listener = {<br>
};<br>
<br>
static void<br>
+wayland_sync_callback(void *data,<br>
+ struct wl_callback *callback,<br>
+ uint32_t time)<br>
+{<br>
+ struct dri2_egl_surface *dri2_surf = data;<br>
+<br>
+ dri2_surf->sync_callback = NULL;<br>
+ wl_callback_destroy(callback);<br>
+}<br>
+<br>
+static const struct wl_callback_listener throttle_listener = {<br>
+ wayland_sync_callback<br>
+};<br>
+<br>
+static void<br>
create_wl_buffer(struct dri2_egl_surface *dri2_surf)<br>
{<br>
struct dri2_egl_display *dri2_dpy =<br>
@@ -514,11 +608,14 @@ dri2_swap_buffers_with_damage(_EGLDriver *drv,<br>
if (ret < 0)<br>
return EGL_FALSE;<br>
<br>
- dri2_surf->frame_callback = wl_surface_frame(dri2_surf->wl_win->surface);<br>
- wl_callback_add_listener(dri2_surf->frame_callback,<br>
- &frame_listener, dri2_surf);<br>
- wl_proxy_set_queue((struct wl_proxy *) dri2_surf->frame_callback,<br>
- dri2_dpy->wl_queue);<br>
+ if (draw->SwapInterval > 0) {<br>
+ dri2_surf->frame_callback =<br>
+ wl_surface_frame(dri2_surf->wl_win->surface);<br>
+ wl_callback_add_listener(dri2_surf->frame_callback,<br>
+ &frame_listener, dri2_surf);<br>
+ wl_proxy_set_queue((struct wl_proxy *) dri2_surf->frame_callback,<br>
+ dri2_dpy->wl_queue);<br>
+ }<br>
<br>
for (i = 0; i < ARRAY_SIZE(dri2_surf->color_buffers); i++)<br>
if (dri2_surf->color_buffers[i].age > 0)<br>
@@ -562,6 +659,16 @@ dri2_swap_buffers_with_damage(_EGLDriver *drv,<br>
<br>
wl_surface_commit(dri2_surf->wl_win->surface);<br>
<br>
+ /* We always want to throttle to a sync callback even if we are also<br>
+ * waiting for a frame callback. The sync callback is checked in<br>
+ * get_back_bo so that we always give a chance for the compositor to handle<br>
+ * the commit and send a release event before checking for a free buffer */<br>
+ dri2_surf->sync_callback = wl_display_sync(dri2_dpy->wl_dpy);<br>
+ wl_callback_add_listener(dri2_surf->sync_callback,<br>
+ &throttle_listener, dri2_surf);<br>
+ wl_proxy_set_queue((struct wl_proxy *) dri2_surf->sync_callback,<br>
+ dri2_dpy->wl_queue);<br>
+<br>
(*dri2_dpy->flush->flush)(dri2_surf->dri_drawable);<br>
(*dri2_dpy->flush->invalidate)(dri2_surf->dri_drawable);<br>
<br>
@@ -808,6 +915,60 @@ static const struct wl_registry_listener registry_listener = {<br>
registry_handle_global_remove<br>
};<br>
<br>
+static EGLBoolean<br>
+dri2_swap_interval(_EGLDriver *drv,<br>
+ _EGLDisplay *disp,<br>
+ _EGLSurface *surf,<br>
+ EGLint interval)<br>
+{<br>
+ if (interval > surf->Config->MaxSwapInterval)<br>
+ interval = surf->Config->MaxSwapInterval;<br>
+ else if (interval < surf->Config->MinSwapInterval)<br>
+ interval = surf->Config->MinSwapInterval;<br>
+<br>
+ surf->SwapInterval = interval;<br>
+<br>
+ return EGL_TRUE;<br>
+}<br>
+<br>
+static void<br>
+dri2_setup_swap_interval(struct dri2_egl_display *dri2_dpy)<br>
+{<br>
+ GLint vblank_mode = DRI_CONF_VBLANK_DEF_INTERVAL_1;<br>
+<br>
+ /* We can't use values greater than 1 on Wayland because we are using the<br>
+ * frame callback to synchronise the frame and the only way we be sure to<br>
+ * get a frame callback is to attach a new buffer. Therefore we can't just<br>
+ * sit drawing nothing to wait until the next ‘n’ frame callbacks */<br>
+<br>
+ if (dri2_dpy->config)<br>
+ dri2_dpy->config->configQueryi(dri2_dpy->dri_screen,<br>
+ "vblank_mode", &vblank_mode);<br>
+ switch (vblank_mode) {<br>
+ case DRI_CONF_VBLANK_NEVER:<br>
+ dri2_dpy->min_swap_interval = 0;<br>
+ dri2_dpy->max_swap_interval = 0;<br>
+ dri2_dpy->default_swap_interval = 0;<br>
+ break;<br>
+ case DRI_CONF_VBLANK_ALWAYS_SYNC:<br>
+ dri2_dpy->min_swap_interval = 1;<br>
+ dri2_dpy->max_swap_interval = 1;<br>
+ dri2_dpy->default_swap_interval = 1;<br>
+ break;<br>
+ case DRI_CONF_VBLANK_DEF_INTERVAL_0:<br>
+ dri2_dpy->min_swap_interval = 0;<br>
+ dri2_dpy->max_swap_interval = 1;<br>
+ dri2_dpy->default_swap_interval = 0;<br>
+ break;<br>
+ default:<br>
+ case DRI_CONF_VBLANK_DEF_INTERVAL_1:<br>
+ dri2_dpy->min_swap_interval = 0;<br>
+ dri2_dpy->max_swap_interval = 1;<br>
+ dri2_dpy->default_swap_interval = 1;<br>
+ break;<br>
+ }<br>
+}<br>
+<br>
EGLBoolean<br>
dri2_initialize_wayland(_EGLDriver *drv, _EGLDisplay *disp)<br>
{<br>
@@ -824,6 +985,7 @@ dri2_initialize_wayland(_EGLDriver *drv, _EGLDisplay *disp)<br>
drv->API.DestroySurface = dri2_destroy_surface;<br>
drv->API.SwapBuffers = dri2_swap_buffers;<br>
drv->API.SwapBuffersWithDamageEXT = dri2_swap_buffers_with_damage;<br>
+ drv->API.SwapInterval = dri2_swap_interval;<br>
drv->API.Terminate = dri2_terminate;<br>
drv->API.QueryBufferAge = dri2_query_buffer_age;<br>
drv->API.CreateWaylandBufferFromImageWL =<br>
@@ -883,9 +1045,13 @@ dri2_initialize_wayland(_EGLDriver *drv, _EGLDisplay *disp)<br>
dri2_dpy->extensions[2] = &use_invalidate.base;<br>
dri2_dpy->extensions[3] = NULL;<br>
<br>
+ dri2_dpy->swap_available = EGL_TRUE;<br>
+<br>
if (!dri2_create_screen(disp))<br>
goto cleanup_driver;<br>
<br>
+ dri2_setup_swap_interval(dri2_dpy);<br>
+<br>
/* The server shouldn't advertise WL_DRM_CAPABILITY_PRIME if the driver<br>
* doesn't have createImageFromFds, since we're using the same driver on<br>
* both sides. We don't want crash if that happens anyway, so fall back to<br>
diff --git a/src/egl/drivers/dri2/platform_x11.c b/src/egl/drivers/dri2/platform_x11.c<br>
index a518db1..cc9a049 100644<br>
--- a/src/egl/drivers/dri2/platform_x11.c<br>
+++ b/src/egl/drivers/dri2/platform_x11.c<br>
@@ -39,12 +39,6 @@<br>
<br>
#include "egl_dri2.h"<br>
<br>
-/* From xmlpool/options.h, user exposed so should be stable */<br>
-#define DRI_CONF_VBLANK_NEVER 0<br>
-#define DRI_CONF_VBLANK_DEF_INTERVAL_0 1<br>
-#define DRI_CONF_VBLANK_DEF_INTERVAL_1 2<br>
-#define DRI_CONF_VBLANK_ALWAYS_SYNC 3<br>
-<br>
static void<br>
swrastCreateDrawable(struct dri2_egl_display * dri2_dpy,<br>
struct dri2_egl_surface * dri2_surf,<br>
<span class="HOEnZb"><font color="#888888">--<br>
1.8.3.1<br>
<br>
_______________________________________________<br>
wayland-devel mailing list<br>
<a href="mailto:wayland-devel@lists.freedesktop.org">wayland-devel@lists.freedesktop.org</a><br>
<a href="http://lists.freedesktop.org/mailman/listinfo/wayland-devel" target="_blank">http://lists.freedesktop.org/mailman/listinfo/wayland-devel</a><br>
</font></span></blockquote></div><br></div>