forked from AuroraMiddleware/gtk
Add gdk_frame_timings_get_predicted_presentation_time()
For an operation like synchronizing audio to video playback, we need to be able to predict the time that a frame will be presented. The details of this depend on the windowing system, so make the backend predict a presentation time for ::begin-frame and set it on the GdkFrameTimings. The timing algorithm of GdkFrameClockIdle is adjusted to give predictable presentation times for frames that are not throttled by the windowing system. Helper functions: gdk_frame_clock_get_current_frame_timings() gdk_frame_clock_get_refresh_info() are added for operations that would otherwise be needed multiple times in different locations. https://bugzilla.gnome.org/show_bug.cgi?id=685460
This commit is contained in:
parent
8855bf052d
commit
fb44ea8a85
@ -378,3 +378,76 @@ gdk_frame_clock_frame_requested (GdkFrameClock *clock)
|
||||
g_signal_emit (G_OBJECT (clock),
|
||||
signals[FRAME_REQUESTED], 0);
|
||||
}
|
||||
|
||||
GdkFrameTimings *
|
||||
gdk_frame_clock_get_current_frame_timings (GdkFrameClock *clock)
|
||||
{
|
||||
GdkFrameHistory *history;
|
||||
gint64 frame_counter;
|
||||
|
||||
g_return_val_if_fail (GDK_IS_FRAME_CLOCK (clock), 0);
|
||||
|
||||
history = gdk_frame_clock_get_history (clock);
|
||||
frame_counter = gdk_frame_history_get_frame_counter (history);
|
||||
return gdk_frame_history_get_timings (history, frame_counter);
|
||||
}
|
||||
|
||||
|
||||
#define DEFAULT_REFRESH_INTERVAL 16667 /* 16.7ms (1/60th second) */
|
||||
#define MAX_HISTORY_AGE 150000 /* 150ms */
|
||||
|
||||
void
|
||||
gdk_frame_clock_get_refresh_info (GdkFrameClock *clock,
|
||||
gint64 base_time,
|
||||
gint64 *refresh_interval_return,
|
||||
gint64 *presentation_time_return)
|
||||
{
|
||||
GdkFrameHistory *history;
|
||||
gint64 frame_counter;
|
||||
|
||||
g_return_if_fail (GDK_IS_FRAME_CLOCK (clock));
|
||||
|
||||
history = gdk_frame_clock_get_history (clock);
|
||||
frame_counter = gdk_frame_history_get_frame_counter (history);
|
||||
|
||||
if (presentation_time_return)
|
||||
*presentation_time_return = 0;
|
||||
if (refresh_interval_return)
|
||||
*refresh_interval_return = DEFAULT_REFRESH_INTERVAL;
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
GdkFrameTimings *timings = gdk_frame_history_get_timings (history, frame_counter);
|
||||
gint64 presentation_time;
|
||||
gint64 refresh_interval;
|
||||
|
||||
if (timings == NULL)
|
||||
return;
|
||||
|
||||
refresh_interval = gdk_frame_timings_get_refresh_interval (timings);
|
||||
presentation_time = gdk_frame_timings_get_presentation_time (timings);
|
||||
|
||||
if (presentation_time != 0)
|
||||
{
|
||||
if (presentation_time > base_time - MAX_HISTORY_AGE &&
|
||||
presentation_time_return)
|
||||
{
|
||||
if (refresh_interval == 0)
|
||||
refresh_interval = DEFAULT_REFRESH_INTERVAL;
|
||||
|
||||
if (refresh_interval_return)
|
||||
*refresh_interval_return = refresh_interval;
|
||||
|
||||
while (presentation_time < base_time)
|
||||
presentation_time += refresh_interval;
|
||||
|
||||
if (presentation_time_return)
|
||||
*presentation_time_return = presentation_time;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
frame_counter--;
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ struct _GdkFrameClockInterface
|
||||
{
|
||||
GTypeInterface base_iface;
|
||||
|
||||
guint64 (* get_frame_time) (GdkFrameClock *clock);
|
||||
guint64 (* get_frame_time) (GdkFrameClock *clock);
|
||||
|
||||
void (* request_phase) (GdkFrameClock *clock,
|
||||
GdkFrameClockPhase phase);
|
||||
@ -102,7 +102,7 @@ struct _GdkFrameClockInterface
|
||||
|
||||
GType gdk_frame_clock_get_type (void) G_GNUC_CONST;
|
||||
|
||||
guint64 gdk_frame_clock_get_frame_time (GdkFrameClock *clock);
|
||||
guint64 gdk_frame_clock_get_frame_time (GdkFrameClock *clock);
|
||||
|
||||
void gdk_frame_clock_request_phase (GdkFrameClock *clock,
|
||||
GdkFrameClockPhase phase);
|
||||
@ -117,6 +117,13 @@ GdkFrameHistory *gdk_frame_clock_get_history (GdkFrameClock *clock);
|
||||
void gdk_frame_clock_get_frame_time_val (GdkFrameClock *clock,
|
||||
GTimeVal *timeval);
|
||||
|
||||
void gdk_frame_clock_get_refresh_info (GdkFrameClock *clock,
|
||||
gint64 base_time,
|
||||
gint64 *refresh_interval_return,
|
||||
gint64 *presentation_time_return);
|
||||
|
||||
GdkFrameTimings *gdk_frame_clock_get_current_frame_timings (GdkFrameClock *clock);
|
||||
|
||||
/* Signal emitters (used in frame clock implementations) */
|
||||
void gdk_frame_clock_frame_requested (GdkFrameClock *clock);
|
||||
|
||||
|
@ -204,7 +204,7 @@ maybe_start_idle (GdkFrameClockIdle *clock_idle)
|
||||
{
|
||||
GdkFrameClockIdlePrivate *priv = clock_idle->priv;
|
||||
|
||||
if (priv->freeze_count == 0)
|
||||
if (priv->freeze_count == 0 && priv->requested != 0)
|
||||
{
|
||||
guint min_interval = 0;
|
||||
|
||||
@ -240,6 +240,23 @@ maybe_start_idle (GdkFrameClockIdle *clock_idle)
|
||||
}
|
||||
}
|
||||
|
||||
static gint64
|
||||
compute_min_next_frame_time (GdkFrameClockIdle *clock_idle,
|
||||
gint64 last_frame_time)
|
||||
{
|
||||
gint64 presentation_time;
|
||||
gint64 refresh_interval;
|
||||
|
||||
gdk_frame_clock_get_refresh_info (GDK_FRAME_CLOCK (clock_idle),
|
||||
last_frame_time,
|
||||
&refresh_interval, &presentation_time);
|
||||
|
||||
if (presentation_time == 0)
|
||||
return last_frame_time + refresh_interval;
|
||||
else
|
||||
return presentation_time + refresh_interval / 2;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gdk_frame_clock_flush_idle (void *data)
|
||||
{
|
||||
@ -277,6 +294,7 @@ gdk_frame_clock_paint_idle (void *data)
|
||||
|
||||
priv->paint_idle_id = 0;
|
||||
priv->in_paint_idle = TRUE;
|
||||
priv->min_next_frame_time = 0;
|
||||
|
||||
skip_to_resume_events =
|
||||
(priv->requested & ~(GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS | GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS)) == 0;
|
||||
@ -403,19 +421,16 @@ gdk_frame_clock_paint_idle (void *data)
|
||||
|
||||
priv->in_paint_idle = FALSE;
|
||||
|
||||
if (priv->freeze_count == 0 && priv->requested != 0)
|
||||
/* If there is throttling in the backend layer, then we'll do another
|
||||
* update as soon as the backend unthrottles (if there is work to do),
|
||||
* otherwise we need to figure when the next frame should be.
|
||||
*/
|
||||
if (priv->freeze_count == 0)
|
||||
{
|
||||
/* We need to start over again immediately - this implies that there is no
|
||||
* throttling at the backend layer, so we need to back-off ourselves.
|
||||
*/
|
||||
gdk_flush ();
|
||||
priv->min_next_frame_time = priv->frame_time + FRAME_INTERVAL;
|
||||
priv->min_next_frame_time = compute_min_next_frame_time (clock_idle,
|
||||
priv->frame_time);
|
||||
maybe_start_idle (clock_idle);
|
||||
}
|
||||
else
|
||||
{
|
||||
priv->min_next_frame_time = 0;
|
||||
}
|
||||
|
||||
if (priv->freeze_count == 0)
|
||||
priv->sleep_serial = get_sleep_serial ();
|
||||
|
@ -154,6 +154,7 @@ _gdk_frame_history_debug_print (GdkFrameHistory *history,
|
||||
gint64 frame_end_time = _gdk_frame_timings_get_frame_end_time (timings);
|
||||
gint64 frame_time = gdk_frame_timings_get_frame_time (timings);
|
||||
gint64 presentation_time = gdk_frame_timings_get_presentation_time (timings);
|
||||
gint64 predicted_presentation_time = gdk_frame_timings_get_predicted_presentation_time (timings);
|
||||
gint64 refresh_interval = gdk_frame_timings_get_refresh_interval (timings);
|
||||
gint64 previous_frame_time = 0;
|
||||
gboolean slept_before = gdk_frame_timings_get_slept_before (timings);
|
||||
@ -177,6 +178,8 @@ _gdk_frame_history_debug_print (GdkFrameHistory *history,
|
||||
g_print (" frame_end=%-4.1f", (frame_end_time - frame_time) / 1000.);
|
||||
if (presentation_time != 0)
|
||||
g_print (" present=%-4.1f", (presentation_time - frame_time) / 1000.);
|
||||
if (predicted_presentation_time != 0)
|
||||
g_print (" predicted=%-4.1f", (predicted_presentation_time - frame_time) / 1000.);
|
||||
if (refresh_interval != 0)
|
||||
g_print (" refresh_interval=%-4.1f", refresh_interval / 1000.);
|
||||
g_print ("\n");
|
||||
|
@ -29,6 +29,7 @@ struct _GdkFrameTimings
|
||||
gint64 drawn_time;
|
||||
gint64 presentation_time;
|
||||
gint64 refresh_interval;
|
||||
gint64 predicted_presentation_time;
|
||||
|
||||
#ifdef G_ENABLE_DEBUG
|
||||
gint64 layout_start_time;
|
||||
@ -187,6 +188,23 @@ gdk_frame_timings_set_presentation_time (GdkFrameTimings *timings,
|
||||
timings->presentation_time = presentation_time;
|
||||
}
|
||||
|
||||
gint64
|
||||
gdk_frame_timings_get_predicted_presentation_time (GdkFrameTimings *timings)
|
||||
{
|
||||
g_return_val_if_fail (timings != NULL, 0);
|
||||
|
||||
return timings->predicted_presentation_time;
|
||||
}
|
||||
|
||||
void
|
||||
gdk_frame_timings_set_predicted_presentation_time (GdkFrameTimings *timings,
|
||||
gint64 predicted_presentation_time)
|
||||
{
|
||||
g_return_if_fail (timings != NULL);
|
||||
|
||||
timings->predicted_presentation_time = predicted_presentation_time;
|
||||
}
|
||||
|
||||
gint64
|
||||
gdk_frame_timings_get_refresh_interval (GdkFrameTimings *timings)
|
||||
{
|
||||
|
@ -62,6 +62,10 @@ gint64 gdk_frame_timings_get_refresh_interval (GdkFrameTimings *timin
|
||||
void gdk_frame_timings_set_refresh_interval (GdkFrameTimings *timings,
|
||||
gint64 refresh_interval);
|
||||
|
||||
gint64 gdk_frame_timings_get_predicted_presentation_time (GdkFrameTimings *timings);
|
||||
void gdk_frame_timings_set_predicted_presentation_time (GdkFrameTimings *timings,
|
||||
gint64 predicted_presentation_time);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GDK_FRAME_TIMINGS_H__ */
|
||||
|
@ -1108,18 +1108,27 @@ _gdk_wm_protocols_filter (GdkXEvent *xev,
|
||||
guint32 d3 = xevent->xclient.data.l[3];
|
||||
|
||||
guint64 serial = ((guint64)d0 << 32) | d1;
|
||||
gint64 frame_drawn_time = ((guint64)d2 << 32) | d3;
|
||||
gint64 refresh_interval, presentation_time;
|
||||
|
||||
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);
|
||||
gdk_frame_timings_set_drawn_time (timings, frame_drawn_time);
|
||||
|
||||
if (window_impl->toplevel->frame_pending)
|
||||
{
|
||||
window_impl->toplevel->frame_pending = FALSE;
|
||||
gdk_frame_clock_thaw (clock);
|
||||
}
|
||||
|
||||
gdk_frame_clock_get_refresh_info (clock,
|
||||
frame_drawn_time,
|
||||
&refresh_interval,
|
||||
&presentation_time);
|
||||
if (presentation_time != 0)
|
||||
window_impl->toplevel->throttled_presentation_time = presentation_time + refresh_interval;
|
||||
}
|
||||
|
||||
return GDK_FILTER_REMOVE;
|
||||
|
@ -277,6 +277,58 @@ unhook_surface_changed (GdkWindow *window)
|
||||
NULL, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
gdk_x11_window_predict_presentation_time (GdkWindow *window)
|
||||
{
|
||||
GdkWindowImplX11 *impl = GDK_WINDOW_IMPL_X11 (window->impl);
|
||||
GdkFrameClock *clock;
|
||||
GdkFrameTimings *timings;
|
||||
gint64 frame_time;
|
||||
gint64 presentation_time;
|
||||
gint64 refresh_interval;
|
||||
gboolean slept_before;
|
||||
|
||||
if (!WINDOW_IS_TOPLEVEL (window))
|
||||
return;
|
||||
|
||||
clock = gdk_window_get_frame_clock (window);
|
||||
|
||||
timings = gdk_frame_clock_get_current_frame_timings (clock);
|
||||
frame_time = gdk_frame_timings_get_frame_time (timings);
|
||||
slept_before = gdk_frame_timings_get_slept_before (timings);
|
||||
|
||||
gdk_frame_clock_get_refresh_info (clock,
|
||||
frame_time,
|
||||
&refresh_interval, &presentation_time);
|
||||
|
||||
if (presentation_time != 0)
|
||||
{
|
||||
if (slept_before)
|
||||
{
|
||||
presentation_time += refresh_interval;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (presentation_time < frame_time + refresh_interval / 2)
|
||||
presentation_time += refresh_interval;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (slept_before)
|
||||
presentation_time = frame_time + refresh_interval + refresh_interval / 2;
|
||||
else
|
||||
presentation_time = frame_time + refresh_interval;
|
||||
}
|
||||
|
||||
if (presentation_time < impl->toplevel->throttled_presentation_time)
|
||||
presentation_time = impl->toplevel->throttled_presentation_time;
|
||||
|
||||
gdk_frame_timings_set_predicted_presentation_time (timings,
|
||||
presentation_time);
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
gdk_x11_window_begin_frame (GdkWindow *window)
|
||||
{
|
||||
@ -299,8 +351,6 @@ static void
|
||||
gdk_x11_window_end_frame (GdkWindow *window)
|
||||
{
|
||||
GdkFrameClock *clock;
|
||||
GdkFrameHistory *history;
|
||||
gint64 frame_counter;
|
||||
GdkFrameTimings *timings;
|
||||
GdkWindowImplX11 *impl;
|
||||
|
||||
@ -314,9 +364,7 @@ gdk_x11_window_end_frame (GdkWindow *window)
|
||||
return;
|
||||
|
||||
clock = gdk_window_get_frame_clock (window);
|
||||
history = gdk_frame_clock_get_history (clock);
|
||||
frame_counter = gdk_frame_history_get_frame_counter (history);
|
||||
timings = gdk_frame_history_get_timings (history, frame_counter);
|
||||
timings = gdk_frame_clock_get_current_frame_timings (clock);
|
||||
|
||||
impl->toplevel->in_frame = FALSE;
|
||||
|
||||
@ -880,6 +928,7 @@ static void
|
||||
on_frame_clock_before_paint (GdkFrameClock *clock,
|
||||
GdkWindow *window)
|
||||
{
|
||||
gdk_x11_window_predict_presentation_time (window);
|
||||
gdk_x11_window_begin_frame (window);
|
||||
}
|
||||
|
||||
|
@ -156,6 +156,10 @@ struct _GdkToplevelX11
|
||||
* ConfigureNotify
|
||||
*/
|
||||
gint64 current_counter_value;
|
||||
|
||||
/* After a _NET_WM_FRAME_DRAWN message, this is the soonest that we think
|
||||
* frame after will be presented */
|
||||
gint64 throttled_presentation_time;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user