From 24733abf68ba326e415c6a17c0c1e59c3d303003 Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Mon, 12 Nov 2012 11:58:10 -0500 Subject: [PATCH] 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 (cherry picked from commit 700401667db2522045e4623d78797b17f9184501) --- 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 | 237 ++++++++++++-- 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-glx-feature-functions.h | 3 +- cogl/winsys/cogl-winsys-glx.c | 298 ++++++++++++++++-- 20 files changed, 972 insertions(+), 69 deletions(-) create mode 100644 cogl/cogl-frame-info-private.h create mode 100644 cogl/cogl-frame-info.c create mode 100644 cogl/cogl-frame-info.h diff --git a/cogl/Makefile.am b/cogl/Makefile.am index bf24f6355..33214abb6 100644 --- a/cogl/Makefile.am +++ b/cogl/Makefile.am @@ -112,6 +112,7 @@ cogl_experimental_h = \ $(srcdir)/cogl-clip-state.h \ $(srcdir)/cogl-framebuffer.h \ $(srcdir)/cogl-onscreen.h \ + $(srcdir)/cogl-frame-info.h \ $(srcdir)/cogl-vector.h \ $(srcdir)/cogl-euler.h \ $(srcdir)/cogl-output.h \ @@ -384,6 +385,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 2780a59d7..4c3835451 100644 --- a/cogl/cogl-context-private.h +++ b/cogl/cogl-context-private.h @@ -50,6 +50,7 @@ #include "cogl-gpu-info-private.h" #include "cogl-gl-header.h" #include "cogl-framebuffer-private.h" +#include "cogl-onscreen-private.h" typedef struct { @@ -190,6 +191,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 6e0367345..d63ce1f1d 100644 --- a/cogl/cogl-context.c +++ b/cogl/cogl-context.c @@ -312,6 +312,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 = @@ -507,6 +512,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 3356440fb..00b24fb06 100644 --- a/cogl/cogl-context.h +++ b/cogl/cogl-context.h @@ -208,6 +208,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 @@ -237,6 +239,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 000000000..79b2a41fa --- /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 . + * + * + */ + +#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 000000000..a64e728d9 --- /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 . + * + * + */ + +#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 000000000..36f8a0468 --- /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 + * . + * + * + * + * Authors: + * Owen Taylor + */ +#if !defined(__COGL_H_INSIDE__) && !defined(COGL_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __COGL_FRAME_INFO_H +#define __COGL_FRAME_INFO_H + +#include +#include +#include + +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. + * + * 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. + * + * 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. + * + * 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(). + * + * 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 1ffcd3211..69b1570e9 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 b70a78ea7..f430a40b8 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 06bdd4dd4..925fa60c5 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 #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; }; @@ -91,9 +110,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 1e2243081..5f72454e6 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" @@ -45,7 +46,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; @@ -115,7 +116,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))) { @@ -123,12 +125,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); + if (framebuffer->context->window_buffer == COGL_FRAMEBUFFER (onscreen)) framebuffer->context->window_buffer = NULL; @@ -141,22 +151,60 @@ _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); + /* FIXME: we shouldn't need to flush *all* journals here! */ cogl_flush (); + winsys = _cogl_framebuffer_get_winsys (framebuffer); winsys->onscreen_swap_buffers (COGL_ONSCREEN (framebuffer)); cogl_framebuffer_discard_buffers (framebuffer, 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 @@ -166,9 +214,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); + /* FIXME: we shouldn't need to flush *all* journals here! */ cogl_flush (); @@ -186,6 +239,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 @@ -286,38 +355,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 @@ -365,16 +503,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 (COGL_FRAMEBUFFER (onscreen), entry->user_data); + { + 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) + { + 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 @@ -468,3 +646,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 5ae97e234..886abd9e3 100644 --- a/cogl/cogl-onscreen.h +++ b/cogl/cogl-onscreen.h @@ -34,6 +34,8 @@ #include #include +#include +#include COGL_BEGIN_DECLS @@ -380,6 +382,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. + * + * 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. + * + * 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); @@ -407,6 +546,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, @@ -423,6 +563,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, @@ -580,6 +721,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 86be2cf03..c6d19fbf5 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 03afeb7a6..b49890f6b 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 f1f281da2..227b7ec8f 100644 --- a/cogl/cogl-types.h +++ b/cogl/cogl-types.h @@ -832,6 +832,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 54a5639e8..0b70012e5 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 f8b010c7a..72be57d53 100644 --- a/cogl/cogl.h +++ b/cogl/cogl.h @@ -105,6 +105,7 @@ #include #include #include +#include #include #if defined (COGL_HAS_EGL_PLATFORM_KMS_SUPPORT) #include diff --git a/cogl/winsys/cogl-winsys-egl-kms.c b/cogl/winsys/cogl-winsys-egl-kms.c index ff538e249..db2b4966a 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 81b75e467..9e7b49d8c 100644 --- a/cogl/winsys/cogl-winsys-glx-feature-functions.h +++ b/cogl/winsys/cogl-winsys-glx-feature-functions.h @@ -177,7 +177,8 @@ COGL_WINSYS_FEATURE_BEGIN (255, 255, "INTEL\0", "swap_event\0", 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 f51f457c3..381c3a885 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 #include #include +#include #include +#include #include @@ -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; @@ -171,23 +175,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 @@ -295,7 +422,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; @@ -434,8 +561,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); @@ -569,10 +696,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; } @@ -1111,7 +1244,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; @@ -1280,15 +1413,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; @@ -1302,15 +1450,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 (¤t_count); glx_renderer->glXWaitVideoSync (2, (current_count + 1) % 2, ¤t_count); + + clock_gettime (CLOCK_MONOTONIC, &ts); + info->presentation_time = + ts.tv_sec * G_GINT64_CONSTANT (1000000000) + ts.tv_nsec; } } } @@ -1352,6 +1508,22 @@ _cogl_winsys_onscreen_get_buffer_age (CoglOnscreen *onscreen) return age; } +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, @@ -1369,6 +1541,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 @@ -1379,6 +1552,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; @@ -1390,7 +1564,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, @@ -1444,7 +1635,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) { @@ -1455,10 +1646,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) { @@ -1516,6 +1707,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 @@ -1575,16 +1796,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 @@ -1595,6 +1816,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 @@ -2257,7 +2480,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; } @@ -2272,10 +2497,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) @@ -2298,13 +2535,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; } }