[cogl] onscreen: Add CoglFrameInfo and _add_frame_callback() api



commit 700401667db2522045e4623d78797b17f9184501
Author: Owen W. Taylor <otaylor fishsoup net>
Date:   Mon Nov 12 11:58:10 2012 -0500

    onscreen: Add CoglFrameInfo and _add_frame_callback() api
    
    Add a CoglFrameInfo object that tracks timing information for frames
    that are drawn. We track a frame counter and frame timing information
    for each CoglOnscreen. Internally a CoglFrameInfo is automatically
    created for each frame, delimited by cogl_onscreen_swap_buffers() or
    cogl_onscreen_swap_region() calls.
    
    CoglFrameInfos are delivered to applications via frame event callbacks
    that can be registered with a new cogl_onscreen_add_frame_callback()
    api. Two initial event types (dispatched on all platforms) have been
    defined; a _SYNC event used for throttling the frame rate of
    applications and a _COMPLETE event used so signify the end of a frame.
    
    Note: This new _add_frame_callback() api makes the
    cogl_onscreen_add_swap_complete_callback() api redundant and so it
    should be considered deprecated. Since the _add_swap_complete_callback()
    api is still experimental api, we will be looking to quickly migrate
    users to the new api so we can remove the old api.
    
    Reviewed-by: Robert Bragg <robert linux intel com>

 cogl/Makefile.am                                |    3 +
 cogl/cogl-context-private.h                     |    6 +
 cogl/cogl-context.c                             |    8 +
 cogl/cogl-context.h                             |    3 +
 cogl/cogl-frame-info-private.h                  |   43 ++++
 cogl/cogl-frame-info.c                          |   72 ++++++
 cogl/cogl-frame-info.h                          |  129 ++++++++++
 cogl/cogl-glx-display-private.h                 |    3 +-
 cogl/cogl-glx-renderer-private.h                |    9 +
 cogl/cogl-onscreen-private.h                    |   43 +++-
 cogl/cogl-onscreen.c                            |  236 ++++++++++++++++--
 cogl/cogl-onscreen.h                            |  156 ++++++++++++
 cogl/cogl-poll.c                                |   10 +
 cogl/cogl-sdl.c                                 |    2 +-
 cogl/cogl-types.h                               |    3 +
 cogl/cogl-x11-renderer-private.h                |    1 +
 cogl/cogl.h                                     |    1 +
 cogl/winsys/cogl-winsys-egl-kms.c               |   11 +-
 cogl/winsys/cogl-winsys-glx-feature-functions.h |    3 +-
 cogl/winsys/cogl-winsys-glx.c                   |  298 ++++++++++++++++++++---
 20 files changed, 971 insertions(+), 69 deletions(-)
---
diff --git a/cogl/Makefile.am b/cogl/Makefile.am
index f1d1025..de5f4cc 100644
--- a/cogl/Makefile.am
+++ b/cogl/Makefile.am
@@ -82,6 +82,7 @@ cogl_public_h = \
 	$(srcdir)/cogl-object.h 		\
 	$(srcdir)/cogl-offscreen.h 		\
 	$(srcdir)/cogl-onscreen-template.h 	\
+	$(srcdir)/cogl-frame-info.h		\
 	$(srcdir)/cogl-onscreen.h		\
 	$(srcdir)/cogl-path.h 			\
 	$(srcdir)/cogl-pipeline-layer-state.h 	\
@@ -353,6 +354,8 @@ cogl_sources_c = \
 	$(srcdir)/cogl-spans.c				\
 	$(srcdir)/cogl-journal-private.h		\
 	$(srcdir)/cogl-journal.c			\
+	$(srcdir)/cogl-frame-info-private.h		\
+	$(srcdir)/cogl-frame-info.c			\
 	$(srcdir)/cogl-framebuffer-private.h		\
 	$(srcdir)/cogl-framebuffer.c 			\
 	$(srcdir)/cogl-onscreen-private.h		\
diff --git a/cogl/cogl-context-private.h b/cogl/cogl-context-private.h
index 5b81338..fc528e7 100644
--- a/cogl/cogl-context-private.h
+++ b/cogl/cogl-context-private.h
@@ -49,6 +49,7 @@
 #include "cogl-gpu-info-private.h"
 #include "cogl-gl-header.h"
 #include "cogl-framebuffer-private.h"
+#include "cogl-onscreen-private.h"
 
 typedef struct
 {
@@ -172,6 +173,11 @@ struct _CoglContext
   gboolean have_last_offscreen_allocate_flags;
   CoglOffscreenAllocateFlags last_offscreen_allocate_flags;
 
+  GHashTable *swap_callback_closures;
+  int next_swap_callback_id;
+
+  CoglOnscreenEventList onscreen_events_queue;
+
   CoglGLES2Context *current_gles2_context;
   GQueue gles2_context_stack;
 
diff --git a/cogl/cogl-context.c b/cogl/cogl-context.c
index 21f87f4..c08b543 100644
--- a/cogl/cogl-context.c
+++ b/cogl/cogl-context.c
@@ -289,6 +289,11 @@ cogl_context_new (CoglDisplay *display,
   context->current_draw_buffer_state_flushed = 0;
   context->current_draw_buffer_changes = COGL_FRAMEBUFFER_STATE_ALL;
 
+  context->swap_callback_closures =
+    g_hash_table_new (g_direct_hash, g_direct_equal);
+
+  COGL_TAILQ_INIT (&context->onscreen_events_queue);
+
   g_queue_init (&context->gles2_context_stack);
 
   context->journal_flush_attributes_array =
@@ -448,6 +453,9 @@ _cogl_context_free (CoglContext *context)
   if (context->blit_texture_pipeline)
     cogl_object_unref (context->blit_texture_pipeline);
 
+  if (context->swap_callback_closures)
+    g_hash_table_destroy (context->swap_callback_closures);
+
   g_warn_if_fail (context->gles2_context_stack.length == 0);
 
   if (context->journal_flush_attributes_array)
diff --git a/cogl/cogl-context.h b/cogl/cogl-context.h
index 25e1d24..f61a104 100644
--- a/cogl/cogl-context.h
+++ b/cogl/cogl-context.h
@@ -203,6 +203,8 @@ cogl_is_context (void *object);
  *    suported.
  * @COGL_FEATURE_ID_DEPTH_TEXTURE: Whether #CoglFramebuffer support rendering
  *     the depth buffer to a texture.
+ * @COGL_FEATURE_ID_PRESENTATION_TIME: Whether frame presentation
+ *    time stamps will be recorded in #CoglFrameInfo objects.
  *
  * All the capabilities that can vary between different GPUs supported
  * by Cogl. Applications that depend on any of these features should explicitly
@@ -231,6 +233,7 @@ typedef enum _CoglFeatureID
   COGL_FEATURE_ID_SWAP_BUFFERS_EVENT,
   COGL_FEATURE_ID_GLES2_CONTEXT,
   COGL_FEATURE_ID_DEPTH_TEXTURE,
+  COGL_FEATURE_ID_PRESENTATION_TIME,
 
   /*< private >*/
   _COGL_N_FEATURE_IDS   /*< skip >*/
diff --git a/cogl/cogl-frame-info-private.h b/cogl/cogl-frame-info-private.h
new file mode 100644
index 0000000..79b2a41
--- /dev/null
+++ b/cogl/cogl-frame-info-private.h
@@ -0,0 +1,43 @@
+/*
+ * Cogl
+ *
+ * An object oriented GL/GLES Abstraction/Utility Layer
+ *
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ */
+
+#ifndef __COGL_FRAME_INFO_PRIVATE_H
+#define __COGL_FRAME_INFO_PRIVATE_H
+
+#include "cogl-frame-info.h"
+#include "cogl-object-private.h"
+
+struct _CoglFrameInfo
+{
+  CoglObject _parent;
+
+  int64_t frame_counter;
+  int64_t presentation_time;
+  float refresh_rate;
+
+  CoglOutput *output;
+};
+
+CoglFrameInfo *_cogl_frame_info_new (void);
+
+#endif /* __COGL_FRAME_INFO_PRIVATE_H */
diff --git a/cogl/cogl-frame-info.c b/cogl/cogl-frame-info.c
new file mode 100644
index 0000000..a64e728
--- /dev/null
+++ b/cogl/cogl-frame-info.c
@@ -0,0 +1,72 @@
+/*
+ * Cogl
+ *
+ * An object oriented GL/GLES Abstraction/Utility Layer
+ *
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "cogl-frame-info-private.h"
+
+static void _cogl_frame_info_free (CoglFrameInfo *info);
+
+COGL_OBJECT_DEFINE (FrameInfo, frame_info);
+
+CoglFrameInfo *
+_cogl_frame_info_new (void)
+{
+  CoglFrameInfo *info;
+
+  info = g_slice_new0 (CoglFrameInfo);
+
+  return _cogl_frame_info_object_new (info);
+}
+
+static void
+_cogl_frame_info_free (CoglFrameInfo *info)
+{
+  g_slice_free (CoglFrameInfo, info);
+}
+
+int64_t
+cogl_frame_info_get_frame_counter (CoglFrameInfo *info)
+{
+  return info->frame_counter;
+}
+
+int64_t
+cogl_frame_info_get_presentation_time (CoglFrameInfo *info)
+{
+  return info->presentation_time;
+}
+
+float
+cogl_frame_info_get_refresh_rate (CoglFrameInfo *info)
+{
+  return info->refresh_rate;
+}
+
+CoglOutput *
+cogl_frame_info_get_output (CoglFrameInfo *info)
+{
+  return info->output;
+}
diff --git a/cogl/cogl-frame-info.h b/cogl/cogl-frame-info.h
new file mode 100644
index 0000000..36f8a04
--- /dev/null
+++ b/cogl/cogl-frame-info.h
@@ -0,0 +1,129 @@
+/*
+ * Cogl
+ *
+ * An object oriented GL/GLES Abstraction/Utility Layer
+ *
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * 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, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ *
+ *
+ * Authors:
+ *   Owen Taylor <otaylor redhat com>
+ */
+#if !defined(__COGL_H_INSIDE__) && !defined(COGL_COMPILATION)
+#error "Only <cogl/cogl.h> can be included directly."
+#endif
+
+#ifndef __COGL_FRAME_INFO_H
+#define __COGL_FRAME_INFO_H
+
+#include <cogl/cogl-types.h>
+#include <cogl/cogl-output.h>
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _CoglFrameInfo CoglFrameInfo;
+#define COGL_FRAME_INFO(X) ((CoglFrameInfo *)(X))
+
+/**
+ * cogl_is_frame_info:
+ * @object: A #CoglObject pointer
+ *
+ * Gets whether the given object references a #CoglFrameInfo.
+ *
+ * Return value: %TRUE if the object references a #CoglFrameInfo
+ *   and %FALSE otherwise.
+ * Since: 2.0
+ * Stability: unstable
+ */
+CoglBool
+cogl_is_frame_info (void *object);
+
+/**
+ * cogl_frame_info_get_frame_counter:
+ * @info: a #CoglFrameInfo object
+ *
+ * Gets the frame counter for the #CoglOnscreen that corresponds
+ * to this frame.
+ *
+ * Return value: The frame counter value
+ * Since: 1.14
+ * Stability: unstable
+ */
+int64_t cogl_frame_info_get_frame_counter (CoglFrameInfo *info);
+
+/**
+ * cogl_frame_info_get_presentation_time:
+ * @info: a #CoglFrameInfo object
+ *
+ * Gets the presentation time for the frame. This is the time at which
+ * the frame became visible to the user.
+ *
+ * The presentation time measured in nanoseconds is based on a
+ * monotonic time source. The time source is not necessarily
+ * correlated with system/wall clock time and may represent the time
+ * elapsed since some undefined system event such as when the system
+ * last booted.
+ *
+ * <note>Linux kernel version less that 3.8 can result in
+ * non-monotonic timestamps being reported when using a drm based
+ * OpenGL driver. Also some buggy Mesa drivers up to 9.0.1 may also
+ * incorrectly report non-monotonic timestamps.</note>
+ *
+ * Return value: the presentation time for the frame
+ * Since: 1.14
+ * Stability: unstable
+ */
+int64_t cogl_frame_info_get_presentation_time (CoglFrameInfo *info);
+
+/**
+ * cogl_frame_info_get_refresh_rate:
+ * @info: a #CoglFrameInfo object
+ *
+ * Gets the refresh rate in Hertz for the output that the frame was on
+ * at the time the frame was presented.
+ *
+ * <note>Some platforms can't associate a #CoglOutput with a
+ * #CoglFrameInfo object but are able to report a refresh rate via
+ * this api. Therefore if you need this information then this api is
+ * more reliable than using cogl_frame_info_get_output() followed by
+ * cogl_output_get_refresh_rate().</note>
+ *
+ * Return value: the refresh rate in Hertz
+ * Since: 1.14
+ * Stability: unstable
+ */
+float cogl_frame_info_get_refresh_rate (CoglFrameInfo *info);
+
+/**
+ * cogl_frame_info_get_output:
+ * @info: a #CoglFrameInfo object
+ *
+ * Gets the #CoglOutput that the swapped frame was presented to.
+ *
+ * Return value: The #CoglOutput that the frame was presented to, or
+ *               %NULL if this could not be determined.
+ * Since: 1.14
+ * Stability: unstable
+ */
+CoglOutput *
+cogl_frame_info_get_output (CoglFrameInfo *info);
+
+G_END_DECLS
+
+#endif /* __COGL_FRAME_INFO_H */
diff --git a/cogl/cogl-glx-display-private.h b/cogl/cogl-glx-display-private.h
index 1ffcd32..69b1570 100644
--- a/cogl/cogl-glx-display-private.h
+++ b/cogl/cogl-glx-display-private.h
@@ -50,7 +50,8 @@ typedef struct _CoglGLXDisplay
   GLXContext glx_context;
   GLXWindow dummy_glxwin;
   Window dummy_xwin;
-  CoglBool pending_swap_notify;
+  CoglBool pending_sync_notify;
+  CoglBool pending_complete_notify;
   CoglBool pending_resize_notify;
 } CoglGLXDisplay;
 
diff --git a/cogl/cogl-glx-renderer-private.h b/cogl/cogl-glx-renderer-private.h
index 20ca543..e43b443 100644
--- a/cogl/cogl-glx-renderer-private.h
+++ b/cogl/cogl-glx-renderer-private.h
@@ -42,6 +42,15 @@ typedef struct _CoglGLXRenderer
   /* Vblank stuff */
   int dri_fd;
 
+  /* enumeration with relatioship between OML_sync_control
+   * UST (unadjusted-system-time) and the system clock */
+  enum {
+    COGL_GLX_UST_IS_UNKNOWN,
+    COGL_GLX_UST_IS_GETTIMEOFDAY,
+    COGL_GLX_UST_IS_MONOTONIC_TIME,
+    COGL_GLX_UST_IS_OTHER
+  } ust_type;
+
   /* GModule pointing to libGL which we use to get glX functions out of */
   GModule *libgl_module;
 
diff --git a/cogl/cogl-onscreen-private.h b/cogl/cogl-onscreen-private.h
index d5a3707..89efb0e 100644
--- a/cogl/cogl-onscreen-private.h
+++ b/cogl/cogl-onscreen-private.h
@@ -24,6 +24,7 @@
 #ifndef __COGL_ONSCREEN_PRIVATE_H
 #define __COGL_ONSCREEN_PRIVATE_H
 
+#include "cogl-onscreen.h"
 #include "cogl-framebuffer-private.h"
 #include "cogl-queue.h"
 
@@ -33,17 +34,16 @@
 #include <windows.h>
 #endif
 
-typedef struct _CoglSwapBuffersNotifyEntry CoglSwapBuffersNotifyEntry;
+COGL_TAILQ_HEAD (CoglFrameCallbackList, CoglFrameClosure);
 
-COGL_TAILQ_HEAD (CoglSwapBuffersNotifyList, CoglSwapBuffersNotifyEntry);
-
-struct _CoglSwapBuffersNotifyEntry
+struct _CoglFrameClosure
 {
-  COGL_TAILQ_ENTRY (CoglSwapBuffersNotifyEntry) list_node;
+  COGL_TAILQ_ENTRY (CoglFrameClosure) list_node;
+
+  CoglFrameCallback callback;
 
-  CoglSwapBuffersNotify callback;
   void *user_data;
-  unsigned int id;
+  CoglUserDataDestroyCallback destroy;
 };
 
 typedef struct _CoglResizeNotifyEntry CoglResizeNotifyEntry;
@@ -59,6 +59,19 @@ struct _CoglResizeNotifyEntry
   unsigned int id;
 };
 
+typedef struct _CoglOnscreenEvent CoglOnscreenEvent;
+
+COGL_TAILQ_HEAD (CoglOnscreenEventList, CoglOnscreenEvent);
+
+struct _CoglOnscreenEvent
+{
+  COGL_TAILQ_ENTRY (CoglOnscreenEvent) list_node;
+
+  CoglOnscreen *onscreen;
+  CoglFrameInfo *info;
+  CoglFrameEvent type;
+};
+
 struct _CoglOnscreen
 {
   CoglFramebuffer  _parent;
@@ -75,11 +88,17 @@ struct _CoglOnscreen
 
   CoglBool swap_throttled;
 
-  CoglSwapBuffersNotifyList swap_callbacks;
+  CoglFrameCallbackList frame_closures;
 
   CoglBool resizable;
   CoglResizeNotifyList resize_callbacks;
 
+  int64_t frame_counter;
+  int64_t swap_frame_counter; /* frame counter at last all to
+                               * cogl_onscreen_swap_region() or
+                               * cogl_onscreen_swap_buffers() */
+  GQueue pending_frame_infos;
+
   void *winsys;
 };
 
@@ -88,9 +107,15 @@ _cogl_framebuffer_winsys_update_size (CoglFramebuffer *framebuffer,
                                       int width, int height);
 
 void
-_cogl_onscreen_notify_swap_buffers (CoglOnscreen *onscreen);
+_cogl_onscreen_notify_frame_sync (CoglOnscreen *onscreen, CoglFrameInfo *info);
+
+void
+_cogl_onscreen_notify_complete (CoglOnscreen *onscreen, CoglFrameInfo *info);
 
 void
 _cogl_onscreen_notify_resize (CoglOnscreen *onscreen);
 
+void
+_cogl_dispatch_onscreen_events (CoglContext *context);
+
 #endif /* __COGL_ONSCREEN_PRIVATE_H */
diff --git a/cogl/cogl-onscreen.c b/cogl/cogl-onscreen.c
index 147ac67..2f05001 100644
--- a/cogl/cogl-onscreen.c
+++ b/cogl/cogl-onscreen.c
@@ -27,6 +27,7 @@
 
 #include "cogl-util.h"
 #include "cogl-onscreen-private.h"
+#include "cogl-frame-info-private.h"
 #include "cogl-framebuffer-private.h"
 #include "cogl-onscreen-template-private.h"
 #include "cogl-context-private.h"
@@ -44,7 +45,7 @@ _cogl_onscreen_init_from_template (CoglOnscreen *onscreen,
 {
   CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
 
-  COGL_TAILQ_INIT (&onscreen->swap_callbacks);
+  COGL_TAILQ_INIT (&onscreen->frame_closures);
   COGL_TAILQ_INIT (&onscreen->resize_callbacks);
 
   framebuffer->config = onscreen_template->config;
@@ -86,7 +87,8 @@ _cogl_onscreen_free (CoglOnscreen *onscreen)
   CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
   const CoglWinsysVtable *winsys = _cogl_framebuffer_get_winsys (framebuffer);
   CoglResizeNotifyEntry *resize_entry;
-  CoglSwapBuffersNotifyEntry *swap_entry;
+  CoglFrameClosure *frame_closure;
+  CoglFrameInfo *frame_info;
 
   while ((resize_entry = COGL_TAILQ_FIRST (&onscreen->resize_callbacks)))
     {
@@ -94,12 +96,20 @@ _cogl_onscreen_free (CoglOnscreen *onscreen)
       g_slice_free (CoglResizeNotifyEntry, resize_entry);
     }
 
-  while ((swap_entry = COGL_TAILQ_FIRST (&onscreen->swap_callbacks)))
+  while ((frame_closure = COGL_TAILQ_FIRST (&onscreen->frame_closures)))
     {
-      COGL_TAILQ_REMOVE (&onscreen->swap_callbacks, swap_entry, list_node);
-      g_slice_free (CoglSwapBuffersNotifyEntry, swap_entry);
+      COGL_TAILQ_REMOVE (&onscreen->frame_closures, frame_closure, list_node);
+
+      if (frame_closure->destroy)
+        frame_closure->destroy (frame_closure->user_data);
+
+      g_slice_free (CoglFrameClosure, frame_closure);
     }
 
+  while ((frame_info = g_queue_pop_tail (&onscreen->pending_frame_infos)))
+    cogl_object_unref (frame_info);
+  g_queue_clear (&onscreen->pending_frame_infos);
+
   winsys->onscreen_deinit (onscreen);
   _COGL_RETURN_IF_FAIL (onscreen->winsys == NULL);
 
@@ -109,14 +119,35 @@ _cogl_onscreen_free (CoglOnscreen *onscreen)
   g_free (onscreen);
 }
 
+static void
+_cogl_onscreen_queue_event (CoglOnscreen *onscreen,
+                            CoglFrameEvent type,
+                            CoglFrameInfo *info)
+{
+  CoglContext *ctx = COGL_FRAMEBUFFER (onscreen)->context;
+
+  CoglOnscreenEvent *event = g_slice_new (CoglOnscreenEvent);
+
+  event->onscreen = cogl_object_ref (onscreen);
+  event->info = cogl_object_ref (info);
+  event->type = type;
+
+  COGL_TAILQ_INSERT_TAIL (&ctx->onscreen_events_queue, event, list_node);
+}
+
 void
 cogl_onscreen_swap_buffers (CoglOnscreen *onscreen)
 {
   CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
   const CoglWinsysVtable *winsys;
+  CoglFrameInfo *info;
 
   _COGL_RETURN_IF_FAIL  (framebuffer->type == COGL_FRAMEBUFFER_TYPE_ONSCREEN);
 
+  info = _cogl_frame_info_new ();
+  info->frame_counter = onscreen->frame_counter;
+  g_queue_push_tail (&onscreen->pending_frame_infos, info);
+
   _cogl_framebuffer_flush_journal (framebuffer);
 
   winsys = _cogl_framebuffer_get_winsys (framebuffer);
@@ -125,6 +156,22 @@ cogl_onscreen_swap_buffers (CoglOnscreen *onscreen)
                                     COGL_BUFFER_BIT_COLOR |
                                     COGL_BUFFER_BIT_DEPTH |
                                     COGL_BUFFER_BIT_STENCIL);
+
+  if (!_cogl_winsys_has_feature (COGL_WINSYS_FEATURE_SYNC_AND_COMPLETE_EVENT))
+    {
+      CoglFrameInfo *info;
+
+      g_warn_if_fail (onscreen->pending_frame_infos.length == 1);
+
+      info = g_queue_pop_tail (&onscreen->pending_frame_infos);
+
+      _cogl_onscreen_queue_event (onscreen, COGL_FRAME_EVENT_SYNC, info);
+      _cogl_onscreen_queue_event (onscreen, COGL_FRAME_EVENT_COMPLETE, info);
+
+      cogl_object_unref (info);
+    }
+
+  onscreen->frame_counter++;
 }
 
 void
@@ -134,9 +181,14 @@ cogl_onscreen_swap_region (CoglOnscreen *onscreen,
 {
   CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
   const CoglWinsysVtable *winsys;
+  CoglFrameInfo *info;
 
   _COGL_RETURN_IF_FAIL  (framebuffer->type == COGL_FRAMEBUFFER_TYPE_ONSCREEN);
 
+  info = _cogl_frame_info_new ();
+  info->frame_counter = onscreen->frame_counter;
+  g_queue_push_tail (&onscreen->pending_frame_infos, info);
+
   _cogl_framebuffer_flush_journal (framebuffer);
 
   winsys = _cogl_framebuffer_get_winsys (framebuffer);
@@ -153,6 +205,22 @@ cogl_onscreen_swap_region (CoglOnscreen *onscreen,
                                     COGL_BUFFER_BIT_COLOR |
                                     COGL_BUFFER_BIT_DEPTH |
                                     COGL_BUFFER_BIT_STENCIL);
+
+  if (!_cogl_winsys_has_feature (COGL_WINSYS_FEATURE_SYNC_AND_COMPLETE_EVENT))
+    {
+      CoglFrameInfo *info;
+
+      g_warn_if_fail (onscreen->pending_frame_infos.length == 1);
+
+      info = g_queue_pop_tail (&onscreen->pending_frame_infos);
+
+      _cogl_onscreen_queue_event (onscreen, COGL_FRAME_EVENT_SYNC, info);
+      _cogl_onscreen_queue_event (onscreen, COGL_FRAME_EVENT_COMPLETE, info);
+
+      cogl_object_unref (info);
+    }
+
+  onscreen->frame_counter++;
 }
 
 int
@@ -253,38 +321,107 @@ cogl_win32_onscreen_get_window (CoglOnscreen *onscreen)
 
 #endif /* COGL_HAS_WIN32_SUPPORT */
 
+CoglFrameClosure *
+cogl_onscreen_add_frame_callback (CoglOnscreen *onscreen,
+                                  CoglFrameCallback callback,
+                                  void *user_data,
+                                  CoglUserDataDestroyCallback destroy)
+{
+  CoglFrameClosure *closure = g_slice_new0 (CoglFrameClosure);
+
+  closure->callback = callback;
+  closure->user_data = user_data;
+  closure->destroy = destroy;
+
+  COGL_TAILQ_INSERT_TAIL (&onscreen->frame_closures, closure, list_node);
+
+  return closure;
+}
+
+void
+cogl_onscreen_remove_frame_callback (CoglOnscreen *onscreen,
+                                     CoglFrameClosure *closure)
+{
+  _COGL_RETURN_IF_FAIL (closure);
+
+  if (closure->destroy)
+    closure->destroy (closure->user_data);
+
+  COGL_TAILQ_REMOVE (&onscreen->frame_closures, closure, list_node);
+
+  g_slice_free (CoglFrameClosure, closure);
+}
+
+typedef struct _SwapBufferCallbackState
+{
+  CoglSwapBuffersNotify callback;
+  void *user_data;
+} SwapBufferCallbackState;
+
+static void
+destroy_swap_buffers_callback_state (void *user_data)
+{
+  g_slice_free (SwapBufferCallbackState, user_data);
+}
+
+static void
+shim_swap_buffers_callback (CoglOnscreen *onscreen,
+                            CoglFrameEvent event,
+                            CoglFrameInfo *info,
+                            void *user_data)
+{
+  SwapBufferCallbackState *state = user_data;
+
+  /* XXX: Note that technically it is a change in semantics for this
+   * interface to forward _SYNC events here and also makes the api
+   * name somewhat missleading.
+   *
+   * In practice though this interface is currently used by
+   * applications for throttling, not because they are strictly
+   * interested in knowing when a frame has been presented and so
+   * forwarding _SYNC events should serve them better.
+   */
+  if (event == COGL_FRAME_EVENT_SYNC)
+    state->callback (COGL_FRAMEBUFFER (onscreen), state->user_data);
+}
+
 unsigned int
 cogl_onscreen_add_swap_buffers_callback (CoglOnscreen *onscreen,
                                          CoglSwapBuffersNotify callback,
                                          void *user_data)
 {
-  CoglSwapBuffersNotifyEntry *entry = g_slice_new0 (CoglSwapBuffersNotifyEntry);
-  static int next_swap_buffers_callback_id = 0;
+  CoglContext *ctx = COGL_FRAMEBUFFER (onscreen)->context;
+  SwapBufferCallbackState *state = g_slice_new (SwapBufferCallbackState);
+  CoglFrameClosure *closure;
+  unsigned int id = ctx->next_swap_callback_id++;
 
-  entry->callback = callback;
-  entry->user_data = user_data;
-  entry->id = next_swap_buffers_callback_id++;
+  state->callback = callback;
+  state->user_data = user_data;
 
-  COGL_TAILQ_INSERT_TAIL (&onscreen->swap_callbacks, entry, list_node);
+  closure =
+    cogl_onscreen_add_frame_callback (onscreen,
+                                      shim_swap_buffers_callback,
+                                      state,
+                                      destroy_swap_buffers_callback_state);
 
-  return entry->id;
+  g_hash_table_insert (ctx->swap_callback_closures,
+                       GINT_TO_POINTER (id),
+                       closure);
+
+  return id;
 }
 
 void
 cogl_onscreen_remove_swap_buffers_callback (CoglOnscreen *onscreen,
                                             unsigned int id)
 {
-  CoglSwapBuffersNotifyEntry *entry;
+  CoglContext *ctx = COGL_FRAMEBUFFER (onscreen)->context;
+  CoglFrameClosure *closure = g_hash_table_lookup (ctx->swap_callback_closures,
+                                                   GINT_TO_POINTER (id));
 
-  COGL_TAILQ_FOREACH (entry, &onscreen->swap_callbacks, list_node)
-    {
-      if (entry->id == id)
-        {
-          COGL_TAILQ_REMOVE (&onscreen->swap_callbacks, entry, list_node);
-          g_slice_free (CoglSwapBuffersNotifyEntry, entry);
-          break;
-        }
-    }
+  _COGL_RETURN_IF_FAIL (closure);
+
+  cogl_onscreen_remove_frame_callback (onscreen, closure);
 }
 
 void
@@ -332,16 +469,56 @@ cogl_onscreen_hide (CoglOnscreen *onscreen)
     }
 }
 
-void
-_cogl_onscreen_notify_swap_buffers (CoglOnscreen *onscreen)
+static void
+notify_event (CoglOnscreen *onscreen,
+              CoglFrameEvent event,
+              CoglFrameInfo *info)
 {
-  CoglSwapBuffersNotifyEntry *entry, *tmp;
+  CoglFrameClosure *entry, *tmp;
 
   COGL_TAILQ_FOREACH_SAFE (entry,
-                           &onscreen->swap_callbacks,
+                           &onscreen->frame_closures,
+                           list_node,
+                           tmp)
+    {
+      entry->callback (onscreen, event, info,
+                       entry->user_data);
+    }
+}
+
+void
+_cogl_dispatch_onscreen_events (CoglContext *context)
+{
+  CoglOnscreenEvent *event, *tmp;
+
+  COGL_TAILQ_FOREACH_SAFE (event,
+                           &context->onscreen_events_queue,
                            list_node,
                            tmp)
-    entry->callback (COGL_FRAMEBUFFER (onscreen), entry->user_data);
+    {
+      CoglOnscreen *onscreen = event->onscreen;
+      CoglFrameInfo *info = event->info;
+
+      notify_event (onscreen, event->type, info);
+
+      cogl_object_unref (onscreen);
+      cogl_object_unref (info);
+
+      COGL_TAILQ_REMOVE (&context->onscreen_events_queue, event, list_node);
+      g_slice_free (CoglOnscreenEvent, event);
+    }
+}
+
+void
+_cogl_onscreen_notify_frame_sync (CoglOnscreen *onscreen, CoglFrameInfo *info)
+{
+  notify_event (onscreen, COGL_FRAME_EVENT_SYNC, info);
+}
+
+void
+_cogl_onscreen_notify_complete (CoglOnscreen *onscreen, CoglFrameInfo *info)
+{
+  notify_event (onscreen, COGL_FRAME_EVENT_COMPLETE, info);
 }
 
 void
@@ -435,3 +612,8 @@ cogl_onscreen_remove_resize_handler (CoglOnscreen *onscreen,
     }
 }
 
+int64_t
+cogl_onscreen_get_frame_counter (CoglOnscreen *onscreen)
+{
+  return onscreen->frame_counter;
+}
diff --git a/cogl/cogl-onscreen.h b/cogl/cogl-onscreen.h
index e7d7e97..0a59eb8 100644
--- a/cogl/cogl-onscreen.h
+++ b/cogl/cogl-onscreen.h
@@ -34,6 +34,8 @@
 
 #include <cogl/cogl-context.h>
 #include <cogl/cogl-framebuffer.h>
+#include <cogl/cogl-frame-info.h>
+#include <cogl/cogl-object.h>
 
 COGL_BEGIN_DECLS
 
@@ -403,6 +405,143 @@ cogl_onscreen_swap_region (CoglOnscreen *onscreen,
                            const int *rectangles,
                            int n_rectangles);
 
+/**
+ * CoglFrameEvent:
+ * @COGL_FRAME_EVENT_SYNC: Notifies that the system compositor has
+ *                         acknowledged a frame and is ready for a
+ *                         new frame to be created.
+ * @COGL_FRAME_EVENT_COMPLETE: Notifies that a frame has ended. This
+ *                             is a good time for applications to
+ *                             collect statistics about the frame
+ *                             since the #CoglFrameInfo should hold
+ *                             the most data at this point. No other
+ *                             events should be expected after a
+ *                             @COGL_FRAME_EVENT_COMPLETE event.
+ *
+ * Identifiers that are passed to #CoglFrameCallback functions
+ * (registered using cogl_onscreen_add_frame_callback()) that
+ * mark the progression of a frame in some way which usually
+ * means that new information will have been accumulated in the
+ * frame's corresponding #CoglFrameInfo object.
+ *
+ * The last event that will be sent for a frame will be a
+ * @COGL_FRAME_EVENT_COMPLETE event and so these are a good
+ * opportunity to collect statistics about a frame since the
+ * #CoglFrameInfo should hold the most data at this point.
+ *
+ * <note>A frame may not be completed before the next frame can start
+ * so applications should avoid needing to collect all statistics for
+ * a particular frame before they can start a new frame.</note>
+ *
+ * Since: 1.14
+ * Stability: unstable
+ */
+typedef enum _CoglFrameEvent
+{
+  COGL_FRAME_EVENT_SYNC = 1,
+  COGL_FRAME_EVENT_COMPLETE
+} CoglFrameEvent;
+
+/**
+ * CoglFrameCallback:
+ * @onscreen: The onscreen that the frame is associated with
+ * @event: A #CoglFrameEvent notifying how the frame has progressed
+ * @info: The meta information, such as timing information, about
+ *        the frame that has progressed.
+ * @user_data: The user pointer passed to
+ *             cogl_onscreen_add_frame_callback()
+ *
+ * Is a callback that can be registered via
+ * cogl_onscreen_add_frame_callback() to be called when a frame
+ * progresses in some notable way.
+ *
+ * Please see the documentation for #CoglFrameEvent and
+ * cogl_onscreen_add_frame_callback() for more details about what
+ * events can be notified.
+ *
+ * Since: 1.14
+ * Stability: unstable
+ */
+typedef void (*CoglFrameCallback) (CoglOnscreen *onscreen,
+                                   CoglFrameEvent event,
+                                   CoglFrameInfo *info,
+                                   void *user_data);
+
+/**
+ * CoglFrameClosure:
+ *
+ * An opaque type that tracks a #CoglFrameCallback and associated user
+ * data. A #CoglFrameClosure pointer will be returned from
+ * cogl_onscreen_add_frame_callback() and it allows you to remove a
+ * callback later using cogl_onscreen_remove_frame_callback().
+ *
+ * Since: 1.14
+ * Stability: unstable
+ */
+typedef struct _CoglFrameClosure CoglFrameClosure;
+
+/**
+ * cogl_onscreen_add_frame_callback:
+ * @onscreen: A #CoglOnscreen framebuffer
+ * @callback: A callback function to call for frame events
+ * @user_data: A private pointer to be passed to @callback
+ * @destroy: An optional callback to destroy @user_data when the
+ *           @callback is removed or @onscreen is freed.
+ *
+ * Installs a @callback function that will be called for significant
+ * events relating to the given @onscreen framebuffer.
+ *
+ * The @callback will be used to notify when the system compositor is
+ * ready for this application to render a new frame. In this case
+ * %COGL_FRAME_EVENT_SYNC will be passed as the event argument to the
+ * given @callback in addition to the #CoglFrameInfo corresponding to
+ * the frame beeing acknowledged by the compositor.
+ *
+ * The @callback will also be called to notify when the frame has
+ * ended. In this case %COGL_FRAME_EVENT_COMPLETE will be passed as
+ * the event argument to the given @callback in addition to the
+ * #CoglFrameInfo corresponding to the newly presented frame.  The
+ * meaning of "ended" here simply means that no more timing
+ * information will be collected within the corresponding
+ * #CoglFrameInfo and so this is a good opportunity to analyse the
+ * given info. It does not necessarily mean that the GPU has finished
+ * rendering the corresponding frame.
+ *
+ * We highly recommend throttling your application according to
+ * %COGL_FRAME_EVENT_SYNC events so that your application can avoid
+ * wasting resources, drawing more frames than your system compositor
+ * can display.
+ *
+ * Return value: a #CoglFrameClosure pointer that can be used to
+ *               remove the callback and associated @user_data later.
+ * Since: 1.14
+ * Stability: unstable
+ */
+CoglFrameClosure *
+cogl_onscreen_add_frame_callback (CoglOnscreen *onscreen,
+                                  CoglFrameCallback callback,
+                                  void *user_data,
+                                  CoglUserDataDestroyCallback destroy);
+
+/**
+ * cogl_onscreen_remove_frame_callback:
+ * @onscreen: A #CoglOnscreen
+ * @closure: A #CoglFrameClosure returned from
+ *           cogl_onscreen_add_frame_callback()
+ *
+ * Removes a callback and associated user data that were previously
+ * registered using cogl_onscreen_add_frame_callback().
+ *
+ * If a destroy callback was passed to
+ * cogl_onscreen_add_frame_callback() to destroy the user data then
+ * this will get called.
+ *
+ * Since: 1.14
+ * Stability: unstable
+ */
+void
+cogl_onscreen_remove_frame_callback (CoglOnscreen *onscreen,
+                                     CoglFrameClosure *closure);
 
 typedef void (*CoglSwapBuffersNotify) (CoglFramebuffer *framebuffer,
                                        void *user_data);
@@ -430,6 +569,7 @@ typedef void (*CoglSwapBuffersNotify) (CoglFramebuffer *framebuffer,
  *               the callback later.
  * Since: 1.10
  * Stability: unstable
+ * Deprecated: 1.14: Use cogl_onscreen_add_swap_complete_callback
  */
 unsigned int
 cogl_onscreen_add_swap_buffers_callback (CoglOnscreen *onscreen,
@@ -446,6 +586,7 @@ cogl_onscreen_add_swap_buffers_callback (CoglOnscreen *onscreen,
  *
  * Since: 1.10
  * Stability: unstable
+ * Deprecated: 1.14: Use cogl_onscreen_remove_swap_complete_callback
  */
 void
 cogl_onscreen_remove_swap_buffers_callback (CoglOnscreen *onscreen,
@@ -603,6 +744,21 @@ cogl_onscreen_remove_resize_handler (CoglOnscreen *onscreen,
 CoglBool
 cogl_is_onscreen (void *object);
 
+/**
+ * cogl_onscreen_get_frame_counter:
+ *
+ * Gets the value of the framebuffers frame counter. This is
+ * a counter that increases by one each time
+ * cogl_onscreen_swap_buffers() or cogl_onscreen_swap_region()
+ * is called.
+ *
+ * Return value: the current frame counter value
+ * Since: 1.14
+ * Stability: unstable
+ */
+int64_t
+cogl_onscreen_get_frame_counter (CoglOnscreen *onscreen);
+
 COGL_END_DECLS
 
 #endif /* __COGL_ONSCREEN_H */
diff --git a/cogl/cogl-poll.c b/cogl/cogl-poll.c
index 86be2cf..c6d19fb 100644
--- a/cogl/cogl-poll.c
+++ b/cogl/cogl-poll.c
@@ -44,6 +44,13 @@ cogl_poll_get_info (CoglContext *context,
   _COGL_RETURN_IF_FAIL (n_poll_fds != NULL);
   _COGL_RETURN_IF_FAIL (timeout != NULL);
 
+  if (!COGL_TAILQ_EMPTY (&context->onscreen_events_queue))
+    {
+      *n_poll_fds = 0;
+      *timeout = 0;
+      return;
+    }
+
   winsys = _cogl_context_get_winsys (context);
 
   if (winsys->poll_get_info)
@@ -70,6 +77,9 @@ cogl_poll_dispatch (CoglContext *context,
 
   _COGL_RETURN_IF_FAIL (cogl_is_context (context));
 
+  if (!COGL_TAILQ_EMPTY (&context->onscreen_events_queue))
+    _cogl_dispatch_onscreen_events (context);
+
   winsys = _cogl_context_get_winsys (context);
 
   if (winsys->poll_dispatch)
diff --git a/cogl/cogl-sdl.c b/cogl/cogl-sdl.c
index 03afeb7..b49890f 100644
--- a/cogl/cogl-sdl.c
+++ b/cogl/cogl-sdl.c
@@ -83,5 +83,5 @@ cogl_sdl_handle_event (CoglContext *context, SDL_Event *event)
 void
 cogl_sdl_idle (CoglContext *context)
 {
-  /* NOP since Cogl doesn't currently need to do anything when idle */
+  _cogl_dispatch_onscreen_events (context);
 }
diff --git a/cogl/cogl-types.h b/cogl/cogl-types.h
index 01ad4c9..e4d8aae 100644
--- a/cogl/cogl-types.h
+++ b/cogl/cogl-types.h
@@ -658,6 +658,9 @@ typedef enum _CoglWinsysFeature
   /* Avaiable if the age of the back buffer can be queried */
   COGL_WINSYS_FEATURE_BUFFER_AGE,
 
+  /* Avaiable if the winsys directly handles _SYNC and _COMPLETE events */
+  COGL_WINSYS_FEATURE_SYNC_AND_COMPLETE_EVENT,
+
   COGL_WINSYS_FEATURE_N_FEATURES
 } CoglWinsysFeature;
 
diff --git a/cogl/cogl-x11-renderer-private.h b/cogl/cogl-x11-renderer-private.h
index 54a5639..0b70012 100644
--- a/cogl/cogl-x11-renderer-private.h
+++ b/cogl/cogl-x11-renderer-private.h
@@ -27,6 +27,7 @@
 typedef struct _CoglX11Renderer
 {
   int damage_base;
+  int randr_base;
 } CoglX11Renderer;
 
 #endif /* __COGL_RENDERER_X11_PRIVATE_H */
diff --git a/cogl/cogl.h b/cogl/cogl.h
index 7d62f76..367d45a 100644
--- a/cogl/cogl.h
+++ b/cogl/cogl.h
@@ -76,6 +76,7 @@
 #include <cogl/cogl-snippet.h>
 #include <cogl/cogl-framebuffer.h>
 #include <cogl/cogl-onscreen.h>
+#include <cogl/cogl-frame-info.h>
 #include <cogl/cogl-poll.h>
 #if defined (COGL_HAS_EGL_PLATFORM_KMS_SUPPORT)
 #include <cogl/cogl-kms-renderer.h>
diff --git a/cogl/winsys/cogl-winsys-egl-kms.c b/cogl/winsys/cogl-winsys-egl-kms.c
index ff538e2..db2b496 100644
--- a/cogl/winsys/cogl-winsys-egl-kms.c
+++ b/cogl/winsys/cogl-winsys-egl-kms.c
@@ -754,9 +754,13 @@ _cogl_winsys_egl_context_init (CoglContext *context,
 {
   COGL_FLAGS_SET (context->features,
                   COGL_FEATURE_ID_SWAP_BUFFERS_EVENT, TRUE);
+  /* TODO: remove this deprecated feature */
   COGL_FLAGS_SET (context->winsys_features,
                   COGL_WINSYS_FEATURE_SWAP_BUFFERS_EVENT,
                   TRUE);
+  COGL_FLAGS_SET (context->winsys_features,
+                  COGL_WINSYS_FEATURE_SYNC_AND_COMPLETE_EVENT,
+                  TRUE);
 
   return TRUE;
 }
@@ -894,8 +898,13 @@ flush_pending_swap_notify_cb (void *data,
 
       if (kms_onscreen->pending_swap_notify)
         {
-          _cogl_onscreen_notify_swap_buffers (onscreen);
+          CoglFrameInfo *info = g_queue_pop_head (&onscreen->pending_frame_infos);
+
+          _cogl_onscreen_notify_frame_sync (onscreen, info);
+          _cogl_onscreen_notify_complete (onscreen, info);
           kms_onscreen->pending_swap_notify = FALSE;
+
+          cogl_object_unref (info);
         }
     }
 }
diff --git a/cogl/winsys/cogl-winsys-glx-feature-functions.h b/cogl/winsys/cogl-winsys-glx-feature-functions.h
index d0d2de3..156b58f 100644
--- a/cogl/winsys/cogl-winsys-glx-feature-functions.h
+++ b/cogl/winsys/cogl-winsys-glx-feature-functions.h
@@ -169,7 +169,8 @@ COGL_WINSYS_FEATURE_BEGIN (255, 255,
                            swap_event,
                            "INTEL\0",
                            "swap_event\0",
-                           COGL_WINSYS_FEATURE_SWAP_BUFFERS_EVENT)
+                           COGL_WINSYS_FEATURE_SWAP_BUFFERS_EVENT |
+                           COGL_WINSYS_FEATURE_SYNC_AND_COMPLETE_EVENT)
 COGL_WINSYS_FEATURE_END ()
 
 COGL_WINSYS_FEATURE_BEGIN (255, 255,
diff --git a/cogl/winsys/cogl-winsys-glx.c b/cogl/winsys/cogl-winsys-glx.c
index 2b28420..10f0d9a 100644
--- a/cogl/winsys/cogl-winsys-glx.c
+++ b/cogl/winsys/cogl-winsys-glx.c
@@ -42,6 +42,7 @@
 #include "cogl-texture-2d-private.h"
 #include "cogl-texture-rectangle-private.h"
 #include "cogl-pipeline-opengl-private.h"
+#include "cogl-frame-info-private.h"
 #include "cogl-framebuffer-private.h"
 #include "cogl-onscreen-private.h"
 #include "cogl-swap-chain-private.h"
@@ -53,7 +54,9 @@
 #include <stdlib.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/time.h>
 #include <fcntl.h>
+#include <time.h>
 
 #include <glib/gi18n-lib.h>
 
@@ -81,7 +84,8 @@ typedef struct _CoglOnscreenGLX
   CoglOnscreenXlib _parent;
   GLXDrawable glxwin;
   uint32_t last_swap_vsync_counter;
-  CoglBool pending_swap_notify;
+  CoglBool pending_sync_notify;
+  CoglBool pending_complete_notify;
   CoglBool pending_resize_notify;
 } CoglOnscreenGLX;
 
@@ -168,23 +172,146 @@ find_onscreen_for_xid (CoglContext *context, uint32_t xid)
 }
 
 static void
-notify_swap_buffers (CoglContext *context, GLXDrawable drawable)
+ensure_ust_type (CoglRenderer *renderer,
+                 GLXDrawable drawable)
 {
-  CoglOnscreen *onscreen = find_onscreen_for_xid (context, (uint32_t)drawable);
-  CoglDisplay *display = context->display;
-  CoglGLXDisplay *glx_display = display->winsys;
+  CoglGLXRenderer *glx_renderer =  renderer->winsys;
+  CoglXlibRenderer *xlib_renderer = _cogl_xlib_renderer_get_data (renderer);
+  int64_t ust;
+  int64_t msc;
+  int64_t sbc;
+  struct timeval tv;
+  struct timespec ts;
+  int64_t current_system_time;
+  int64_t current_monotonic_time;
+
+  if (glx_renderer->ust_type != COGL_GLX_UST_IS_UNKNOWN)
+    return;
+
+  glx_renderer->ust_type = COGL_GLX_UST_IS_OTHER;
+
+  if (glx_renderer->glXGetSyncValues == NULL)
+    goto out;
+
+  if (!glx_renderer->glXGetSyncValues (xlib_renderer->xdpy, drawable,
+                                       &ust, &msc, &sbc))
+    goto out;
+
+  /* This is the time source that existing (buggy) linux drm drivers
+   * use */
+  gettimeofday (&tv, NULL);
+  current_system_time = (tv.tv_sec * G_GINT64_CONSTANT (1000000)) + tv.tv_usec;
+
+  if (current_system_time > ust - 1000000 &&
+      current_system_time < ust + 1000000)
+    {
+      glx_renderer->ust_type = COGL_GLX_UST_IS_GETTIMEOFDAY;
+      goto out;
+    }
+
+  /* This is the time source that the newer (fixed) linux drm
+   * drivers use (Linux >= 3.8) */
+  clock_gettime (CLOCK_MONOTONIC, &ts);
+  current_monotonic_time = (ts.tv_sec * G_GINT64_CONSTANT (1000000)) +
+    (ts.tv_nsec / G_GINT64_CONSTANT (1000));
+
+  if (current_monotonic_time > ust - 1000000 &&
+      current_monotonic_time < ust + 1000000)
+    {
+      glx_renderer->ust_type = COGL_GLX_UST_IS_MONOTONIC_TIME;
+      goto out;
+    }
+
+ out:
+  COGL_NOTE (WINSYS, "Classified OML system time as: %s",
+             glx_renderer->ust_type == COGL_GLX_UST_IS_GETTIMEOFDAY ? "gettimeofday" :
+             (glx_renderer->ust_type == COGL_GLX_UST_IS_MONOTONIC_TIME ? "monotonic" :
+              "other"));
+  return;
+}
+
+static int64_t
+ust_to_nanoseconds (CoglRenderer *renderer,
+                    GLXDrawable drawable,
+                    int64_t ust)
+{
+  CoglGLXRenderer *glx_renderer =  renderer->winsys;
+
+  ensure_ust_type (renderer, drawable);
+
+  switch (glx_renderer->ust_type)
+    {
+    case COGL_GLX_UST_IS_UNKNOWN:
+      g_assert_not_reached ();
+      break;
+    case COGL_GLX_UST_IS_GETTIMEOFDAY:
+    case COGL_GLX_UST_IS_MONOTONIC_TIME:
+      return 1000 * ust;
+    case COGL_GLX_UST_IS_OTHER:
+      /* In this case the scale of UST is undefined so we can't easily
+       * scale to nanoseconds.
+       *
+       * For example the driver may be reporting the rdtsc CPU counter
+       * as UST values and so the scale would need to be determined
+       * empirically.
+       *
+       * Potentially we could block for a known duration within
+       * ensure_ust_type() to measure the timescale of UST but for now
+       * we just ignore unknown time sources */
+      return 0;
+    }
+
+  return 0;
+}
+
+static void
+set_sync_pending (CoglOnscreen *onscreen)
+{
+  CoglOnscreenGLX *glx_onscreen = onscreen->winsys;
+  CoglContext *context = COGL_FRAMEBUFFER (onscreen)->context;
+  CoglGLXDisplay *glx_display = context->display->winsys;
+
+  glx_display->pending_sync_notify = TRUE;
+  glx_onscreen->pending_sync_notify = TRUE;
+}
+
+static void
+set_complete_pending (CoglOnscreen *onscreen)
+{
+  CoglOnscreenGLX *glx_onscreen = onscreen->winsys;
+  CoglContext *context = COGL_FRAMEBUFFER (onscreen)->context;
+  CoglGLXDisplay *glx_display = context->display->winsys;
+
+  glx_display->pending_complete_notify = TRUE;
+  glx_onscreen->pending_complete_notify = TRUE;
+}
+
+static void
+notify_swap_buffers (CoglContext *context, GLXBufferSwapComplete *swap_event)
+{
+  CoglOnscreen *onscreen = find_onscreen_for_xid (context, (uint32_t)swap_event->drawable);
   CoglOnscreenGLX *glx_onscreen;
 
   if (!onscreen)
     return;
-
   glx_onscreen = onscreen->winsys;
 
   /* We only want to notify that the swap is complete when the
      application calls cogl_context_dispatch so instead of immediately
      notifying we'll set a flag to remember to notify later */
-  glx_display->pending_swap_notify = TRUE;
-  glx_onscreen->pending_swap_notify = TRUE;
+  set_sync_pending (onscreen);
+
+  if (swap_event->ust != 0)
+    {
+      CoglFrameInfo *info = g_queue_peek_head (&onscreen->pending_frame_infos);
+
+      info->presentation_time =
+        ust_to_nanoseconds (context->display->renderer,
+                            glx_onscreen->glxwin,
+                            swap_event->ust);
+    }
+
+  set_complete_pending (onscreen);
 }
 
 static void
@@ -292,7 +419,7 @@ glx_event_filter_cb (XEvent *xevent, void *data)
     {
       GLXBufferSwapComplete *swap_event = (GLXBufferSwapComplete *) xevent;
 
-      notify_swap_buffers (context, swap_event->drawable);
+      notify_swap_buffers (context, swap_event);
 
       /* remove SwapComplete events from the queue */
       return COGL_FILTER_REMOVE;
@@ -429,8 +556,8 @@ update_base_winsys_features (CoglRenderer *renderer)
                   COGL_WINSYS_FEATURE_MULTIPLE_ONSCREEN,
                   TRUE);
 
-  if (glx_renderer->pf_glXWaitVideoSync ||
-      glx_renderer->pf_glXWaitForMsc)
+  if (glx_renderer->glXWaitVideoSync ||
+      glx_renderer->glXWaitForMsc)
     COGL_FLAGS_SET (glx_renderer->base_winsys_features,
                     COGL_WINSYS_FEATURE_VBLANK_WAIT,
                     TRUE);
@@ -561,10 +688,16 @@ update_winsys_features (CoglContext *context, CoglError **error)
     COGL_FLAGS_SET (context->winsys_features,
                     COGL_WINSYS_FEATURE_SWAP_REGION_THROTTLE, TRUE);
 
-  if (_cogl_winsys_has_feature (COGL_WINSYS_FEATURE_SWAP_BUFFERS_EVENT))
-    COGL_FLAGS_SET (context->features,
-                    COGL_FEATURE_ID_SWAP_BUFFERS_EVENT,
-                    TRUE);
+  if (_cogl_winsys_has_feature (COGL_WINSYS_FEATURE_SYNC_AND_COMPLETE_EVENT))
+    {
+      /* TODO: remove this deprecated feature */
+      COGL_FLAGS_SET (context->features,
+                      COGL_FEATURE_ID_SWAP_BUFFERS_EVENT,
+                      TRUE);
+      COGL_FLAGS_SET (context->features,
+                      COGL_FEATURE_ID_PRESENTATION_TIME,
+                      TRUE);
+    }
 
   return TRUE;
 }
@@ -1103,7 +1236,7 @@ _cogl_winsys_onscreen_init (CoglOnscreen *onscreen,
     }
 
 #ifdef GLX_INTEL_swap_event
-  if (_cogl_winsys_has_feature (COGL_WINSYS_FEATURE_SWAP_BUFFERS_EVENT))
+  if (_cogl_winsys_has_feature (COGL_WINSYS_FEATURE_SYNC_AND_COMPLETE_EVENT))
     {
       GLXDrawable drawable =
         glx_onscreen->glxwin ? glx_onscreen->glxwin : xlib_onscreen->xwin;
@@ -1272,15 +1405,30 @@ _cogl_winsys_onscreen_bind (CoglOnscreen *onscreen)
 }
 
 static void
-_cogl_winsys_wait_for_vblank (CoglContext *ctx)
+_cogl_winsys_wait_for_gpu (CoglOnscreen *onscreen)
 {
+  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
+  CoglContext *ctx = framebuffer->context;
+
+  ctx->glFinish ();
+}
+
+static void
+_cogl_winsys_wait_for_vblank (CoglOnscreen *onscreen)
+{
+  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
+  CoglContext *ctx = framebuffer->context;
   CoglGLXRenderer *glx_renderer;
+  CoglXlibRenderer *xlib_renderer;
 
   glx_renderer = ctx->display->renderer->winsys;
+  xlib_renderer = _cogl_xlib_renderer_get_data (ctx->display->renderer);
 
   if (glx_renderer->glXWaitForMsc ||
       glx_renderer->glXGetVideoSync)
     {
+      CoglFrameInfo *info = g_queue_peek_tail (&onscreen->pending_frame_infos);
+
       if (glx_renderer->glXWaitForMsc)
         {
           CoglOnscreenGLX *glx_onscreen = onscreen->winsys;
@@ -1294,15 +1442,23 @@ _cogl_winsys_wait_for_vblank (CoglContext *ctx)
           glx_renderer->glXWaitForMsc (xlib_renderer->xdpy, drawable,
                                        0, 2, (msc + 1) % 2,
                                        &ust, &msc, &sbc);
+          info->presentation_time = ust_to_nanoseconds (ctx->display->renderer,
+                                                        drawable,
+                                                        ust);
         }
       else
         {
           uint32_t current_count;
+          struct timespec ts;
 
           glx_renderer->glXGetVideoSync (&current_count);
           glx_renderer->glXWaitVideoSync (2,
                                           (current_count + 1) % 2,
                                           &current_count);
+
+          clock_gettime (CLOCK_MONOTONIC, &ts);
+          info->presentation_time =
+            ts.tv_sec * G_GINT64_CONSTANT (1000000000) + ts.tv_nsec;
         }
     }
 }
@@ -1345,6 +1501,22 @@ _cogl_winsys_onscreen_get_buffer_age (CoglOnscreen *onscreen)
 }
 
 static void
+set_frame_info_output (CoglOnscreen *onscreen,
+                       CoglOutput *output)
+{
+  CoglFrameInfo *info = g_queue_peek_tail (&onscreen->pending_frame_infos);
+
+  info->output = output;
+
+  if (output)
+    {
+      float refresh_rate = cogl_output_get_refresh_rate (output);
+      if (refresh_rate != 0.0)
+        info->refresh_rate = refresh_rate;
+    }
+}
+
+static void
 _cogl_winsys_onscreen_swap_region (CoglOnscreen *onscreen,
                                    const int *user_rectangles,
                                    int n_rectangles)
@@ -1361,6 +1533,7 @@ _cogl_winsys_onscreen_swap_region (CoglOnscreen *onscreen,
   uint32_t end_frame_vsync_counter = 0;
   CoglBool have_counter;
   CoglBool can_wait;
+  int x_min = 0, x_max = 0, y_min = 0, y_max = 0;
 
   /*
    * We assume that glXCopySubBuffer is synchronized which means it won't prevent multiple
@@ -1371,6 +1544,7 @@ _cogl_winsys_onscreen_swap_region (CoglOnscreen *onscreen,
   CoglBool blit_sub_buffer_is_synchronized =
      _cogl_winsys_has_feature (COGL_WINSYS_FEATURE_SWAP_REGION_SYNCHRONIZED);
 
+  int framebuffer_width =  cogl_framebuffer_get_width (framebuffer);
   int framebuffer_height =  cogl_framebuffer_get_height (framebuffer);
   int *rectangles = g_alloca (sizeof (int) * n_rectangles * 4);
   int i;
@@ -1382,7 +1556,24 @@ _cogl_winsys_onscreen_swap_region (CoglOnscreen *onscreen,
   for (i = 0; i < n_rectangles; i++)
     {
       int *rect = &rectangles[4 * i];
+
+      if (i == 0)
+        {
+          x_min = rect[0];
+          x_max = rect[0] + rect[2];
+          y_min = rect[1];
+          y_max = rect[1] + rect[3];
+        }
+      else
+        {
+          x_min = MIN (x_min, rect[0]);
+          x_max = MAX (x_max, rect[0] + rect[2]);
+          y_min = MIN (y_min, rect[1]);
+          y_max = MAX (y_max, rect[1] + rect[3]);
+        }
+
       rect[1] = framebuffer_height - rect[1] - rect[3];
+
     }
 
   _cogl_framebuffer_flush_state (framebuffer,
@@ -1436,7 +1627,7 @@ _cogl_winsys_onscreen_swap_region (CoglOnscreen *onscreen,
    *   additional extension so we can report the limited region of
    *   the window damage to X/compositors.
    */
-  context->glFinish ();
+  _cogl_winsys_wait_for_gpu (onscreen);
 
   if (blit_sub_buffer_is_synchronized && have_counter && can_wait)
     {
@@ -1447,10 +1638,10 @@ _cogl_winsys_onscreen_swap_region (CoglOnscreen *onscreen,
        * any waits if we can see that the video sync count has
        * already progressed. */
       if (glx_onscreen->last_swap_vsync_counter == end_frame_vsync_counter)
-        _cogl_winsys_wait_for_vblank (context);
+        _cogl_winsys_wait_for_vblank (onscreen);
     }
   else if (can_wait)
-    _cogl_winsys_wait_for_vblank (context);
+    _cogl_winsys_wait_for_vblank (onscreen);
 
   if (glx_renderer->glXCopySubBuffer)
     {
@@ -1508,6 +1699,36 @@ _cogl_winsys_onscreen_swap_region (CoglOnscreen *onscreen,
    */
   if (have_counter)
     glx_onscreen->last_swap_vsync_counter = end_frame_vsync_counter;
+
+  if (!xlib_onscreen->is_foreign_xwin)
+    {
+      CoglOutput *output;
+
+      x_min = CLAMP (x_min, 0, framebuffer_width);
+      x_max = CLAMP (x_max, 0, framebuffer_width);
+      y_min = CLAMP (y_min, 0, framebuffer_width);
+      y_max = CLAMP (y_max, 0, framebuffer_height);
+
+      output =
+        _cogl_xlib_renderer_output_for_rectangle (context->display->renderer,
+                                                  xlib_onscreen->x + x_min,
+                                                  xlib_onscreen->y + y_min,
+                                                  x_max - x_min,
+                                                  y_max - y_min);
+
+      set_frame_info_output (onscreen, output);
+    }
+
+  /* XXX: we don't get SwapComplete events based on how we implement
+   * the _swap_region() API but if cogl-onscreen.c knows we are
+   * handling _SYNC and _COMPLETE events in the winsys then we need to
+   * send fake events in this case.
+   */
+  if (_cogl_winsys_has_feature (COGL_WINSYS_FEATURE_SYNC_AND_COMPLETE_EVENT))
+    {
+      set_sync_pending (onscreen);
+      set_complete_pending (onscreen);
+    }
 }
 
 static void
@@ -1567,16 +1788,16 @@ _cogl_winsys_onscreen_swap_buffers (CoglOnscreen *onscreen)
            * obviously does not happen when we use _GLX_SWAP and let
            * the driver do the right thing
            */
-          context->glFinish ();
+          _cogl_winsys_wait_for_gpu (onscreen);
 
           if (have_counter && can_wait)
             {
               if (glx_onscreen->last_swap_vsync_counter ==
                   end_frame_vsync_counter)
-                _cogl_winsys_wait_for_vblank (context);
+                _cogl_winsys_wait_for_vblank (onscreen);
             }
           else if (can_wait)
-            _cogl_winsys_wait_for_vblank (context);
+            _cogl_winsys_wait_for_vblank (onscreen);
         }
     }
   else
@@ -1587,6 +1808,8 @@ _cogl_winsys_onscreen_swap_buffers (CoglOnscreen *onscreen)
   if (have_counter)
     glx_onscreen->last_swap_vsync_counter =
       _cogl_winsys_get_vsync_counter (context);
+
+  set_frame_info_output (onscreen, xlib_onscreen->output);
 }
 
 static uint32_t
@@ -2248,7 +2471,9 @@ _cogl_winsys_poll_get_info (CoglContext *context,
 
   /* If we've already got a pending swap notify then we'll dispatch
      immediately */
-  if (glx_display->pending_swap_notify || glx_display->pending_resize_notify)
+  if (glx_display->pending_sync_notify ||
+      glx_display->pending_resize_notify ||
+      glx_display->pending_complete_notify)
     *timeout = 0;
 }
 
@@ -2263,10 +2488,22 @@ flush_pending_notifications_cb (void *data,
       CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer);
       CoglOnscreenGLX *glx_onscreen = onscreen->winsys;
 
-      if (glx_onscreen->pending_swap_notify)
+      if (glx_onscreen->pending_sync_notify)
         {
-          _cogl_onscreen_notify_swap_buffers (onscreen);
-          glx_onscreen->pending_swap_notify = FALSE;
+          CoglFrameInfo *info = g_queue_peek_head (&onscreen->pending_frame_infos);
+
+          _cogl_onscreen_notify_frame_sync (onscreen, info);
+          glx_onscreen->pending_sync_notify = FALSE;
+        }
+
+      if (glx_onscreen->pending_complete_notify)
+        {
+          CoglFrameInfo *info = g_queue_pop_head (&onscreen->pending_frame_infos);
+
+          _cogl_onscreen_notify_complete (onscreen, info);
+          glx_onscreen->pending_complete_notify = FALSE;
+
+          cogl_object_unref (info);
         }
 
       if (glx_onscreen->pending_resize_notify)
@@ -2289,13 +2526,16 @@ _cogl_winsys_poll_dispatch (CoglContext *context,
                                      poll_fds,
                                      n_poll_fds);
 
-  if (glx_display->pending_swap_notify || glx_display->pending_resize_notify)
+  if (glx_display->pending_sync_notify ||
+      glx_display->pending_resize_notify ||
+      glx_display->pending_complete_notify)
     {
       g_list_foreach (context->framebuffers,
                       flush_pending_notifications_cb,
                       NULL);
-      glx_display->pending_swap_notify = FALSE;
+      glx_display->pending_sync_notify = FALSE;
       glx_display->pending_resize_notify = FALSE;
+      glx_display->pending_complete_notify = FALSE;
     }
 }
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]