Add GdkFrameHistory and GdkFrameTimings, handle _NET_WM_FRAME_TIMINGS

In order to be able to track statistics about how well we are drawing,
and in order to be able to do sophisticated things with frame timing
like predicting per-frame latencies and synchronizing audio with video,
we need to be able to track exactly when previous frames were drawn
to the screen.

Information about each frame is stored in a new GdkFrameTimings object.
A new GdkFrameHistory object is added which keeps a queue of recent
GdkFrameTimings (this is added to avoid further complicating the
implementation of GdkFrameClock.)

https://bugzilla.gnome.org/show_bug.cgi?id=685460
This commit is contained in:
Owen W. Taylor 2012-11-14 12:49:06 -05:00
parent d761df7e0c
commit 15ee04c66f
10 changed files with 575 additions and 16 deletions

View File

@ -75,6 +75,8 @@ gdk_public_h_sources = \
gdkdisplaymanager.h \
gdkdnd.h \
gdkevents.h \
gdkframehistory.h \
gdkframetimings.h \
gdkkeys.h \
gdkkeysyms.h \
gdkkeysyms-compat.h \
@ -123,6 +125,8 @@ gdk_c_sources = \
gdkdisplaymanager.c \
gdkdnd.c \
gdkevents.c \
gdkframehistory.c \
gdkframetimings.c \
gdkglobals.c \
gdkkeys.c \
gdkkeyuni.c \

View File

@ -304,6 +304,23 @@ gdk_frame_clock_thaw (GdkFrameClock *clock)
GDK_FRAME_CLOCK_GET_IFACE (clock)->thaw (clock);
}
/**
* gdk_frame_clock_get_history:
* @clock: the clock
*
* Gets the #GdkFrameHistory for the frame clock.
*
* Since: 3.8
* Return value: (transfer none): the frame history object
*/
GdkFrameHistory *
gdk_frame_clock_get_history (GdkFrameClock *clock)
{
g_return_val_if_fail (GDK_IS_FRAME_CLOCK (clock), NULL);
return GDK_FRAME_CLOCK_GET_IFACE (clock)->get_history (clock);
}
/**
* gdk_frame_clock_get_requested:
* @clock: the clock

View File

@ -31,7 +31,7 @@
#ifndef __GDK_FRAME_CLOCK_H__
#define __GDK_FRAME_CLOCK_H__
#include <glib-object.h>
#include <gdk/gdkframehistory.h>
G_BEGIN_DECLS
@ -87,6 +87,8 @@ struct _GdkFrameClockInterface
void (* freeze) (GdkFrameClock *clock);
void (* thaw) (GdkFrameClock *clock);
GdkFrameHistory * (* get_history) (GdkFrameClock *clock);
/* signals */
/* void (* frame_requested) (GdkFrameClock *clock); */
/* void (* flush_events) (GdkFrameClock *clock); */
@ -109,6 +111,8 @@ GdkFrameClockPhase gdk_frame_clock_get_requested (GdkFrameClock *clock);
void gdk_frame_clock_freeze (GdkFrameClock *clock);
void gdk_frame_clock_thaw (GdkFrameClock *clock);
GdkFrameHistory *gdk_frame_clock_get_history (GdkFrameClock *clock);
/* Convenience API */
void gdk_frame_clock_get_frame_time_val (GdkFrameClock *clock,
GTimeVal *timeval);

View File

@ -33,6 +33,7 @@
struct _GdkFrameClockIdlePrivate
{
GdkFrameHistory *history;
GTimer *timer;
/* timer_base is used to avoid ever going backward */
guint64 timer_base;
@ -77,6 +78,7 @@ gdk_frame_clock_idle_init (GdkFrameClockIdle *frame_clock_idle)
GdkFrameClockIdlePrivate);
priv = frame_clock_idle->priv;
priv->history = gdk_frame_history_new ();
priv->timer = g_timer_new ();
priv->freeze_count = 0;
}
@ -229,7 +231,14 @@ gdk_frame_clock_paint_idle (void *data)
case GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT:
if (priv->freeze_count == 0)
{
GdkFrameTimings *timings;
gint64 frame_counter;
priv->frame_time = compute_frame_time (clock_idle);
gdk_frame_history_begin_frame (priv->history);
frame_counter = gdk_frame_history_get_frame_counter (priv->history);
timings = gdk_frame_history_get_timings (priv->history, frame_counter);
gdk_frame_timings_set_frame_time (timings, priv->frame_time);
priv->phase = GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT;
@ -370,6 +379,15 @@ gdk_frame_clock_idle_thaw (GdkFrameClock *clock)
}
}
static GdkFrameHistory *
gdk_frame_clock_idle_get_history (GdkFrameClock *clock)
{
GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock);
GdkFrameClockIdlePrivate *priv = clock_idle->priv;
return priv->history;
}
static void
gdk_frame_clock_idle_interface_init (GdkFrameClockInterface *iface)
{
@ -378,6 +396,7 @@ gdk_frame_clock_idle_interface_init (GdkFrameClockInterface *iface)
iface->get_requested = gdk_frame_clock_idle_get_requested;
iface->freeze = gdk_frame_clock_idle_freeze;
iface->thaw = gdk_frame_clock_idle_thaw;
iface->get_history = gdk_frame_clock_idle_get_history;
}
GdkFrameClock *

143
gdk/gdkframehistory.c Normal file
View File

@ -0,0 +1,143 @@
/* GDK - The GIMP Drawing Kit
* 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/>.
*/
#include "config.h"
#include "gdkframehistory.h"
#define FRAME_HISTORY_MAX_LENGTH 16
struct _GdkFrameHistory
{
GObject parent_instance;
gint64 frame_counter;
gint n_timings;
gint current;
GdkFrameTimings *timings[FRAME_HISTORY_MAX_LENGTH];
};
struct _GdkFrameHistoryClass
{
GObjectClass parent_class;
};
G_DEFINE_TYPE (GdkFrameHistory, gdk_frame_history, G_TYPE_OBJECT)
static void
gdk_frame_history_finalize (GObject *object)
{
GdkFrameHistory *history = GDK_FRAME_HISTORY (object);
int i;
for (i = 0; i < FRAME_HISTORY_MAX_LENGTH; i++)
if (history->timings[i] != 0)
gdk_frame_timings_unref (history->timings[i]);
G_OBJECT_CLASS (gdk_frame_history_parent_class)->finalize (object);
}
static void
gdk_frame_history_class_init (GdkFrameHistoryClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gdk_frame_history_finalize;
}
static void
gdk_frame_history_init (GdkFrameHistory *history)
{
history->frame_counter = -1;
history->current = FRAME_HISTORY_MAX_LENGTH - 1;
}
GdkFrameHistory *
gdk_frame_history_new (void)
{
return g_object_new (GDK_TYPE_FRAME_HISTORY, NULL);
}
gint64
gdk_frame_history_get_frame_counter (GdkFrameHistory *history)
{
g_return_val_if_fail (GDK_IS_FRAME_HISTORY (history), 0);
return history->frame_counter;
}
gint64
gdk_frame_history_get_start (GdkFrameHistory *history)
{
g_return_val_if_fail (GDK_IS_FRAME_HISTORY (history), 0);
return history->frame_counter + 1 - history->n_timings;
}
void
gdk_frame_history_begin_frame (GdkFrameHistory *history)
{
g_return_if_fail (GDK_IS_FRAME_HISTORY (history));
history->frame_counter++;
history->current = (history->current + 1) % FRAME_HISTORY_MAX_LENGTH;
if (history->n_timings < FRAME_HISTORY_MAX_LENGTH)
history->n_timings++;
else
{
gdk_frame_timings_unref(history->timings[history->current]);
}
history->timings[history->current] = gdk_frame_timings_new (history->frame_counter);
}
GdkFrameTimings *
gdk_frame_history_get_timings (GdkFrameHistory *history,
gint64 frame_counter)
{
gint pos;
g_return_val_if_fail (GDK_IS_FRAME_HISTORY (history), NULL);
if (frame_counter > history->frame_counter)
return NULL;
if (frame_counter <= history->frame_counter - history->n_timings)
return NULL;
pos = (history->current - (history->frame_counter - frame_counter) + FRAME_HISTORY_MAX_LENGTH) % FRAME_HISTORY_MAX_LENGTH;
return history->timings[pos];
}
GdkFrameTimings *
gdk_frame_history_get_last_complete (GdkFrameHistory *history)
{
gint i;
g_return_val_if_fail (GDK_IS_FRAME_HISTORY (history), NULL);
for (i = 0; i < history->n_timings; i++)
{
gint pos = ((history->current - i) + FRAME_HISTORY_MAX_LENGTH) % FRAME_HISTORY_MAX_LENGTH;
if (gdk_frame_timings_get_complete (history->timings[pos]))
return history->timings[pos];
}
return NULL;
}

49
gdk/gdkframehistory.h Normal file
View File

@ -0,0 +1,49 @@
/* GDK - The GIMP Drawing Kit
* 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/>.
*/
#if !defined (__GDK_H_INSIDE__) && !defined (GDK_COMPILATION)
#error "Only <gdk/gdk.h> can be included directly."
#endif
#ifndef __GDK_FRAME_HISTORY_H__
#define __GDK_FRAME_HISTORY_H__
#include <gdk/gdkframetimings.h>
G_BEGIN_DECLS
typedef struct _GdkFrameHistory GdkFrameHistory;
typedef struct _GdkFrameHistoryClass GdkFrameHistoryClass;
#define GDK_TYPE_FRAME_HISTORY (gdk_frame_history_get_type ())
#define GDK_FRAME_HISTORY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDK_TYPE_FRAME_HISTORY, GdkFrameHistory))
#define GDK_IS_FRAME_HISTORY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDK_TYPE_FRAME_HISTORY))
GType gdk_frame_history_get_type (void) G_GNUC_CONST;
GdkFrameHistory *gdk_frame_history_new (void);
gint64 gdk_frame_history_get_frame_counter (GdkFrameHistory *history);
gint64 gdk_frame_history_get_start (GdkFrameHistory *history);
void gdk_frame_history_begin_frame (GdkFrameHistory *history);
GdkFrameTimings *gdk_frame_history_get_timings (GdkFrameHistory *history,
gint64 frame_counter);
GdkFrameTimings *gdk_frame_history_get_last_complete (GdkFrameHistory *history);
G_END_DECLS
#endif /* __GDK_FRAME_HISTORY_H__ */

180
gdk/gdkframetimings.c Normal file
View File

@ -0,0 +1,180 @@
/* GDK - The GIMP Drawing Kit
* 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/>.
*/
#include "config.h"
#include "gdkframetimings.h"
struct _GdkFrameTimings
{
guint ref_count;
gboolean complete;
gint64 frame_counter;
guint64 cookie;
gint64 frame_time;
gint64 drawn_time;
gint64 presentation_time;
gint64 refresh_interval;
};
G_DEFINE_BOXED_TYPE (GdkFrameTimings, gdk_frame_timings,
gdk_frame_timings_ref,
gdk_frame_timings_unref)
GdkFrameTimings *
gdk_frame_timings_new (gint64 frame_counter)
{
GdkFrameTimings *timings;
timings = g_slice_new0 (GdkFrameTimings);
timings->ref_count = 1;
timings->frame_counter = frame_counter;
return timings;
}
GdkFrameTimings *
gdk_frame_timings_ref (GdkFrameTimings *timings)
{
g_return_val_if_fail (timings != NULL, NULL);
timings->ref_count++;
return timings;
}
void
gdk_frame_timings_unref (GdkFrameTimings *timings)
{
g_return_if_fail (timings != NULL);
g_return_if_fail (timings->ref_count > 0);
timings->ref_count--;
if (timings->ref_count == 0)
{
g_slice_free (GdkFrameTimings, timings);
}
}
gint64
gdk_frame_timings_get_frame_counter (GdkFrameTimings *timings)
{
return timings->frame_counter;
}
guint64
gdk_frame_timings_get_cookie (GdkFrameTimings *timings)
{
g_return_val_if_fail (timings != NULL, 0);
return timings->cookie;
}
void
gdk_frame_timings_set_cookie (GdkFrameTimings *timings,
guint64 cookie)
{
g_return_if_fail (timings != NULL);
timings->cookie = cookie;
}
gboolean
gdk_frame_timings_get_complete (GdkFrameTimings *timings)
{
g_return_val_if_fail (timings != NULL, FALSE);
return timings->complete;
}
void
gdk_frame_timings_set_complete (GdkFrameTimings *timings,
gboolean complete)
{
g_return_if_fail (timings != NULL);
timings->complete = complete;
}
gint64
gdk_frame_timings_get_frame_time (GdkFrameTimings *timings)
{
g_return_val_if_fail (timings != NULL, 0);
return timings->frame_time;
}
void
gdk_frame_timings_set_frame_time (GdkFrameTimings *timings,
gint64 frame_time)
{
g_return_if_fail (timings != NULL);
timings->frame_time = frame_time;
}
gint64
gdk_frame_timings_get_drawn_time (GdkFrameTimings *timings)
{
g_return_val_if_fail (timings != NULL, 0);
return timings->drawn_time;
}
void
gdk_frame_timings_set_drawn_time (GdkFrameTimings *timings,
gint64 drawn_time)
{
g_return_if_fail (timings != NULL);
timings->drawn_time = drawn_time;
}
gint64
gdk_frame_timings_get_presentation_time (GdkFrameTimings *timings)
{
g_return_val_if_fail (timings != NULL, 0);
return timings->presentation_time;
}
void
gdk_frame_timings_set_presentation_time (GdkFrameTimings *timings,
gint64 presentation_time)
{
g_return_if_fail (timings != NULL);
timings->presentation_time = presentation_time;
}
gint64
gdk_frame_timings_get_refresh_interval (GdkFrameTimings *timings)
{
g_return_val_if_fail (timings != NULL, 0);
return timings->refresh_interval;
}
void
gdk_frame_timings_set_refresh_interval (GdkFrameTimings *timings,
gint64 refresh_interval)
{
g_return_if_fail (timings != NULL);
timings->refresh_interval = refresh_interval;
}

63
gdk/gdkframetimings.h Normal file
View File

@ -0,0 +1,63 @@
/* GDK - The GIMP Drawing Kit
* 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/>.
*/
#if !defined (__GDK_H_INSIDE__) && !defined (GDK_COMPILATION)
#error "Only <gdk/gdk.h> can be included directly."
#endif
#ifndef __GDK_FRAME_TIMINGS_H__
#define __GDK_FRAME_TIMINGS_H__
#include <glib-object.h>
G_BEGIN_DECLS
typedef struct _GdkFrameTimings GdkFrameTimings;
GType gdk_frame_timings_get_type (void) G_GNUC_CONST;
GdkFrameTimings *gdk_frame_timings_new (gint64 frame_counter);
GdkFrameTimings *gdk_frame_timings_ref (GdkFrameTimings *timings);
void gdk_frame_timings_unref (GdkFrameTimings *timings);
gint64 gdk_frame_timings_get_frame_counter (GdkFrameTimings *timings);
guint64 gdk_frame_timings_get_cookie (GdkFrameTimings *timings);
void gdk_frame_timings_set_cookie (GdkFrameTimings *timings,
guint64 cookie);
gboolean gdk_frame_timings_get_complete (GdkFrameTimings *timings);
void gdk_frame_timings_set_complete (GdkFrameTimings *timings,
gboolean complete);
gint64 gdk_frame_timings_get_frame_time (GdkFrameTimings *timings);
void gdk_frame_timings_set_frame_time (GdkFrameTimings *timings,
gint64 frame_time);
gint64 gdk_frame_timings_get_drawn_time (GdkFrameTimings *timings);
void gdk_frame_timings_set_drawn_time (GdkFrameTimings *timings,
gint64 frame_time);
gint64 gdk_frame_timings_get_presentation_time (GdkFrameTimings *timings);
void gdk_frame_timings_set_presentation_time (GdkFrameTimings *timings,
gint64 presentation_time);
gint64 gdk_frame_timings_get_refresh_interval (GdkFrameTimings *timings);
void gdk_frame_timings_set_refresh_interval (GdkFrameTimings *timings,
gint64 refresh_interval);
G_END_DECLS
#endif /* __GDK_FRAME_TIMINGS_H__ */

View File

@ -1056,6 +1056,26 @@ gdk_x11_display_translate_event (GdkEventTranslator *translator,
return return_val;
}
static GdkFrameTimings *
find_frame_timings (GdkFrameClock *clock,
guint64 serial)
{
GdkFrameHistory *history = gdk_frame_clock_get_history (clock);
gint64 start_frame, end_frame, i;
start_frame = gdk_frame_history_get_start (history);
end_frame = gdk_frame_history_get_frame_counter (history);
for (i = end_frame; i >= start_frame; i--)
{
GdkFrameTimings *timings = gdk_frame_history_get_timings (history, i);
if (gdk_frame_timings_get_cookie (timings) == serial)
return timings;
}
return NULL;
}
GdkFilterReturn
_gdk_wm_protocols_filter (GdkXEvent *xev,
GdkEvent *event,
@ -1074,6 +1094,71 @@ _gdk_wm_protocols_filter (GdkXEvent *xev,
display = GDK_WINDOW_DISPLAY (win);
/* This isn't actually WM_PROTOCOLS because that wouldn't leave enough space
* in the message for everything that gets stuffed in */
if (xevent->xclient.message_type == gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_FRAME_DRAWN"))
{
GdkWindowImplX11 *window_impl;
window_impl = GDK_WINDOW_IMPL_X11 (event->any.window->impl);
if (window_impl->toplevel)
{
guint32 d0 = xevent->xclient.data.l[0];
guint32 d1 = xevent->xclient.data.l[1];
guint32 d2 = xevent->xclient.data.l[2];
guint32 d3 = xevent->xclient.data.l[3];
guint64 serial = ((guint64)d0 << 32) | d1;
GdkFrameClock *clock = gdk_window_get_frame_clock (event->any.window);
GdkFrameTimings *timings = find_frame_timings (clock, serial);
if (timings)
gdk_frame_timings_set_drawn_time (timings, ((guint64)d2 << 32) | d3);
if (window_impl->toplevel->frame_pending)
{
window_impl->toplevel->frame_pending = FALSE;
gdk_frame_clock_thaw (clock);
}
}
return GDK_FILTER_REMOVE;
}
if (xevent->xclient.message_type == gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_FRAME_TIMINGS"))
{
GdkWindowImplX11 *window_impl;
window_impl = GDK_WINDOW_IMPL_X11 (event->any.window->impl);
if (window_impl->toplevel)
{
guint32 d0 = xevent->xclient.data.l[0];
guint32 d1 = xevent->xclient.data.l[1];
guint32 d2 = xevent->xclient.data.l[2];
guint32 d3 = xevent->xclient.data.l[3];
guint64 serial = ((guint64)d0 << 32) | d1;
GdkFrameClock *clock = gdk_window_get_frame_clock (event->any.window);
GdkFrameTimings *timings = find_frame_timings (clock, serial);
if (timings)
{
gint64 drawn_time = gdk_frame_timings_get_drawn_time (timings);
gint32 presentation_time_offset = (gint32)d2;
gint32 refresh_interval = d3;
if (drawn_time && presentation_time_offset)
gdk_frame_timings_set_presentation_time (timings,
drawn_time + presentation_time_offset);
if (refresh_interval)
gdk_frame_timings_set_refresh_interval (timings, refresh_interval);
gdk_frame_timings_set_complete (timings, TRUE);
}
}
}
if (xevent->xclient.message_type != gdk_x11_get_xatom_by_name_for_display (display, "WM_PROTOCOLS"))
return GDK_FILTER_CONTINUE;
@ -1145,21 +1230,6 @@ _gdk_wm_protocols_filter (GdkXEvent *xev,
return GDK_FILTER_REMOVE;
}
else if (atom == gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_FRAME_DRAWN"))
{
GdkWindowImplX11 *window_impl;
window_impl = GDK_WINDOW_IMPL_X11 (event->any.window->impl);
if (window_impl->toplevel &&
window_impl->toplevel->frame_pending)
{
window_impl->toplevel->frame_pending = FALSE;
gdk_frame_clock_thaw (gdk_window_get_frame_clock (event->any.window));
}
return GDK_FILTER_REMOVE;
}
return GDK_FILTER_CONTINUE;
}

View File

@ -853,7 +853,17 @@ static void
on_frame_clock_after_paint (GdkFrameClock *clock,
GdkWindow *window)
{
GdkToplevelX11 *toplevel = _gdk_x11_window_get_toplevel (window);
GdkFrameHistory *history = gdk_frame_clock_get_history (clock);
gint64 frame_counter = gdk_frame_history_get_frame_counter (history);
GdkFrameTimings *timings = gdk_frame_history_get_timings (history, frame_counter);
gdk_x11_window_end_frame (window);
if (toplevel->frame_pending)
gdk_frame_timings_set_cookie (timings, toplevel->current_counter_value);
else
gdk_frame_timings_set_complete (timings, TRUE);
}
void