mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-11-10 02:40:11 +00:00
video-timer: add a test case for display at a constant frame rate
Add a test case that simulates the timing operaton that goes on when showing a constant frame rate stream like a video - each frame is shown at the VBlank interval that is closest to when it would ideally be timed. https://bugzilla.gnome.org/show_bug.cgi?id=685460
This commit is contained in:
parent
fb44ea8a85
commit
e77a96a0ea
@ -30,6 +30,7 @@ noinst_PROGRAMS = $(TEST_PROGS) \
|
||||
simple \
|
||||
flicker \
|
||||
print-editor \
|
||||
video-timer \
|
||||
testaccel \
|
||||
testadjustsize \
|
||||
testappchooser \
|
||||
@ -156,6 +157,7 @@ flicker_DEPENDENCIES = $(TEST_DEPS)
|
||||
motion_compression_DEPENDENCIES = $(TEST_DEPS)
|
||||
simple_DEPENDENCIES = $(TEST_DEPS)
|
||||
print_editor_DEPENDENCIES = $(TEST_DEPS)
|
||||
video_timer_DEPENDENCIES = $(TEST_DEPS)
|
||||
testheightforwidth_DEPENDENCIES = $(TEST_DEPS)
|
||||
testicontheme_DEPENDENCIES = $(TEST_DEPS)
|
||||
testiconview_DEPENDENCIES = $(TEST_DEPS)
|
||||
@ -258,6 +260,11 @@ animated_resizing_SOURCES = \
|
||||
variable.c \
|
||||
variable.h
|
||||
|
||||
video_timer_SOURCES = \
|
||||
video-timer.c \
|
||||
variable.c \
|
||||
variable.h
|
||||
|
||||
testboxcss_SOURCES = \
|
||||
testboxcss.c \
|
||||
prop-editor.c
|
||||
|
328
tests/video-timer.c
Normal file
328
tests/video-timer.c
Normal file
@ -0,0 +1,328 @@
|
||||
#include <math.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "variable.h"
|
||||
|
||||
typedef struct {
|
||||
gdouble angle;
|
||||
gint64 stream_time;
|
||||
gint64 clock_time;
|
||||
gint64 frame_counter;
|
||||
} FrameData;
|
||||
|
||||
static FrameData *displayed_frame;
|
||||
static GtkWidget *window;
|
||||
static GList *past_frames;
|
||||
static Variable latency_error = VARIABLE_INIT;
|
||||
static int dropped_frames = 0;
|
||||
static int n_frames = 0;
|
||||
|
||||
static int fps = 24;
|
||||
|
||||
/* Thread-safe frame queue */
|
||||
|
||||
#define MAX_QUEUE_LENGTH 5
|
||||
|
||||
static GQueue *frame_queue;
|
||||
static GMutex frame_mutex;
|
||||
static GCond frame_cond;
|
||||
|
||||
static void
|
||||
queue_frame (FrameData *frame_data)
|
||||
{
|
||||
g_mutex_lock (&frame_mutex);
|
||||
|
||||
while (frame_queue->length == MAX_QUEUE_LENGTH)
|
||||
g_cond_wait (&frame_cond, &frame_mutex);
|
||||
|
||||
g_queue_push_tail (frame_queue, frame_data);
|
||||
|
||||
g_mutex_unlock (&frame_mutex);
|
||||
}
|
||||
|
||||
static FrameData *
|
||||
unqueue_frame (void)
|
||||
{
|
||||
FrameData *frame_data;
|
||||
|
||||
g_mutex_lock (&frame_mutex);
|
||||
|
||||
if (frame_queue->length > 0)
|
||||
{
|
||||
frame_data = g_queue_pop_head (frame_queue);
|
||||
g_cond_signal (&frame_cond);
|
||||
}
|
||||
else
|
||||
{
|
||||
frame_data = NULL;
|
||||
}
|
||||
|
||||
g_mutex_unlock (&frame_mutex);
|
||||
|
||||
return frame_data;
|
||||
}
|
||||
|
||||
static FrameData *
|
||||
peek_pending_frame (void)
|
||||
{
|
||||
FrameData *frame_data;
|
||||
|
||||
g_mutex_lock (&frame_mutex);
|
||||
|
||||
if (frame_queue->head)
|
||||
frame_data = frame_queue->head->data;
|
||||
else
|
||||
frame_data = NULL;
|
||||
|
||||
g_mutex_unlock (&frame_mutex);
|
||||
|
||||
return frame_data;
|
||||
}
|
||||
|
||||
static FrameData *
|
||||
peek_next_frame (void)
|
||||
{
|
||||
FrameData *frame_data;
|
||||
|
||||
g_mutex_lock (&frame_mutex);
|
||||
|
||||
if (frame_queue->head && frame_queue->head->next)
|
||||
frame_data = frame_queue->head->next->data;
|
||||
else
|
||||
frame_data = NULL;
|
||||
|
||||
g_mutex_unlock (&frame_mutex);
|
||||
|
||||
return frame_data;
|
||||
}
|
||||
|
||||
/* Frame producer thread */
|
||||
|
||||
static gpointer
|
||||
create_frames_thread (gpointer data)
|
||||
{
|
||||
int frame_count = 0;
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
FrameData *frame_data = g_slice_new0 (FrameData);
|
||||
frame_data->angle = 2 * M_PI * (frame_count % fps) / (double)fps;
|
||||
frame_data->stream_time = (G_GINT64_CONSTANT (1000000) * frame_count) / fps;
|
||||
|
||||
queue_frame (frame_data);
|
||||
frame_count++;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Clock management */
|
||||
|
||||
#define PRE_BUFFER_TIME 500000
|
||||
|
||||
static gint64 start_time;
|
||||
|
||||
static gint64
|
||||
stream_time_to_clock_time (gint64 stream_time)
|
||||
{
|
||||
return start_time + stream_time + PRE_BUFFER_TIME;
|
||||
}
|
||||
|
||||
/* Drawing */
|
||||
|
||||
static void
|
||||
on_window_draw (GtkWidget *widget,
|
||||
cairo_t *cr)
|
||||
{
|
||||
GdkRectangle allocation;
|
||||
double cx, cy, r;
|
||||
|
||||
cairo_set_source_rgb (cr, 1., 1., 1.);
|
||||
cairo_paint (cr);
|
||||
|
||||
cairo_set_source_rgb (cr, 0., 0., 0.);
|
||||
gtk_widget_get_allocation (widget, &allocation);
|
||||
|
||||
cx = allocation.width / 2.;
|
||||
cy = allocation.height / 2.;
|
||||
r = MIN (allocation.width, allocation.height) / 2.;
|
||||
|
||||
cairo_arc (cr, cx, cy, r,
|
||||
0, 2 * M_PI);
|
||||
cairo_stroke (cr);
|
||||
if (displayed_frame)
|
||||
{
|
||||
cairo_move_to (cr, cx, cy);
|
||||
cairo_line_to (cr,
|
||||
cx + r * cos(displayed_frame->angle - M_PI / 2),
|
||||
cy + r * sin(displayed_frame->angle - M_PI / 2));
|
||||
cairo_stroke (cr);
|
||||
|
||||
if (displayed_frame->frame_counter == 0)
|
||||
{
|
||||
GdkFrameClock *frame_clock = gtk_widget_get_frame_clock (window);
|
||||
GdkFrameHistory *history = gdk_frame_clock_get_history (frame_clock);
|
||||
|
||||
displayed_frame->frame_counter = gdk_frame_history_get_frame_counter (history);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
collect_old_frames (void)
|
||||
{
|
||||
GdkFrameClock *frame_clock = gtk_widget_get_frame_clock (window);
|
||||
GdkFrameHistory *history = gdk_frame_clock_get_history (frame_clock);
|
||||
GList *l, *l_next;
|
||||
|
||||
for (l = past_frames; l; l = l_next)
|
||||
{
|
||||
FrameData *frame_data = l->data;
|
||||
gboolean remove = FALSE;
|
||||
l_next = l->next;
|
||||
|
||||
GdkFrameTimings *timings = gdk_frame_history_get_timings (history,
|
||||
frame_data->frame_counter);
|
||||
if (timings == NULL)
|
||||
{
|
||||
remove = TRUE;
|
||||
}
|
||||
else if (gdk_frame_timings_get_complete (timings))
|
||||
{
|
||||
gint64 presentation_time = gdk_frame_timings_get_presentation_time (timings);
|
||||
if (presentation_time)
|
||||
variable_add (&latency_error,
|
||||
presentation_time - frame_data->clock_time);
|
||||
|
||||
remove = TRUE;
|
||||
}
|
||||
|
||||
if (remove)
|
||||
{
|
||||
past_frames = g_list_delete_link (past_frames, l);
|
||||
g_slice_free (FrameData, frame_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
print_statistics (void)
|
||||
{
|
||||
gint64 now = g_get_monotonic_time ();
|
||||
static gint64 last_print_time = 0;
|
||||
|
||||
if (last_print_time == 0)
|
||||
last_print_time = now;
|
||||
else if (now -last_print_time > 5000000)
|
||||
{
|
||||
g_print ("dropped_frames: %d/%d\n",
|
||||
dropped_frames, n_frames);
|
||||
g_print ("collected_frames: %g/%d\n",
|
||||
latency_error.weight, n_frames);
|
||||
g_print ("latency_error: %g +/- %g\n",
|
||||
variable_mean (&latency_error),
|
||||
variable_standard_deviation (&latency_error));
|
||||
variable_reset (&latency_error);
|
||||
dropped_frames = 0;
|
||||
n_frames = 0;
|
||||
last_print_time = now;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_update (GdkFrameClock *frame_clock,
|
||||
gpointer data)
|
||||
{
|
||||
GdkFrameTimings *timings = gdk_frame_clock_get_current_frame_timings (frame_clock);
|
||||
gint64 frame_time = gdk_frame_timings_get_frame_time (timings);
|
||||
gint64 predicted_presentation_time = gdk_frame_timings_get_predicted_presentation_time (timings);
|
||||
gint64 refresh_interval;
|
||||
FrameData *pending_frame;
|
||||
|
||||
if (start_time == 0)
|
||||
start_time = frame_time;
|
||||
|
||||
gdk_frame_clock_get_refresh_info (frame_clock, frame_time,
|
||||
&refresh_interval, NULL);
|
||||
|
||||
pending_frame = peek_pending_frame ();
|
||||
if (stream_time_to_clock_time (pending_frame->stream_time)
|
||||
< predicted_presentation_time + refresh_interval / 2)
|
||||
{
|
||||
while (TRUE)
|
||||
{
|
||||
FrameData *next_frame = peek_next_frame ();
|
||||
if (next_frame &&
|
||||
stream_time_to_clock_time (next_frame->stream_time)
|
||||
< predicted_presentation_time + refresh_interval / 2)
|
||||
{
|
||||
g_slice_free (FrameData, unqueue_frame ());
|
||||
n_frames++;
|
||||
dropped_frames++;
|
||||
pending_frame = next_frame;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if (displayed_frame)
|
||||
past_frames = g_list_prepend (past_frames, displayed_frame);
|
||||
|
||||
n_frames++;
|
||||
displayed_frame = unqueue_frame ();
|
||||
displayed_frame->clock_time = stream_time_to_clock_time (displayed_frame->stream_time);
|
||||
displayed_frame->frame_counter = gdk_frame_timings_get_frame_counter (timings);
|
||||
|
||||
collect_old_frames ();
|
||||
print_statistics ();
|
||||
|
||||
gtk_widget_queue_draw (window);
|
||||
}
|
||||
|
||||
gdk_frame_clock_request_phase (frame_clock, GDK_FRAME_CLOCK_PHASE_UPDATE);
|
||||
}
|
||||
|
||||
static GOptionEntry options[] = {
|
||||
{ "fps", 'f', 0, G_OPTION_ARG_INT, &fps, "Frame rate", "FPS" },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GdkFrameClock *frame_clock;
|
||||
|
||||
if (!gtk_init_with_args (&argc, &argv, "",
|
||||
options, NULL, &error))
|
||||
{
|
||||
g_printerr ("Option parsing failed: %s\n", error->message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||
gtk_widget_set_app_paintable (window, TRUE);
|
||||
gtk_window_set_default_size (GTK_WINDOW (window), 300, 300);
|
||||
|
||||
g_signal_connect (window, "draw",
|
||||
G_CALLBACK (on_window_draw), NULL);
|
||||
g_signal_connect (window, "destroy",
|
||||
G_CALLBACK (gtk_main_quit), NULL);
|
||||
|
||||
gtk_widget_show (window);
|
||||
|
||||
frame_queue = g_queue_new ();
|
||||
g_mutex_init (&frame_mutex);
|
||||
g_cond_init (&frame_cond);
|
||||
|
||||
g_thread_new ("Create Frames", create_frames_thread, NULL);
|
||||
|
||||
frame_clock = gtk_widget_get_frame_clock (window);
|
||||
g_signal_connect (frame_clock, "update",
|
||||
G_CALLBACK (on_update), NULL);
|
||||
gdk_frame_clock_request_phase (frame_clock, GDK_FRAME_CLOCK_PHASE_UPDATE);
|
||||
|
||||
gtk_main ();
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user