glx backend: Adds support for GLX_INTEL_swap_event

If your OpenGL driver supports GLX_INTEL_swap_event that means when
glXSwapBuffers is called it returns immediatly and an XEvent is sent when
the actual swap has finished.

Clutter can use the events that notify swap completion as a means to
throttle rendering in the master clock without blocking the CPU and so it
should help improve the performance of CPU bound applications.
This commit is contained in:
Robert Bragg 2009-11-12 20:37:01 +00:00
parent 848db1ee4c
commit 5d702853b8
17 changed files with 329 additions and 24 deletions

View File

@ -61,7 +61,8 @@ typedef enum
CLUTTER_FEATURE_STAGE_CURSOR = (1 << 8),
CLUTTER_FEATURE_SHADERS_GLSL = (1 << 9),
CLUTTER_FEATURE_OFFSCREEN = (1 << 10),
CLUTTER_FEATURE_STAGE_MULTIPLE = (1 << 11)
CLUTTER_FEATURE_STAGE_MULTIPLE = (1 << 11),
CLUTTER_FEATURE_SWAP_EVENTS = (1 << 12)
} ClutterFeatureFlags;
gboolean clutter_feature_available (ClutterFeatureFlags feature);

View File

@ -68,7 +68,12 @@ struct _ClutterMasterClock
*/
GSource *source;
guint updated_stages : 1;
/* If the master clock is idle that means it's
* fallen back to idle polling for timeline
* progressions and it may have been some time since
* the last real stage update.
*/
guint idle : 1;
guint ensure_next_iteration : 1;
};
@ -117,11 +122,24 @@ master_clock_is_running (ClutterMasterClock *master_clock)
{
ClutterStageManager *stage_manager = clutter_stage_manager_get_default ();
const GSList *stages, *l;
gboolean stage_free = FALSE;
stages = clutter_stage_manager_peek_stages (stage_manager);
/* If all of the stages are busy waiting for a swap-buffers to complete
* then we stop the master clock... */
for (l = stages; l != NULL; l = l->next)
if (_clutter_stage_get_pending_swaps (l->data) == 0)
{
stage_free = TRUE;
break;
}
if (!stage_free)
return FALSE;
if (master_clock->timelines)
return TRUE;
stages = clutter_stage_manager_peek_stages (stage_manager);
for (l = stages; l; l = l->next)
if (_clutter_stage_has_queued_events (l->data) ||
_clutter_stage_needs_update (l->data))
@ -154,12 +172,21 @@ master_clock_next_frame_delay (ClutterMasterClock *master_clock)
if (!master_clock_is_running (master_clock))
return -1;
/* When we have sync-to-vblank, we count on swap-buffer requests (or
* swap-buffer-complete events if supported in the backend) to throttle our
* frame rate so no additional delay is needed to start the next frame.
*
* If the master-clock has become idle due to no timeline progression causing
* redraws then we can no longer rely on vblank synchronization because the
* last real stage update/redraw may have happened a long time ago and so we
* fallback to polling for timeline progressions every 1/frame_rate seconds.
*
* (NB: if there aren't even any timelines running then the master clock will
* be completely stopped in master_clock_is_running())
*/
if (clutter_feature_available (CLUTTER_FEATURE_SYNC_TO_VBLANK) &&
master_clock->updated_stages)
!master_clock->idle)
{
/* When we have sync-to-vblank, we count on that to throttle
* our frame rate, and otherwise draw frames as fast as possible.
*/
CLUTTER_NOTE (SCHEDULER, "vblank available and updated stages");
return 0;
}
@ -273,6 +300,7 @@ clutter_clock_dispatch (GSource *source,
ClutterMasterClock *master_clock = clock_source->master_clock;
ClutterStageManager *stage_manager = clutter_stage_manager_get_default ();
GSList *stages, *l;
gboolean stages_updated = FALSE;
CLUTTER_STATIC_TIMER (master_dispatch_timer,
"Mainloop",
@ -303,11 +331,18 @@ clutter_clock_dispatch (GSource *source,
CLUTTER_TIMER_START (_clutter_uprof_context, master_event_process);
master_clock->updated_stages = FALSE;
master_clock->idle = FALSE;
/* Process queued events */
for (l = stages; l != NULL; l = l->next)
_clutter_stage_process_queued_events (l->data);
{
/* NB: If a stage is busy waiting for a swap-buffers completion then
* we don't process its events so we can maximize the benefits of
* motion compression, and avoid multiple picks per frame.
*/
if (_clutter_stage_get_pending_swaps (l->data) == 0)
_clutter_stage_process_queued_events (l->data);
}
CLUTTER_TIMER_STOP (_clutter_uprof_context, master_event_process);
@ -319,7 +354,24 @@ clutter_clock_dispatch (GSource *source,
* is advanced.
*/
for (l = stages; l != NULL; l = l->next)
master_clock->updated_stages |= _clutter_stage_do_update (l->data);
{
/* If a stage has a swap-buffers pending we don't want to draw to it
* in case the driver may block the CPU while it waits for the next
* backbuffer to become available.
*
* TODO: We should be able to identify if we are running triple or N
* buffered and in these cases we can still draw if there is 1 swap
* pending so we can hopefully always be ready to swap for the next
* vblank and really match the vsync frequency.
*/
if (_clutter_stage_get_pending_swaps (l->data) == 0)
stages_updated |= _clutter_stage_do_update (l->data);
}
/* The master clock goes idle if no stages were updated and falls back
* to polling for timeline progressions... */
if (!stages_updated)
master_clock->idle = TRUE;
g_slist_foreach (stages, (GFunc) g_object_unref, NULL);
g_slist_free (stages);
@ -359,7 +411,7 @@ clutter_master_clock_init (ClutterMasterClock *self)
source = clutter_clock_source_new (self);
self->source = source;
self->updated_stages = TRUE;
self->idle = FALSE;
self->ensure_next_iteration = FALSE;
g_source_set_priority (source, CLUTTER_PRIORITY_REDRAW);

View File

@ -246,6 +246,8 @@ gboolean _clutter_stage_has_queued_events (ClutterStage *stage);
void _clutter_stage_process_queued_events (ClutterStage *stage);
void _clutter_stage_update_input_devices (ClutterStage *stage);
int _clutter_stage_get_pending_swaps (ClutterStage *stage);
/* vfuncs implemented by backend */
GType _clutter_backend_impl_get_type (void);

View File

@ -108,3 +108,16 @@ _clutter_stage_window_get_geometry (ClutterStageWindow *window,
{
CLUTTER_STAGE_WINDOW_GET_IFACE (window)->get_geometry (window, geometry);
}
int
_clutter_stage_window_get_pending_swaps (ClutterStageWindow *window)
{
if (!CLUTTER_STAGE_WINDOW_GET_IFACE (window)->get_pending_swaps)
{
g_assert (!clutter_feature_available (CLUTTER_FEATURE_SWAP_EVENTS));
return 0;
}
return CLUTTER_STAGE_WINDOW_GET_IFACE (window)->get_pending_swaps (window);
}

View File

@ -40,6 +40,8 @@ struct _ClutterStageWindowIface
gint height);
void (* get_geometry) (ClutterStageWindow *stage_window,
ClutterGeometry *geometry);
int (* get_pending_swaps) (ClutterStageWindow *stage_window);
};
GType clutter_stage_window_get_type (void) G_GNUC_CONST;
@ -67,6 +69,7 @@ void _clutter_stage_window_resize (ClutterStageWindow *wind
gint height);
void _clutter_stage_window_get_geometry (ClutterStageWindow *window,
ClutterGeometry *geometry);
int _clutter_stage_window_get_pending_swaps (ClutterStageWindow *window);
G_END_DECLS

View File

@ -2397,3 +2397,11 @@ clutter_stage_get_minimum_size (ClutterStage *stage,
*height = stage->priv->minimum_height;
}
/* Returns the number of swap buffers pending completion for the stage */
int
_clutter_stage_get_pending_swaps (ClutterStage *stage)
{
ClutterStageWindow *stage_window = _clutter_stage_get_window (stage);
return _clutter_stage_window_get_pending_swaps (stage_window);
}

View File

@ -26,6 +26,8 @@ libclutter_glx_la_DEPENDENCIES = \
libclutter_glx_la_SOURCES = \
clutter-backend-glx.h \
clutter-backend-glx.c \
clutter-event-glx.h \
clutter-event-glx.c \
clutter-stage-glx.h \
clutter-stage-glx.c \
clutter-glx-texture-pixmap.h \

View File

@ -37,9 +37,11 @@
#include <errno.h>
#include <GL/glx.h>
#include <GL/glxext.h>
#include <GL/gl.h>
#include "clutter-backend-glx.h"
#include "clutter-event-glx.h"
#include "clutter-stage-glx.h"
#include "clutter-glx.h"
#include "clutter-profile.h"
@ -129,23 +131,37 @@ clutter_backend_glx_post_parse (ClutterBackend *backend,
GError **error)
{
ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (backend);
ClutterBackendGLX *backend_glx = CLUTTER_BACKEND_GLX (backend);
ClutterBackendClass *backend_class =
CLUTTER_BACKEND_CLASS (clutter_backend_glx_parent_class);
int glx_major, glx_minor;
if (clutter_backend_x11_post_parse (backend, error))
{
if (!glXQueryVersion (backend_x11->xdpy, &glx_major, &glx_minor)
|| !(glx_major > 1 || glx_minor > 1))
{
g_set_error (error, CLUTTER_INIT_ERROR,
CLUTTER_INIT_ERROR_BACKEND,
"XServer appears to lack required GLX support");
return FALSE;
}
}
else
if (!backend_class->post_parse (backend, error))
return FALSE;
if (!glXQueryExtension (backend_x11->xdpy,
&backend_glx->error_base,
&backend_glx->event_base))
{
g_set_error (error, CLUTTER_INIT_ERROR,
CLUTTER_INIT_ERROR_BACKEND,
"XServer appears to lack required GLX support");
return FALSE;
}
/* XXX: Technically we should require >= GLX 1.3 support but for a long
* time Mesa has exported a hybrid GLX, exporting extensions specified
* to require GLX 1.3, but still reporting 1.2 via glXQueryVersion. */
if (!glXQueryVersion (backend_x11->xdpy, &glx_major, &glx_minor)
|| !(glx_major > 1 || glx_minor > 2))
{
g_set_error (error, CLUTTER_INIT_ERROR,
CLUTTER_INIT_ERROR_BACKEND,
"XServer appears to lack required GLX 1.2 support");
return FALSE;
}
return TRUE;
}
@ -310,6 +326,15 @@ clutter_backend_glx_get_features (ClutterBackend *backend)
if (!(flags & CLUTTER_FEATURE_SYNC_TO_VBLANK))
CLUTTER_NOTE (BACKEND, "glXSwapIntervalSGI vblank setup failed");
#ifdef GLX_INTEL_swap_event
/* GLX_INTEL_swap_event allows us to avoid blocking the CPU while
* we wait for glXSwapBuffers to complete, and instead we get an X
* event notifying us of completion... */
if (cogl_check_extension ("GLX_INTEL_swap_event", glx_extensions) &&
flags & CLUTTER_FEATURE_SYNC_TO_VBLANK)
flags |= CLUTTER_FEATURE_SWAP_EVENTS;
#endif /* GLX_INTEL_swap_event */
}
if (!check_vblank_env ("dri") &&
@ -600,6 +625,8 @@ clutter_backend_glx_create_context (ClutterBackend *backend,
return TRUE;
}
/* TODO: remove this interface in favour of
* _clutter_stage_window_make_current () */
static void
clutter_backend_glx_ensure_context (ClutterBackend *backend,
ClutterStage *stage)
@ -806,6 +833,12 @@ clutter_backend_glx_redraw (ClutterBackend *backend,
backend_x11->xdpy,
(unsigned long) drawable);
/* If we have GLX swap buffer events then glXSwapBuffers will return
* immediately and we need to track that there is a swap in
* progress... */
if (clutter_feature_available (CLUTTER_FEATURE_SWAP_EVENTS))
stage_glx->pending_swaps++;
CLUTTER_TIMER_START (_clutter_uprof_context, swapbuffers_timer);
glXSwapBuffers (backend_x11->xdpy, drawable);
CLUTTER_TIMER_STOP (_clutter_uprof_context, swapbuffers_timer);
@ -862,6 +895,7 @@ clutter_backend_glx_class_init (ClutterBackendGLXClass *klass)
backend_class->ensure_context = clutter_backend_glx_ensure_context;
backendx11_class->get_visual_info = clutter_backend_glx_get_visual_info;
backendx11_class->handle_event = clutter_backend_glx_handle_event;
}
static void

View File

@ -62,6 +62,9 @@ struct _ClutterBackendGLX
{
ClutterBackendX11 parent_instance;
int error_base;
int event_base;
/* Single context for all wins */
gboolean found_fbconfig;
GLXFBConfig fbconfig;

View File

@ -0,0 +1,96 @@
/* Clutter.
* An OpenGL based 'interactive canvas' library.
* Authored By Matthew Allum <mallum@openedhand.com>
* Copyright (C) 2006-2007 OpenedHand
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "clutter-x11.h"
#include "clutter-stage-x11.h"
#include "clutter-backend-x11.h"
#include "clutter-stage-glx.h"
#include "clutter-backend-glx.h"
#include "clutter-private.h"
#include <clutter/clutter-backend.h>
#include <clutter/clutter-stage-manager.h>
#include <X11/Xlib.h>
#include <GL/glxext.h>
#include <glib.h>
gboolean
clutter_backend_glx_handle_event (ClutterBackendX11 *backend_x11,
XEvent *xevent)
{
#ifdef GLX_INTEL_swap_event
ClutterBackendGLX *backend_glx = CLUTTER_BACKEND_GLX (backend_x11);
ClutterStageManager *stage_manager;
GLXBufferSwapComplete *swap_complete_event;
const GSList *l;
if (xevent->type != (backend_glx->event_base + GLX_BufferSwapComplete))
return FALSE; /* Unhandled */
swap_complete_event = (GLXBufferSwapComplete *)xevent;
#if 0
{
const char *event_name;
if (swap_complete_event->event_type == GLX_EXCHANGE_COMPLETE_INTEL)
event_name = "GLX_EXCHANGE_COMPLETE";
else if (swap_complete_event->event_type == GLX_BLIT_COMPLETE_INTEL)
event_name = "GLX_BLIT_COMPLETE";
else
{
g_assert (swap_complete_event->event_type == GLX_FLIP_COMPLETE_INTEL);
event_name = "GLX_FLIP_COMPLETE";
}
g_print ("XXX: clutter_backend_glx_event_handle event = %s\n",
event_name);
}
#endif
stage_manager = clutter_stage_manager_get_default ();
for (l = clutter_stage_manager_peek_stages (stage_manager); l; l = l->next)
{
ClutterStageWindow *stage_win = _clutter_stage_get_window (l->data);
ClutterStageGLX *stage_glx = CLUTTER_STAGE_GLX (stage_win);
ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_win);
if (stage_x11->xwin == swap_complete_event->drawable)
{
g_assert (stage_glx->pending_swaps);
stage_glx->pending_swaps--;
return TRUE;
}
}
return TRUE;
#else
return FALSE;
#endif
}

View File

@ -0,0 +1,38 @@
/* Clutter.
* An OpenGL based 'interactive canvas' library.
* Copyright (C) 2009 Intel Corporation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __CLUTTER_EVENT_GLX_H__
#define __CLUTTER_EVENT_GLX_H__
#include <glib.h>
#include <clutter/clutter-event.h>
#include <clutter/clutter-backend.h>
#include <X11/Xlib.h>
G_BEGIN_DECLS
gboolean
clutter_backend_glx_handle_event (ClutterBackendX11 *backend,
XEvent *xevent);
G_END_DECLS
#endif /* __CLUTTER_EVENT_GLX_H__ */

View File

@ -196,6 +196,17 @@ clutter_stage_glx_realize (ClutterStageWindow *stage_window)
ButtonPressMask | ButtonReleaseMask |
EnterWindowMask | LeaveWindowMask |
PropertyChangeMask);
#ifdef GLX_INTEL_swap_event
if (clutter_feature_available (CLUTTER_FEATURE_SWAP_EVENTS))
{
GLXDrawable drawable =
stage_glx->glxwin ? stage_glx->glxwin : stage_x11->xwin;
glXSelectEvent (backend_x11->xdpy,
drawable,
GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK);
}
#endif /* GLX_INTEL_swap_event */
}
/* no user resize.. */
@ -220,6 +231,14 @@ clutter_stage_glx_realize (ClutterStageWindow *stage_window)
return clutter_stage_glx_parent_iface->realize (stage_window);
}
static int
clutter_stage_glx_get_pending_swaps (ClutterStageWindow *stage_window)
{
ClutterStageGLX *stage_glx = CLUTTER_STAGE_GLX (stage_window);
return stage_glx->pending_swaps;
}
static void
clutter_stage_glx_dispose (GObject *gobject)
{
@ -246,6 +265,8 @@ clutter_stage_window_iface_init (ClutterStageWindowIface *iface)
iface->realize = clutter_stage_glx_realize;
iface->unrealize = clutter_stage_glx_unrealize;
iface->get_pending_swaps = clutter_stage_glx_get_pending_swaps;
/* the rest is inherited from ClutterStageX11 */
}

View File

@ -48,6 +48,8 @@ struct _ClutterStageGLX
{
ClutterStageX11 parent_instance;
int pending_swaps;
GLXPixmap glxpixmap;
GLXWindow glxwin;
};

View File

@ -457,11 +457,19 @@ clutter_backend_x11_get_features (ClutterBackend *backend)
return CLUTTER_FEATURE_STAGE_USER_RESIZE | CLUTTER_FEATURE_STAGE_CURSOR;
}
gboolean
clutter_backend_x11_handle_event (ClutterBackendX11 *backend_x11,
XEvent *xevent)
{
return FALSE;
}
static void
clutter_backend_x11_class_init (ClutterBackendX11Class *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
ClutterBackendClass *backend_class = CLUTTER_BACKEND_CLASS (klass);
ClutterBackendX11Class *backendx11_class = CLUTTER_BACKEND_X11_CLASS (klass);
gobject_class->constructor = clutter_backend_x11_constructor;
gobject_class->dispose = clutter_backend_x11_dispose;
@ -472,6 +480,8 @@ clutter_backend_x11_class_init (ClutterBackendX11Class *klass)
backend_class->init_events = clutter_backend_x11_init_events;
backend_class->add_options = clutter_backend_x11_add_options;
backend_class->get_features = clutter_backend_x11_get_features;
backendx11_class->handle_event = clutter_backend_x11_handle_event;
}
static void

View File

@ -96,6 +96,13 @@ struct _ClutterBackendX11Class
* may need to be handled differently for different backends.
*/
XVisualInfo *(* get_visual_info) (ClutterBackendX11 *backend);
/*
* Different X11 backends may care about some special events so they all have
* a chance to intercept them.
*/
gboolean (*handle_event) (ClutterBackendX11 *backend,
XEvent *xevent);
};
void _clutter_backend_x11_events_init (ClutterBackend *backend);

View File

@ -964,6 +964,8 @@ static void
events_queue (ClutterBackend *backend)
{
ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (backend);
ClutterBackendX11Class *backend_x11_class =
CLUTTER_BACKEND_X11_GET_CLASS (backend_x11);
ClutterEvent *event;
Display *xdisplay = backend_x11->xdpy;
XEvent xevent;
@ -975,6 +977,9 @@ events_queue (ClutterBackend *backend)
{
XNextEvent (xdisplay, &xevent);
if (backend_x11_class->handle_event (backend_x11, &xevent))
continue;
event = clutter_event_new (CLUTTER_NOTHING);
if (event_translate (backend, event, &xevent))

View File

@ -152,6 +152,14 @@ the native window handle created in ::realize().
The ::resize() virtual function implementation should cause an update
of the COGL viewport.
The stage implementation actor can optionally implement:
• ClutterStageWindow::get_pending_swaps()
The get_pending_swaps() implementation should return the number of swap
buffer requests pending completion. This is only relevent for backends
that also support CLUTTER_FEATURE_SWAP_EVENTS.
NOTES
=====