diff --git a/clutter/clutter/clutter-debug.h b/clutter/clutter/clutter-debug.h index 76b8505cb03894dfb29504fc1ecb88149e4b0c7c..677bed107976e425f9f8446e1f84a01562879fd8 100644 --- a/clutter/clutter/clutter-debug.h +++ b/clutter/clutter/clutter-debug.h @@ -41,6 +41,7 @@ G_BEGIN_DECLS extern guint clutter_debug_flags; extern guint clutter_pick_debug_flags; extern guint clutter_paint_debug_flags; +extern int clutter_max_render_time_constant_us; void _clutter_debug_messagev (const char *format, va_list var_args) G_GNUC_PRINTF (1, 0); diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c index 05e79e6626f3cd579f66f60eb08171a67df205ba..9b3b10ebac14779b93d336f022e278e8a074c8d4 100644 --- a/clutter/clutter/clutter-frame-clock.c +++ b/clutter/clutter/clutter-frame-clock.c @@ -19,6 +19,7 @@ #include "clutter/clutter-frame-clock.h" +#include "clutter/clutter-debug.h" #include "clutter/clutter-main.h" #include "clutter/clutter-private.h" #include "clutter/clutter-timeline-private.h" @@ -33,8 +34,21 @@ enum static guint signals[N_SIGNALS]; -/* Wait 2ms after vblank before starting to draw next frame */ -#define SYNC_DELAY_US ms2us (2) +/* An estimate queue holds several int64_t values. Adding a new value to the + * queue overwrites the oldest value. + */ +#define ESTIMATE_QUEUE_LENGTH 16 + +typedef struct _EstimateQueue +{ + int64_t values[ESTIMATE_QUEUE_LENGTH]; + int next_index; +} EstimateQueue; + +/* When heuristic render time is off, + * wait 2ms after vblank before starting to draw next frame. + */ +#define SYNC_DELAY_FALLBACK_US ms2us (2) typedef struct _ClutterFrameListener { @@ -76,6 +90,22 @@ struct _ClutterFrameClock gboolean is_next_presentation_time_valid; int64_t next_presentation_time_us; + /* Buffer must be submitted to KMS and GPU rendering must be finished + * this amount of time before the next presentation time. + */ + int64_t vblank_duration_us; + /* Last KMS buffer submission time. */ + int64_t last_flip_time_us; + + /* Last few durations between dispatch start and buffer swap. */ + EstimateQueue dispatch_to_swap_us; + /* Last few durations between buffer swap and GPU rendering finish. */ + EstimateQueue swap_to_rendering_done_us; + /* Last few durations between buffer swap and KMS submission. */ + EstimateQueue swap_to_flip_us; + /* If we got new measurements last frame. */ + gboolean got_measurements_last_frame; + gboolean pending_reschedule; gboolean pending_reschedule_now; @@ -87,6 +117,14 @@ struct _ClutterFrameClock G_DEFINE_TYPE (ClutterFrameClock, clutter_frame_clock, G_TYPE_OBJECT) +static void +estimate_queue_add_value (EstimateQueue *queue, + int64_t value) +{ + queue->values[queue->next_index] = value; + queue->next_index = (queue->next_index + 1) % ESTIMATE_QUEUE_LENGTH; +} + float clutter_frame_clock_get_refresh_rate (ClutterFrameClock *frame_clock) { @@ -184,6 +222,38 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, { frame_clock->last_presentation_time_us = frame_info->presentation_time; + frame_clock->got_measurements_last_frame = FALSE; + + if (frame_info->cpu_time_before_buffer_swap_us != 0 && + frame_info->gpu_rendering_duration_ns != 0) + { + int64_t dispatch_to_swap_us, swap_to_rendering_done_us, swap_to_flip_us; + + dispatch_to_swap_us = + frame_info->cpu_time_before_buffer_swap_us - + frame_clock->last_dispatch_time_us; + swap_to_rendering_done_us = + frame_info->gpu_rendering_duration_ns / 1000; + swap_to_flip_us = + frame_clock->last_flip_time_us - + frame_info->cpu_time_before_buffer_swap_us; + + CLUTTER_NOTE (FRAME_TIMINGS, + "dispatch2swap %ld µs, swap2render %ld µs, swap2flip %ld µs", + dispatch_to_swap_us, + swap_to_rendering_done_us, + swap_to_flip_us); + + estimate_queue_add_value (&frame_clock->dispatch_to_swap_us, + dispatch_to_swap_us); + estimate_queue_add_value (&frame_clock->swap_to_rendering_done_us, + swap_to_rendering_done_us); + estimate_queue_add_value (&frame_clock->swap_to_flip_us, + swap_to_flip_us); + + frame_clock->got_measurements_last_frame = TRUE; + } + if (frame_info->refresh_rate > 1) frame_clock->refresh_rate = frame_info->refresh_rate; @@ -220,6 +290,58 @@ clutter_frame_clock_notify_ready (ClutterFrameClock *frame_clock) } } +static int64_t +clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock) +{ + int64_t refresh_interval_us; + int64_t max_dispatch_to_swap_us = 0; + int64_t max_swap_to_rendering_done_us = 0; + int64_t max_swap_to_flip_us = 0; + int64_t max_render_time_us; + int i; + + refresh_interval_us = + (int64_t) (0.5 + G_USEC_PER_SEC / frame_clock->refresh_rate); + + if (!frame_clock->got_measurements_last_frame || + G_UNLIKELY (clutter_paint_debug_flags & + CLUTTER_DEBUG_DISABLE_DYNAMIC_MAX_RENDER_TIME)) + return refresh_interval_us - SYNC_DELAY_FALLBACK_US; + + for (i = 0; i < ESTIMATE_QUEUE_LENGTH; ++i) + { + max_dispatch_to_swap_us = + MAX (max_dispatch_to_swap_us, + frame_clock->dispatch_to_swap_us.values[i]); + max_swap_to_rendering_done_us = + MAX (max_swap_to_rendering_done_us, + frame_clock->swap_to_rendering_done_us.values[i]); + max_swap_to_flip_us = + MAX (max_swap_to_flip_us, + frame_clock->swap_to_flip_us.values[i]); + } + + /* Max render time shows how early the frame clock needs to be dispatched + * to make it to the predicted next presentation time. It is composed of: + * - An estimate of duration from dispatch start to buffer swap. + * - Maximum between estimates of duration from buffer swap to GPU rendering + * finish and duration from buffer swap to buffer submission to KMS. This + * is because both of these things need to happen before the vblank, and + * they are done in parallel. + * - Duration of the vblank. + * - A constant to account for variations in the above estimates. + */ + max_render_time_us = + max_dispatch_to_swap_us + + MAX (max_swap_to_rendering_done_us, max_swap_to_flip_us) + + frame_clock->vblank_duration_us + + clutter_max_render_time_constant_us; + + max_render_time_us = CLAMP (max_render_time_us, 0, refresh_interval_us); + + return max_render_time_us; +} + static void calculate_next_update_time_us (ClutterFrameClock *frame_clock, int64_t *out_next_update_time_us, @@ -253,7 +375,8 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, } min_render_time_allowed_us = refresh_interval_us / 2; - max_render_time_allowed_us = refresh_interval_us - SYNC_DELAY_US; + max_render_time_allowed_us = + clutter_frame_clock_compute_max_render_time_us (frame_clock); if (min_render_time_allowed_us > max_render_time_allowed_us) min_render_time_allowed_us = max_render_time_allowed_us; @@ -548,6 +671,58 @@ frame_clock_source_dispatch (GSource *source, return G_SOURCE_CONTINUE; } +void +clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock, + int64_t flip_time_us) +{ + frame_clock->last_flip_time_us = flip_time_us; +} + +GString * +clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock) +{ + int64_t max_dispatch_to_swap_us = 0; + int64_t max_swap_to_rendering_done_us = 0; + int64_t max_swap_to_flip_us = 0; + int i; + GString *string; + + string = g_string_new (NULL); + g_string_append_printf (string, "Max render time: %ld µs", + clutter_frame_clock_compute_max_render_time_us (frame_clock)); + + if (frame_clock->got_measurements_last_frame) + g_string_append_printf (string, " ="); + else + g_string_append_printf (string, " (no measurements last frame)"); + + for (i = 0; i < ESTIMATE_QUEUE_LENGTH; ++i) + { + max_dispatch_to_swap_us = + MAX (max_dispatch_to_swap_us, + frame_clock->dispatch_to_swap_us.values[i]); + max_swap_to_rendering_done_us = + MAX (max_swap_to_rendering_done_us, + frame_clock->swap_to_rendering_done_us.values[i]); + max_swap_to_flip_us = + MAX (max_swap_to_flip_us, + frame_clock->swap_to_flip_us.values[i]); + } + + g_string_append_printf (string, "\nVblank duration: %ld µs +", + frame_clock->vblank_duration_us); + g_string_append_printf (string, "\nDispatch to swap: %ld µs +", + max_dispatch_to_swap_us); + g_string_append_printf (string, "\nmax(Swap to rendering done: %ld µs,", + max_swap_to_rendering_done_us); + g_string_append_printf (string, "\nSwap to flip: %ld µs) +", + max_swap_to_flip_us); + g_string_append_printf (string, "\nConstant: %d µs", + clutter_max_render_time_constant_us); + + return string; +} + static GSourceFuncs frame_clock_source_funcs = { NULL, NULL, @@ -577,6 +752,7 @@ init_frame_clock_source (ClutterFrameClock *frame_clock) ClutterFrameClock * clutter_frame_clock_new (float refresh_rate, + int64_t vblank_duration_us, const ClutterFrameListenerIface *iface, gpointer user_data) { @@ -592,6 +768,7 @@ clutter_frame_clock_new (float refresh_rate, init_frame_clock_source (frame_clock); frame_clock->refresh_rate = refresh_rate; + frame_clock->vblank_duration_us = vblank_duration_us; return frame_clock; } diff --git a/clutter/clutter/clutter-frame-clock.h b/clutter/clutter/clutter-frame-clock.h index 9f91b6bfd3c942f6cf7a498f156f19023c3074b5..e71b5498735d11ab1fa09fe18d2b9f84db63d8d7 100644 --- a/clutter/clutter/clutter-frame-clock.h +++ b/clutter/clutter/clutter-frame-clock.h @@ -56,6 +56,7 @@ typedef struct _ClutterFrameListenerIface CLUTTER_EXPORT ClutterFrameClock * clutter_frame_clock_new (float refresh_rate, + int64_t vblank_duration_us, const ClutterFrameListenerIface *iface, gpointer user_data); @@ -90,4 +91,9 @@ void clutter_frame_clock_remove_timeline (ClutterFrameClock *frame_clock, CLUTTER_EXPORT float clutter_frame_clock_get_refresh_rate (ClutterFrameClock *frame_clock); +void clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock, + int64_t flip_time_us); + +GString * clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock); + #endif /* CLUTTER_FRAME_CLOCK_H */ diff --git a/clutter/clutter/clutter-main.c b/clutter/clutter/clutter-main.c index d79b690a3d6db8e707458bc668e9b171deabb24c..a800138eabfdf3dd60beb302ead53c76cf65ec75 100644 --- a/clutter/clutter/clutter-main.c +++ b/clutter/clutter/clutter-main.c @@ -95,6 +95,11 @@ guint clutter_debug_flags = 0; guint clutter_paint_debug_flags = 0; guint clutter_pick_debug_flags = 0; +/* A constant added to heuristic max render time to account for variations + * in the estimates. + */ +int clutter_max_render_time_constant_us = 2000; + #ifdef CLUTTER_ENABLE_DEBUG static const GDebugKey clutter_debug_keys[] = { { "misc", CLUTTER_DEBUG_MISC }, @@ -112,6 +117,7 @@ static const GDebugKey clutter_debug_keys[] = { { "layout", CLUTTER_DEBUG_LAYOUT }, { "clipping", CLUTTER_DEBUG_CLIPPING }, { "oob-transforms", CLUTTER_DEBUG_OOB_TRANSFORMS }, + { "frame-timings", CLUTTER_DEBUG_FRAME_TIMINGS }, }; #endif /* CLUTTER_ENABLE_DEBUG */ @@ -129,6 +135,8 @@ static const GDebugKey clutter_paint_debug_keys[] = { { "continuous-redraw", CLUTTER_DEBUG_CONTINUOUS_REDRAW }, { "paint-deform-tiles", CLUTTER_DEBUG_PAINT_DEFORM_TILES }, { "damage-region", CLUTTER_DEBUG_PAINT_DAMAGE_REGION }, + { "disable-dynamic-max-render-time", CLUTTER_DEBUG_DISABLE_DYNAMIC_MAX_RENDER_TIME }, + { "max-render-time", CLUTTER_DEBUG_PAINT_MAX_RENDER_TIME }, }; gboolean @@ -2273,6 +2281,12 @@ clutter_remove_debug_flags (ClutterDebugFlag debug_flags, clutter_pick_debug_flags &= ~pick_flags; } +void +clutter_debug_set_max_render_time_constant (int max_render_time_constant_us) +{ + clutter_max_render_time_constant_us = max_render_time_constant_us; +} + void _clutter_set_sync_to_vblank (gboolean sync_to_vblank) { diff --git a/clutter/clutter/clutter-main.h b/clutter/clutter/clutter-main.h index 10e2aca997930062047449eec28e177daaabdab6..846a9d68d28471e2b0923a8fef059acc4568fe31 100644 --- a/clutter/clutter/clutter-main.h +++ b/clutter/clutter/clutter-main.h @@ -53,6 +53,7 @@ typedef enum CLUTTER_DEBUG_EVENTLOOP = 1 << 14, CLUTTER_DEBUG_CLIPPING = 1 << 15, CLUTTER_DEBUG_OOB_TRANSFORMS = 1 << 16, + CLUTTER_DEBUG_FRAME_TIMINGS = 1 << 17, } ClutterDebugFlag; typedef enum @@ -62,15 +63,17 @@ typedef enum typedef enum { - CLUTTER_DEBUG_DISABLE_SWAP_EVENTS = 1 << 0, - CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS = 1 << 1, - CLUTTER_DEBUG_REDRAWS = 1 << 2, - CLUTTER_DEBUG_PAINT_VOLUMES = 1 << 3, - CLUTTER_DEBUG_DISABLE_CULLING = 1 << 4, - CLUTTER_DEBUG_DISABLE_OFFSCREEN_REDIRECT = 1 << 5, - CLUTTER_DEBUG_CONTINUOUS_REDRAW = 1 << 6, - CLUTTER_DEBUG_PAINT_DEFORM_TILES = 1 << 7, - CLUTTER_DEBUG_PAINT_DAMAGE_REGION = 1 << 8, + CLUTTER_DEBUG_DISABLE_SWAP_EVENTS = 1 << 0, + CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS = 1 << 1, + CLUTTER_DEBUG_REDRAWS = 1 << 2, + CLUTTER_DEBUG_PAINT_VOLUMES = 1 << 3, + CLUTTER_DEBUG_DISABLE_CULLING = 1 << 4, + CLUTTER_DEBUG_DISABLE_OFFSCREEN_REDIRECT = 1 << 5, + CLUTTER_DEBUG_CONTINUOUS_REDRAW = 1 << 6, + CLUTTER_DEBUG_PAINT_DEFORM_TILES = 1 << 7, + CLUTTER_DEBUG_PAINT_DAMAGE_REGION = 1 << 8, + CLUTTER_DEBUG_DISABLE_DYNAMIC_MAX_RENDER_TIME = 1 << 9, + CLUTTER_DEBUG_PAINT_MAX_RENDER_TIME = 1 << 10, } ClutterDrawDebugFlag; /** @@ -196,6 +199,9 @@ void clutter_remove_debug_flags (ClutterDebugFla ClutterDrawDebugFlag draw_flags, ClutterPickDebugFlag pick_flags); +CLUTTER_EXPORT +void clutter_debug_set_max_render_time_constant (int max_render_time_constant_us); + G_END_DECLS #endif /* _CLUTTER_MAIN_H__ */ diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c index b44bf88854989c6e182b89f7c46fe4359d4aa525..2b2cadd68437994248765bb1a377cc15f4df5632 100644 --- a/clutter/clutter/clutter-stage-view.c +++ b/clutter/clutter/clutter-stage-view.c @@ -43,6 +43,7 @@ enum PROP_USE_SHADOWFB, PROP_SCALE, PROP_REFRESH_RATE, + PROP_VBLANK_DURATION_US, PROP_LAST }; @@ -79,6 +80,7 @@ typedef struct _ClutterStageViewPrivate cairo_region_t *redraw_clip; float refresh_rate; + int64_t vblank_duration_us; ClutterFrameClock *frame_clock; struct { @@ -1187,6 +1189,9 @@ handle_frame_clock_frame (ClutterFrameClock *frame_clock, _clutter_stage_window_redraw_view (stage_window, view, &frame); + clutter_frame_clock_record_flip_time (frame_clock, + g_get_monotonic_time ()); + clutter_stage_emit_after_paint (stage, view); if (_clutter_context_get_show_fps ()) @@ -1295,6 +1300,9 @@ clutter_stage_view_get_property (GObject *object, case PROP_REFRESH_RATE: g_value_set_float (value, priv->refresh_rate); break; + case PROP_VBLANK_DURATION_US: + g_value_set_int64 (value, priv->vblank_duration_us); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -1338,6 +1346,9 @@ clutter_stage_view_set_property (GObject *object, case PROP_REFRESH_RATE: priv->refresh_rate = g_value_get_float (value); break; + case PROP_VBLANK_DURATION_US: + priv->vblank_duration_us = g_value_get_int64 (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -1354,6 +1365,7 @@ clutter_stage_view_constructed (GObject *object) init_shadowfb (view); priv->frame_clock = clutter_frame_clock_new (priv->refresh_rate, + priv->vblank_duration_us, &frame_clock_listener_iface, view); @@ -1497,5 +1509,14 @@ clutter_stage_view_class_init (ClutterStageViewClass *klass) G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + obj_props[PROP_VBLANK_DURATION_US] = + g_param_spec_int64 ("vblank-duration-us", + "Vblank duration (µs)", + "The vblank duration", + 0, G_MAXINT64, 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, PROP_LAST, obj_props); } diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c index 8627c6a17f9ac3e3cad06d0e025596fd258eae81..fc170107f2891bd0f891f14b4216ca946a5a4b8a 100644 --- a/clutter/clutter/clutter-stage.c +++ b/clutter/clutter/clutter-stage.c @@ -1312,6 +1312,51 @@ clutter_stage_real_paint_view (ClutterStage *stage, clutter_stage_do_paint_view (stage, view, redraw_clip); } +static void +clutter_stage_paint (ClutterActor *actor, + ClutterPaintContext *paint_context) +{ + ClutterStageView *view; + + CLUTTER_ACTOR_CLASS (clutter_stage_parent_class)->paint (actor, paint_context); + + view = clutter_paint_context_get_stage_view (paint_context); + if (view && + G_UNLIKELY (clutter_paint_debug_flags & CLUTTER_DEBUG_PAINT_MAX_RENDER_TIME)) + { + cairo_rectangle_int_t view_layout; + ClutterFrameClock *frame_clock; + g_autoptr (GString) string = NULL; + PangoLayout *layout; + PangoRectangle logical; + ClutterColor color; + g_autoptr (ClutterPaintNode) node = NULL; + ClutterActorBox box; + + clutter_stage_view_get_layout (view, &view_layout); + frame_clock = clutter_stage_view_get_frame_clock (view); + + string = clutter_frame_clock_get_max_render_time_debug_info (frame_clock); + + layout = clutter_actor_create_pango_layout (actor, string->str); + pango_layout_set_alignment (layout, PANGO_ALIGN_RIGHT); + pango_layout_get_pixel_extents (layout, NULL, &logical); + + clutter_color_init (&color, 255, 255, 255, 255); + node = clutter_text_node_new (layout, &color); + + box.x1 = view_layout.x; + box.y1 = view_layout.y + 30; + box.x2 = box.x1 + logical.width; + box.y2 = box.y1 + logical.height; + clutter_paint_node_add_rectangle (node, &box); + + clutter_paint_node_paint (node, paint_context); + + g_object_unref (layout); + } +} + static void clutter_stage_class_init (ClutterStageClass *klass) { @@ -1335,6 +1380,7 @@ clutter_stage_class_init (ClutterStageClass *klass) actor_class->hide_all = clutter_stage_hide_all; actor_class->queue_relayout = clutter_stage_real_queue_relayout; actor_class->apply_transform = clutter_stage_real_apply_transform; + actor_class->paint = clutter_stage_paint; klass->paint_view = clutter_stage_real_paint_view; diff --git a/clutter/clutter/clutter-stage.h b/clutter/clutter/clutter-stage.h index 9f25e54726da3716063368a5e0742e2df09241c3..69ab1d6eba967c02bce09d4b1e52946a898106ea 100644 --- a/clutter/clutter/clutter-stage.h +++ b/clutter/clutter/clutter-stage.h @@ -149,6 +149,9 @@ struct _ClutterFrameInfo ClutterFrameInfoFlag flags; unsigned int sequence; + + int64_t gpu_rendering_duration_ns; + int64_t cpu_time_before_buffer_swap_us; }; typedef struct _ClutterCapture diff --git a/clutter/clutter/cogl/clutter-stage-cogl.c b/clutter/clutter/cogl/clutter-stage-cogl.c index 2fe6bd10e0c1e1d7a315616c7b27d8d77d57803a..4f1c305f96329b324edcdecb301ef89818ed9276 100644 --- a/clutter/clutter/cogl/clutter-stage-cogl.c +++ b/clutter/clutter/cogl/clutter-stage-cogl.c @@ -254,6 +254,7 @@ swap_framebuffer (ClutterStageWindow *stage_window, ClutterStageCoglPrivate *priv = _clutter_stage_cogl_get_instance_private (stage_cogl); CoglFramebuffer *framebuffer = clutter_stage_view_get_onscreen (view); + CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); clutter_stage_view_before_swap_buffer (view, swap_region); @@ -276,7 +277,8 @@ swap_framebuffer (ClutterStageWindow *stage_window, damage[i * 4 + 3] = rect.height; } - frame_info = cogl_frame_info_new (priv->global_frame_counter); + frame_info = + cogl_frame_info_new (cogl_context, priv->global_frame_counter); priv->global_frame_counter++; /* push on the screen */ @@ -688,6 +690,7 @@ clutter_stage_cogl_scanout_view (ClutterStageCogl *stage_cogl, ClutterStageCoglPrivate *priv = _clutter_stage_cogl_get_instance_private (stage_cogl); CoglFramebuffer *framebuffer = clutter_stage_view_get_framebuffer (view); + CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); CoglOnscreen *onscreen; CoglFrameInfo *frame_info; @@ -695,7 +698,7 @@ clutter_stage_cogl_scanout_view (ClutterStageCogl *stage_cogl, onscreen = COGL_ONSCREEN (framebuffer); - frame_info = cogl_frame_info_new (priv->global_frame_counter); + frame_info = cogl_frame_info_new (cogl_context, priv->global_frame_counter); if (!cogl_onscreen_direct_scanout (onscreen, scanout, @@ -748,9 +751,10 @@ clutter_stage_cogl_add_onscreen_frame_info (ClutterStageCogl *stage_cogl, ClutterStageCoglPrivate *priv = _clutter_stage_cogl_get_instance_private (stage_cogl); CoglFramebuffer *framebuffer = clutter_stage_view_get_onscreen (view); + CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); CoglFrameInfo *frame_info; - frame_info = cogl_frame_info_new (priv->global_frame_counter); + frame_info = cogl_frame_info_new (cogl_context, priv->global_frame_counter); priv->global_frame_counter++; cogl_onscreen_add_frame_info (COGL_ONSCREEN (framebuffer), frame_info); @@ -845,6 +849,10 @@ frame_cb (CoglOnscreen *onscreen, cogl_frame_info_get_presentation_time_us (frame_info), .flags = flags, .sequence = cogl_frame_info_get_sequence (frame_info), + .gpu_rendering_duration_ns = + cogl_frame_info_get_rendering_duration_ns (frame_info), + .cpu_time_before_buffer_swap_us = + cogl_frame_info_get_time_before_buffer_swap_us (frame_info), }; clutter_stage_view_notify_presented (view, &clutter_frame_info); } diff --git a/cogl/cogl/cogl-context-private.h b/cogl/cogl/cogl-context-private.h index 25004c3933966f28d25b121e4e51651ccc0a1744..5856f76e9839f2a3aeb6cf17375d9121e926d7e8 100644 --- a/cogl/cogl/cogl-context-private.h +++ b/cogl/cogl/cogl-context-private.h @@ -62,6 +62,11 @@ typedef struct GLubyte c[4]; } CoglTextureGLVertex; +struct _CoglTimestampQuery +{ + unsigned int id; +}; + struct _CoglContext { CoglObject _parent; diff --git a/cogl/cogl/cogl-context.c b/cogl/cogl/cogl-context.c index b7bf14f87e8efcbed73f3572e4be4fa27c6aeac9..89019e908dfcfe68bb8097f1e7ba46ddf0464590 100644 --- a/cogl/cogl/cogl-context.c +++ b/cogl/cogl/cogl-context.c @@ -491,3 +491,27 @@ cogl_context_get_named_pipeline (CoglContext *context, { return g_hash_table_lookup (context->named_pipelines, key); } + +void +cogl_context_free_timestamp_query (CoglContext *context, + CoglTimestampQuery *query) +{ + context->driver_vtable->free_timestamp_query (context, query); +} + +int64_t +cogl_context_timestamp_query_get_time_ns (CoglContext *context, + CoglTimestampQuery *query) +{ + return context->driver_vtable->timestamp_query_get_time_ns (context, query); +} + +int64_t +cogl_context_get_gpu_time_ns (CoglContext *context) +{ + g_return_val_if_fail (cogl_has_feature (context, + COGL_FEATURE_ID_GET_GPU_TIME), + 0); + + return context->driver_vtable->get_gpu_time_ns (context); +} diff --git a/cogl/cogl/cogl-context.h b/cogl/cogl/cogl-context.h index 20b9561b94f3006ad8d4603a6db2e52706bd0913..38ac11be8302f368c780ac215bcd8128eae2e8d4 100644 --- a/cogl/cogl/cogl-context.h +++ b/cogl/cogl/cogl-context.h @@ -41,6 +41,7 @@ * dependency issues with the following headers. */ typedef struct _CoglContext CoglContext; +typedef struct _CoglTimestampQuery CoglTimestampQuery; #include #include @@ -208,6 +209,8 @@ typedef enum _CoglFeatureID COGL_FEATURE_ID_BUFFER_AGE, COGL_FEATURE_ID_TEXTURE_EGL_IMAGE_EXTERNAL, COGL_FEATURE_ID_BLIT_FRAMEBUFFER, + COGL_FEATURE_ID_TIMESTAMP_QUERY, + COGL_FEATURE_ID_GET_GPU_TIME, /*< private >*/ _COGL_N_FEATURE_IDS /*< skip >*/ @@ -370,6 +373,26 @@ COGL_EXPORT CoglPipeline * cogl_context_get_named_pipeline (CoglContext *context, CoglPipelineKey *key); +COGL_EXPORT void +cogl_context_free_timestamp_query (CoglContext *context, + CoglTimestampQuery *query); + +COGL_EXPORT int64_t +cogl_context_timestamp_query_get_time_ns (CoglContext *context, + CoglTimestampQuery *query); + +/** + * cogl_context_get_gpu_time_ns: + * @context: a #CoglContext pointer + * + * This function should only be called if the COGL_FEATURE_ID_GET_GPU_TIME + * feature is advertised. + * + * Return value: Current GPU time in nanoseconds + */ +COGL_EXPORT int64_t +cogl_context_get_gpu_time_ns (CoglContext *context); + G_END_DECLS #endif /* __COGL_CONTEXT_H__ */ diff --git a/cogl/cogl/cogl-driver.h b/cogl/cogl/cogl-driver.h index 21519085b252bf2665c59bfcf99fc9016a1a5485..aeabfe0e75ef466a8b8511d95e00ef3b8f9e80f1 100644 --- a/cogl/cogl/cogl-driver.h +++ b/cogl/cogl/cogl-driver.h @@ -231,6 +231,20 @@ struct _CoglDriverVtable (* set_uniform) (CoglContext *ctx, GLint location, const CoglBoxedValue *value); + + CoglTimestampQuery * + (* create_timestamp_query) (CoglContext *context); + + void + (* free_timestamp_query) (CoglContext *context, + CoglTimestampQuery *query); + + int64_t + (* timestamp_query_get_time_ns) (CoglContext *context, + CoglTimestampQuery *query); + + int64_t + (* get_gpu_time_ns) (CoglContext *context); }; #define COGL_DRIVER_ERROR (_cogl_driver_error_quark ()) diff --git a/cogl/cogl/cogl-frame-info-private.h b/cogl/cogl/cogl-frame-info-private.h index 81904f7a425062c1de79e6f59eb3e12cbc0dcfd3..2a59288d18445cd55d57573971b815e2e56cbbd3 100644 --- a/cogl/cogl/cogl-frame-info-private.h +++ b/cogl/cogl/cogl-frame-info-private.h @@ -33,6 +33,7 @@ #include "cogl-frame-info.h" #include "cogl-object-private.h" +#include "cogl-context.h" typedef enum _CoglFrameInfoFlag { @@ -62,6 +63,8 @@ struct _CoglFrameInfo { CoglObject _parent; + CoglContext *context; + int64_t frame_counter; int64_t presentation_time_us; /* CLOCK_MONOTONIC */ float refresh_rate; @@ -71,9 +74,14 @@ struct _CoglFrameInfo CoglFrameInfoFlag flags; unsigned int sequence; + + CoglTimestampQuery *timestamp_query; + int64_t gpu_time_before_buffer_swap_ns; + int64_t cpu_time_before_buffer_swap_us; }; COGL_EXPORT -CoglFrameInfo *cogl_frame_info_new (int64_t global_frame_counter); +CoglFrameInfo *cogl_frame_info_new (CoglContext *context, + int64_t global_frame_counter); #endif /* __COGL_FRAME_INFO_PRIVATE_H */ diff --git a/cogl/cogl/cogl-frame-info.c b/cogl/cogl/cogl-frame-info.c index a31d91689670f938e3ca019895a9354d721d50c9..adaf2bad982ead112bdad2c3e75b06d97d51b484 100644 --- a/cogl/cogl/cogl-frame-info.c +++ b/cogl/cogl/cogl-frame-info.c @@ -32,6 +32,7 @@ #include "cogl-frame-info-private.h" #include "cogl-gtype-private.h" +#include "cogl-context-private.h" static void _cogl_frame_info_free (CoglFrameInfo *info); @@ -39,11 +40,13 @@ COGL_OBJECT_DEFINE (FrameInfo, frame_info); COGL_GTYPE_DEFINE_CLASS (FrameInfo, frame_info); CoglFrameInfo * -cogl_frame_info_new (int64_t global_frame_counter) +cogl_frame_info_new (CoglContext *context, + int64_t global_frame_counter) { CoglFrameInfo *info; info = g_new0 (CoglFrameInfo, 1); + info->context = context; info->global_frame_counter = global_frame_counter; return _cogl_frame_info_object_new (info); @@ -52,6 +55,9 @@ cogl_frame_info_new (int64_t global_frame_counter) static void _cogl_frame_info_free (CoglFrameInfo *info) { + if (info->timestamp_query) + cogl_context_free_timestamp_query (info->context, info->timestamp_query); + g_free (info); } @@ -114,3 +120,25 @@ cogl_frame_info_get_sequence (CoglFrameInfo *info) return info->sequence; } + +int64_t +cogl_frame_info_get_rendering_duration_ns (CoglFrameInfo *info) +{ + int64_t gpu_time_rendering_done_ns; + + if (!info->timestamp_query || + info->gpu_time_before_buffer_swap_ns == 0) + return 0; + + gpu_time_rendering_done_ns = + cogl_context_timestamp_query_get_time_ns (info->context, + info->timestamp_query); + + return gpu_time_rendering_done_ns - info->gpu_time_before_buffer_swap_ns; +} + +int64_t +cogl_frame_info_get_time_before_buffer_swap_us (CoglFrameInfo *info) +{ + return info->cpu_time_before_buffer_swap_us; +} diff --git a/cogl/cogl/cogl-frame-info.h b/cogl/cogl/cogl-frame-info.h index 7278a08ca6d1bad376bbdb1c03facee0dd4d5a97..c0ff12bc17612c6ebfe63fc402c3dfc1131aa98f 100644 --- a/cogl/cogl/cogl-frame-info.h +++ b/cogl/cogl/cogl-frame-info.h @@ -150,6 +150,12 @@ gboolean cogl_frame_info_is_vsync (CoglFrameInfo *info); COGL_EXPORT unsigned int cogl_frame_info_get_sequence (CoglFrameInfo *info); +COGL_EXPORT +int64_t cogl_frame_info_get_rendering_duration_ns (CoglFrameInfo *info); + +COGL_EXPORT +int64_t cogl_frame_info_get_time_before_buffer_swap_us (CoglFrameInfo *info); + G_END_DECLS #endif /* __COGL_FRAME_INFO_H */ diff --git a/cogl/cogl/cogl-framebuffer.c b/cogl/cogl/cogl-framebuffer.c index 7a8acecb96f3c815f0cc94e709d0f1a69e3c3249..525b24293fdce2c5ba75d43a906f54b541bd5d58 100644 --- a/cogl/cogl/cogl-framebuffer.c +++ b/cogl/cogl/cogl-framebuffer.c @@ -2656,3 +2656,28 @@ cogl_framebuffer_get_driver (CoglFramebuffer *framebuffer) return priv->driver; } + +CoglTimestampQuery * +cogl_framebuffer_create_timestamp_query (CoglFramebuffer *framebuffer) +{ + CoglFramebufferPrivate *priv = + cogl_framebuffer_get_instance_private (framebuffer); + const CoglDriverVtable *driver_vtable = priv->context->driver_vtable; + + g_return_val_if_fail (cogl_has_feature (priv->context, + COGL_FEATURE_ID_TIMESTAMP_QUERY), + NULL); + + /* The timestamp query completes upon completion of all previously submitted + * GL commands. So make sure those commands are indeed submitted by flushing + * the journal. + */ + _cogl_framebuffer_flush_journal (framebuffer); + + cogl_context_flush_framebuffer_state (priv->context, + framebuffer, + framebuffer, + COGL_FRAMEBUFFER_STATE_BIND); + + return driver_vtable->create_timestamp_query (priv->context); +} diff --git a/cogl/cogl/cogl-framebuffer.h b/cogl/cogl/cogl-framebuffer.h index 993de7a25f9c8c9d46b18ed0ed049eaa64b54a11..571b4899882b648b352e6e1e95747241a8fe03a1 100644 --- a/cogl/cogl/cogl-framebuffer.h +++ b/cogl/cogl/cogl-framebuffer.h @@ -1564,6 +1564,19 @@ cogl_blit_framebuffer (CoglFramebuffer *framebuffer, COGL_EXPORT void cogl_framebuffer_flush (CoglFramebuffer *framebuffer); +/** + * cogl_framebuffer_create_timestamp_query: (skip) + * + * Creates a query for the GPU timestamp that will complete upon completion of + * all previously submitted GL commands related to this framebuffer. E.g. when + * the rendering is finished on this framebuffer. + * + * This function should only be called if the COGL_FEATURE_ID_TIMESTAMP_QUERY + * feature is advertised. + */ +COGL_EXPORT CoglTimestampQuery * +cogl_framebuffer_create_timestamp_query (CoglFramebuffer *framebuffer); + G_END_DECLS #endif /* __COGL_FRAMEBUFFER_H */ diff --git a/cogl/cogl/driver/gl/cogl-util-gl-private.h b/cogl/cogl/driver/gl/cogl-util-gl-private.h index 0b7eaa772e12e730ee0e23f4f3431fd3dd49ac8b..be8fa1eeb996da09a2992c5cd122dc242f885f2d 100644 --- a/cogl/cogl/driver/gl/cogl-util-gl-private.h +++ b/cogl/cogl/driver/gl/cogl-util-gl-private.h @@ -146,6 +146,20 @@ _cogl_gl_util_parse_gl_version (const char *version_string, CoglGraphicsResetStatus _cogl_gl_get_graphics_reset_status (CoglContext *context); +CoglTimestampQuery * +cogl_gl_create_timestamp_query (CoglContext *context); + +void +cogl_gl_free_timestamp_query (CoglContext *context, + CoglTimestampQuery *query); + +int64_t +cogl_gl_timestamp_query_get_time_ns (CoglContext *context, + CoglTimestampQuery *query); + +int64_t +cogl_gl_get_gpu_time_ns (CoglContext *context); + #ifndef GL_FRAMEBUFFER #define GL_FRAMEBUFFER 0x8D40 #endif @@ -229,4 +243,11 @@ _cogl_gl_get_graphics_reset_status (CoglContext *context); #define GL_STENCIL 0x1802 #endif +#ifndef GL_TIMESTAMP +#define GL_TIMESTAMP 0x8E28 +#endif +#ifndef GL_QUERY_RESULT +#define GL_QUERY_RESULT 0x8866 +#endif + #endif /* _COGL_UTIL_GL_PRIVATE_H_ */ diff --git a/cogl/cogl/driver/gl/cogl-util-gl.c b/cogl/cogl/driver/gl/cogl-util-gl.c index 9b59bef1d652ee6b2c89425fa9fd67acae42f526..096fcb78863301a7749ef4fbf53817ca7e3fbe73 100644 --- a/cogl/cogl/driver/gl/cogl-util-gl.c +++ b/cogl/cogl/driver/gl/cogl-util-gl.c @@ -493,3 +493,63 @@ _cogl_gl_get_graphics_reset_status (CoglContext *context) return COGL_GRAPHICS_RESET_STATUS_NO_ERROR; } } + +CoglTimestampQuery * +cogl_gl_create_timestamp_query (CoglContext *context) +{ + CoglTimestampQuery *query; + + g_return_val_if_fail (cogl_has_feature (context, + COGL_FEATURE_ID_TIMESTAMP_QUERY), + NULL); + + query = g_new0 (CoglTimestampQuery, 1); + + GE (context, glGenQueries (1, &query->id)); + GE (context, glQueryCounter (query->id, GL_TIMESTAMP)); + + /* Flush right away so GL knows about our timestamp query. + * + * E.g. the direct scanout path doesn't call SwapBuffers or any other + * glFlush-inducing operation, and skipping explicit glFlush here results in + * the timestamp query being placed at the point of glGetQueryObject much + * later, resulting in a GPU timestamp much later on in time. + */ + GE (context, glFlush ()); + + return query; +} + +void +cogl_gl_free_timestamp_query (CoglContext *context, + CoglTimestampQuery *query) +{ + GE (context, glDeleteQueries (1, &query->id)); + g_free (query); +} + +int64_t +cogl_gl_timestamp_query_get_time_ns (CoglContext *context, + CoglTimestampQuery *query) +{ + int64_t query_time_ns; + + GE (context, glGetQueryObjecti64v (query->id, + GL_QUERY_RESULT, + &query_time_ns)); + + return query_time_ns; +} + +int64_t +cogl_gl_get_gpu_time_ns (CoglContext *context) +{ + int64_t gpu_time_ns; + + g_return_val_if_fail (cogl_has_feature (context, + COGL_FEATURE_ID_GET_GPU_TIME), + 0); + + GE (context, glGetInteger64v (GL_TIMESTAMP, &gpu_time_ns)); + return gpu_time_ns; +} diff --git a/cogl/cogl/driver/gl/gl/cogl-driver-gl.c b/cogl/cogl/driver/gl/gl/cogl-driver-gl.c index 35577518d0e26ebe35f3dc82d5e6d0a860447b59..ef4a61a09afd344ba78e45855a81d6e60bd042a9 100644 --- a/cogl/cogl/driver/gl/gl/cogl-driver-gl.c +++ b/cogl/cogl/driver/gl/gl/cogl-driver-gl.c @@ -539,6 +539,12 @@ _cogl_driver_update_features (CoglContext *ctx, COGL_PRIVATE_FEATURE_TEXTURE_FORMAT_HALF_FLOAT, TRUE); + if (ctx->glGenQueries && ctx->glQueryCounter) + COGL_FLAGS_SET (ctx->features, COGL_FEATURE_ID_TIMESTAMP_QUERY, TRUE); + + if (ctx->glGetInteger64v) + COGL_FLAGS_SET (ctx->features, COGL_FEATURE_ID_GET_GPU_TIME, TRUE); + /* Cache features */ for (i = 0; i < G_N_ELEMENTS (private_features); i++) ctx->private_features[i] |= private_features[i]; @@ -591,4 +597,8 @@ _cogl_driver_gl = _cogl_sampler_gl_init, _cogl_sampler_gl_free, _cogl_gl_set_uniform, /* XXX name is weird... */ + cogl_gl_create_timestamp_query, + cogl_gl_free_timestamp_query, + cogl_gl_timestamp_query_get_time_ns, + cogl_gl_get_gpu_time_ns, }; diff --git a/cogl/cogl/driver/gl/gles/cogl-driver-gles.c b/cogl/cogl/driver/gl/gles/cogl-driver-gles.c index 3e8ea8a0a2a3f01ea3e80247c3737ab7aba81d25..633e2c4157400c2cbd723787ecf3194afb5d2f8e 100644 --- a/cogl/cogl/driver/gl/gles/cogl-driver-gles.c +++ b/cogl/cogl/driver/gl/gles/cogl-driver-gles.c @@ -432,6 +432,12 @@ _cogl_driver_update_features (CoglContext *context, COGL_FEATURE_ID_TEXTURE_RG, TRUE); + if (context->glGenQueries && context->glQueryCounter) + COGL_FLAGS_SET (context->features, COGL_FEATURE_ID_TIMESTAMP_QUERY, TRUE); + + if (context->glGetInteger64v) + COGL_FLAGS_SET (context->features, COGL_FEATURE_ID_GET_GPU_TIME, TRUE); + /* Cache features */ for (i = 0; i < G_N_ELEMENTS (private_features); i++) context->private_features[i] |= private_features[i]; @@ -479,4 +485,8 @@ _cogl_driver_gles = _cogl_sampler_gl_init, _cogl_sampler_gl_free, _cogl_gl_set_uniform, + cogl_gl_create_timestamp_query, + cogl_gl_free_timestamp_query, + cogl_gl_timestamp_query_get_time_ns, + cogl_gl_get_gpu_time_ns, }; diff --git a/cogl/cogl/gl-prototypes/cogl-all-functions.h b/cogl/cogl/gl-prototypes/cogl-all-functions.h index a0c6395af2407b45523c64579015aee1bb06cad4..3a31a610ac42768c4c267798e4cd0d146b34217e 100644 --- a/cogl/cogl/gl-prototypes/cogl-all-functions.h +++ b/cogl/cogl/gl-prototypes/cogl-all-functions.h @@ -224,6 +224,14 @@ COGL_EXT_FUNCTION (void, glDeleteSync, COGL_EXT_END () #endif +COGL_EXT_BEGIN (sync_get_int64, 3, 2, + 0, + "ARB:\0", + "sync\0") +COGL_EXT_FUNCTION (void, glGetInteger64v, + (GLenum pname, GLint64 *params)) +COGL_EXT_END () + COGL_EXT_BEGIN (draw_buffers, 2, 0, COGL_EXT_IN_GLES3, "ARB\0EXT\0", @@ -247,3 +255,23 @@ COGL_EXT_BEGIN (multitexture_part1, 1, 3, COGL_EXT_FUNCTION (void, glClientActiveTexture, (GLenum texture)) COGL_EXT_END () + +COGL_EXT_BEGIN (query_counter, 3, 3, + 0, + "ARB:\0", + "timer_query\0") +COGL_EXT_FUNCTION (void, glQueryCounter, + (GLuint id, GLenum target)) +COGL_EXT_FUNCTION (void, glGetQueryObjecti64v, + (GLuint id, GLenum pname, GLint64 *params)) +COGL_EXT_END () + +COGL_EXT_BEGIN (queries, 1, 5, + 0, + "\0", + "\0") +COGL_EXT_FUNCTION (void, glGenQueries, + (GLsizei n, GLuint *ids)) +COGL_EXT_FUNCTION (void, glDeleteQueries, + (GLsizei n, const GLuint *ids)) +COGL_EXT_END () diff --git a/cogl/cogl/winsys/cogl-onscreen-egl.c b/cogl/cogl/winsys/cogl-onscreen-egl.c index 347e2071cb0b78bb6859b7bbdc8528b6dfbbe072..20b3c60efe724dd0884f4ac8cd21e046c1aa8cbe 100644 --- a/cogl/cogl/winsys/cogl-onscreen-egl.c +++ b/cogl/cogl/winsys/cogl-onscreen-egl.c @@ -28,6 +28,7 @@ #include "winsys/cogl-onscreen-egl.h" #include "cogl-context-private.h" +#include "cogl-frame-info-private.h" #include "cogl-renderer-private.h" #include "cogl-trace.h" #include "winsys/cogl-winsys-egl-private.h" @@ -269,6 +270,21 @@ cogl_onscreen_egl_swap_buffers_with_damage (CoglOnscreen *onscreen, COGL_FRAMEBUFFER (onscreen), COGL_FRAMEBUFFER_STATE_BIND); + if (cogl_has_feature (context, COGL_FEATURE_ID_GET_GPU_TIME)) + { + info->gpu_time_before_buffer_swap_ns = + cogl_context_get_gpu_time_ns (context); + } + + info->cpu_time_before_buffer_swap_us = g_get_monotonic_time (); + + /* Set up a timestamp query for when all rendering will be finished. */ + if (cogl_has_feature (context, COGL_FEATURE_ID_TIMESTAMP_QUERY)) + { + info->timestamp_query = + cogl_framebuffer_create_timestamp_query (COGL_FRAMEBUFFER (onscreen)); + } + if (n_rectangles && egl_renderer->pf_eglSwapBuffersWithDamage) { CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); diff --git a/src/backends/meta-crtc-mode.h b/src/backends/meta-crtc-mode.h index 87778b86625172a332438abb8f927dca9ec663db..2ac90e40894cb0d978a020ce02e82a4266b13421 100644 --- a/src/backends/meta-crtc-mode.h +++ b/src/backends/meta-crtc-mode.h @@ -54,6 +54,7 @@ typedef struct _MetaCrtcModeInfo int width; int height; float refresh_rate; + int64_t vblank_duration_us; MetaCrtcModeFlag flags; } MetaCrtcModeInfo; diff --git a/src/backends/native/meta-crtc-mode-kms.c b/src/backends/native/meta-crtc-mode-kms.c index 6f190edc4a559292bb878e966aa41b9f46660bae..053367cfd1c88db5b87f3640ed059896a71ed245 100644 --- a/src/backends/native/meta-crtc-mode-kms.c +++ b/src/backends/native/meta-crtc-mode-kms.c @@ -55,6 +55,8 @@ meta_crtc_mode_kms_new (MetaKmsMode *kms_mode, crtc_mode_info->flags = drm_mode->flags; crtc_mode_info->refresh_rate = meta_calculate_drm_mode_refresh_rate (drm_mode); + crtc_mode_info->vblank_duration_us = + meta_calculate_drm_mode_vblank_duration_us (drm_mode); crtc_mode_name = g_strndup (drm_mode->name, DRM_DISPLAY_MODE_LEN); mode_kms = g_object_new (META_TYPE_CRTC_MODE_KMS, diff --git a/src/backends/native/meta-drm-buffer-gbm.c b/src/backends/native/meta-drm-buffer-gbm.c index 6bdc9995a9b22d498591e262e1b931a6de0f2e21..f011afaca188fa10b1b3431380b13912d5da123e 100644 --- a/src/backends/native/meta-drm-buffer-gbm.c +++ b/src/backends/native/meta-drm-buffer-gbm.c @@ -192,6 +192,122 @@ meta_drm_buffer_gbm_new_take (MetaDeviceFile *device_file, return buffer_gbm; } +static gboolean +meta_drm_buffer_gbm_fill_timings (MetaDrmBuffer *buffer, + CoglFrameInfo *info, + GError **error) +{ + MetaDrmBufferGbm *buffer_gbm = META_DRM_BUFFER_GBM (buffer); + MetaBackend *backend = meta_get_backend (); + MetaEgl *egl = meta_backend_get_egl (backend); + ClutterBackend *clutter_backend = + meta_backend_get_clutter_backend (backend); + CoglContext *cogl_context = + clutter_backend_get_cogl_context (clutter_backend); + CoglDisplay *cogl_display = cogl_context->display; + CoglRenderer *cogl_renderer = cogl_display->renderer; + CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys; + EGLDisplay egl_display = cogl_renderer_egl->edpy; + EGLImageKHR egl_image; + CoglPixelFormat cogl_format; + CoglEglImageFlags flags; + g_autoptr (CoglOffscreen) cogl_fbo = NULL; + CoglTexture2D *cogl_tex; + uint32_t n_planes; + uint64_t *modifiers; + uint32_t *strides; + uint32_t *offsets; + uint32_t width; + uint32_t height; + uint32_t drm_format; + int *fds; + gboolean result; + int dmabuf_fd = -1; + uint32_t i; + + dmabuf_fd = gbm_bo_get_fd (buffer_gbm->bo); + if (dmabuf_fd == -1) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to export buffer's DMA fd: %s", + g_strerror (errno)); + return FALSE; + } + + drm_format = gbm_bo_get_format (buffer_gbm->bo); + result = meta_cogl_pixel_format_from_drm_format (drm_format, + &cogl_format, + NULL); + g_assert (result); + + width = gbm_bo_get_width (buffer_gbm->bo); + height = gbm_bo_get_height (buffer_gbm->bo); + n_planes = gbm_bo_get_plane_count (buffer_gbm->bo); + fds = g_alloca (sizeof (int) * n_planes); + strides = g_alloca (sizeof (uint32_t) * n_planes); + offsets = g_alloca (sizeof (uint32_t) * n_planes); + modifiers = g_alloca (sizeof (uint64_t) * n_planes); + + for (i = 0; i < n_planes; i++) + { + fds[i] = dmabuf_fd; + strides[i] = gbm_bo_get_stride_for_plane (buffer_gbm->bo, i); + offsets[i] = gbm_bo_get_offset (buffer_gbm->bo, i); + modifiers[i] = gbm_bo_get_modifier (buffer_gbm->bo); + } + + egl_image = meta_egl_create_dmabuf_image (egl, + egl_display, + width, + height, + drm_format, + n_planes, + fds, + strides, + offsets, + modifiers, + error); + if (egl_image == EGL_NO_IMAGE_KHR) + goto out; + + flags = COGL_EGL_IMAGE_FLAG_NO_GET_DATA; + cogl_tex = cogl_egl_texture_2d_new_from_image (cogl_context, + width, + height, + cogl_format, + egl_image, + flags, + error); + + meta_egl_destroy_image (egl, egl_display, egl_image, NULL); + + if (!cogl_tex) + goto out; + + cogl_fbo = cogl_offscreen_new_with_texture (COGL_TEXTURE (cogl_tex)); + cogl_object_unref (cogl_tex); + + if (cogl_has_feature (cogl_context, COGL_FEATURE_ID_GET_GPU_TIME)) + { + info->gpu_time_before_buffer_swap_ns = + cogl_context_get_gpu_time_ns (cogl_context); + } + + info->cpu_time_before_buffer_swap_us = g_get_monotonic_time (); + + /* Set up a timestamp query for when all rendering will be finished. */ + if (cogl_has_feature (cogl_context, COGL_FEATURE_ID_TIMESTAMP_QUERY)) + { + info->timestamp_query = + cogl_framebuffer_create_timestamp_query (COGL_FRAMEBUFFER (cogl_fbo)); + } + +out: + close (dmabuf_fd); + + return TRUE; +} + static gboolean meta_drm_buffer_gbm_blit_to_framebuffer (CoglScanout *scanout, CoglFramebuffer *framebuffer, @@ -227,13 +343,6 @@ meta_drm_buffer_gbm_blit_to_framebuffer (CoglScanout *scanout, int dmabuf_fd = -1; uint32_t i; - if (!buffer_gbm->bo) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, - "No gbm_bo available"); - return FALSE; - } - dmabuf_fd = gbm_bo_get_fd (buffer_gbm->bo); if (dmabuf_fd == -1) { @@ -361,4 +470,5 @@ meta_drm_buffer_gbm_class_init (MetaDrmBufferGbmClass *klass) buffer_class->get_height = meta_drm_buffer_gbm_get_height; buffer_class->get_stride = meta_drm_buffer_gbm_get_stride; buffer_class->get_format = meta_drm_buffer_gbm_get_format; + buffer_class->fill_timings = meta_drm_buffer_gbm_fill_timings; } diff --git a/src/backends/native/meta-drm-buffer-private.h b/src/backends/native/meta-drm-buffer-private.h index 859d21fb930c5094a137b4a1e212015b4bcf1ab4..a54ce7c31e14ab8173d705c1753c7ddba817b100 100644 --- a/src/backends/native/meta-drm-buffer-private.h +++ b/src/backends/native/meta-drm-buffer-private.h @@ -43,6 +43,10 @@ struct _MetaDrmBufferClass int (* get_height) (MetaDrmBuffer *buffer); int (* get_stride) (MetaDrmBuffer *buffer); uint32_t (* get_format) (MetaDrmBuffer *buffer); + + gboolean (* fill_timings) (MetaDrmBuffer *buffer, + CoglFrameInfo *info, + GError **error); }; MetaDeviceFile * meta_drm_buffer_get_device_file (MetaDrmBuffer *buffer); diff --git a/src/backends/native/meta-drm-buffer.c b/src/backends/native/meta-drm-buffer.c index a1a24f08348db87802b7d73a47d4b5edf011ec59..1da622037216b84877f2c23ae75089f47cc438a4 100644 --- a/src/backends/native/meta-drm-buffer.c +++ b/src/backends/native/meta-drm-buffer.c @@ -185,6 +185,27 @@ meta_drm_buffer_get_format (MetaDrmBuffer *buffer) return META_DRM_BUFFER_GET_CLASS (buffer)->get_format (buffer); } +gboolean +meta_drm_buffer_supports_fill_timings (MetaDrmBuffer *buffer) +{ + return META_DRM_BUFFER_GET_CLASS (buffer)->fill_timings != NULL; +} + +gboolean +meta_drm_buffer_fill_timings (MetaDrmBuffer *buffer, + CoglFrameInfo *info, + GError **error) +{ + if (!meta_drm_buffer_supports_fill_timings (buffer)) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Buffer doesn't support filling timing info"); + return FALSE; + } + + return META_DRM_BUFFER_GET_CLASS (buffer)->fill_timings (buffer, info, error); +} + static void meta_drm_buffer_get_property (GObject *object, guint prop_id, diff --git a/src/backends/native/meta-drm-buffer.h b/src/backends/native/meta-drm-buffer.h index 1647d399e7f26736bac58264c9c4f48d76e6769b..d321355911b66ec2b87b0241a4c948c13d0d7d00 100644 --- a/src/backends/native/meta-drm-buffer.h +++ b/src/backends/native/meta-drm-buffer.h @@ -26,6 +26,8 @@ #include #include +#include "cogl/cogl.h" + #define META_TYPE_DRM_BUFFER (meta_drm_buffer_get_type ()) G_DECLARE_DERIVABLE_TYPE (MetaDrmBuffer, meta_drm_buffer, @@ -42,4 +44,10 @@ int meta_drm_buffer_get_stride (MetaDrmBuffer *buffer); uint32_t meta_drm_buffer_get_format (MetaDrmBuffer *buffer); +gboolean meta_drm_buffer_supports_fill_timings (MetaDrmBuffer *buffer); + +gboolean meta_drm_buffer_fill_timings (MetaDrmBuffer *buffer, + CoglFrameInfo *info, + GError **error); + #endif /* META_DRM_BUFFER_H */ diff --git a/src/backends/native/meta-kms-utils.c b/src/backends/native/meta-kms-utils.c index 1c58db83d503daeee095c21f788e7b6718f71a32..2289cfa918eafb91271d450dbf297b776b9c496f 100644 --- a/src/backends/native/meta-kms-utils.c +++ b/src/backends/native/meta-kms-utils.c @@ -47,6 +47,27 @@ meta_calculate_drm_mode_refresh_rate (const drmModeModeInfo *drm_mode) return numerator / denominator; } +int64_t +meta_calculate_drm_mode_vblank_duration_us (const drmModeModeInfo *drm_mode) +{ + int64_t value; + + if (drm_mode->htotal <= 0 || drm_mode->vtotal <= 0) + return 0; + + /* Convert to int64_t early. */ + value = drm_mode->vtotal - drm_mode->vdisplay; + value *= drm_mode->htotal; + + if (drm_mode->flags & DRM_MODE_FLAG_DBLSCAN) + value *= 2; + + /* Round the duration up as it is used for buffer swap deadline computation. */ + value = (value * 1000 + drm_mode->clock - 1) / drm_mode->clock; + + return value; +} + /** * meta_drm_format_to_string: * @tmp: temporary buffer diff --git a/src/backends/native/meta-kms-utils.h b/src/backends/native/meta-kms-utils.h index c22ceaaa0ffffd7b7f0e2ac6ba85e68d91f38c9b..2f2bad1f908fc872f73dc537e4a64f90b2a4a816 100644 --- a/src/backends/native/meta-kms-utils.h +++ b/src/backends/native/meta-kms-utils.h @@ -34,6 +34,9 @@ typedef struct _MetaDrmFormatBuf META_EXPORT_TEST float meta_calculate_drm_mode_refresh_rate (const drmModeModeInfo *drm_mode); +META_EXPORT_TEST +int64_t meta_calculate_drm_mode_vblank_duration_us (const drmModeModeInfo *drm_mode); + const char * meta_drm_format_to_string (MetaDrmFormatBuf *tmp, uint32_t drm_format); diff --git a/src/backends/native/meta-onscreen-native.c b/src/backends/native/meta-onscreen-native.c index e37b86a08f5081df95c8a2da13954d0e9b39a37a..17dcac03f5764a61d8659d5d01f8383e4c4ecad6 100644 --- a/src/backends/native/meta-onscreen-native.c +++ b/src/backends/native/meta-onscreen-native.c @@ -1222,6 +1222,8 @@ meta_onscreen_native_direct_scanout (CoglOnscreen *onscreen, meta_backend_get_monitor_manager (backend); MetaPowerSave power_save_mode; ClutterFrame *frame = user_data; + MetaDrmBuffer *scanout_buffer; + GError *fill_timings_error = NULL; MetaKmsCrtc *kms_crtc; MetaKmsDevice *kms_device; MetaKmsUpdateFlag flags; @@ -1255,6 +1257,33 @@ meta_onscreen_native_direct_scanout (CoglOnscreen *onscreen, g_set_object (&onscreen_native->gbm.next_fb, META_DRM_BUFFER (scanout)); + /* Try to get a measurement of GPU rendering time on the scanout buffer. + * + * The successful operation here adds ~0.4 ms to a ~0.1 ms total frame clock + * dispatch duration when displaying an unredirected client, thus + * unfortunately bringing it more in line with duration of the regular + * non-unredirected frame clock dispatch. However, measuring GPU rendering + * time is important for computing accurate max render time without + * underestimating. Also this operation should be optimizable by caching + * EGLImage for each buffer instead of re-creating it every time it's needed. + * This should also help all other cases which convert the buffer to a + * EGLImage. + */ + if (META_IS_DRM_BUFFER (scanout)) + { + scanout_buffer = META_DRM_BUFFER (scanout); + if (meta_drm_buffer_supports_fill_timings (scanout_buffer)) + { + if (!meta_drm_buffer_fill_timings (scanout_buffer, frame_info, + &fill_timings_error)) + { + g_warning ("Failed to fill timings for a scanout buffer: %s", + fill_timings_error->message); + g_error_free (fill_timings_error); + } + } + } + ensure_crtc_modes (onscreen); meta_onscreen_native_flip_crtc (onscreen, onscreen_native->view, diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c index 9256b679d3a9dbab37439eee5bdf6655f0991b3a..4784b88e4826cdbaf1e7ef9d838007b8bda95674 100644 --- a/src/backends/native/meta-renderer-native.c +++ b/src/backends/native/meta-renderer-native.c @@ -1217,6 +1217,7 @@ meta_renderer_native_create_view (MetaRenderer *renderer, "use-shadowfb", use_shadowfb, "transform", view_transform, "refresh-rate", crtc_mode_info->refresh_rate, + "vblank-duration-us", crtc_mode_info->vblank_duration_us, NULL); if (META_IS_ONSCREEN_NATIVE (framebuffer)) diff --git a/src/backends/x11/nested/meta-stage-x11-nested.c b/src/backends/x11/nested/meta-stage-x11-nested.c index 7a6c14a09fd84d4e3c4a504ec03fc23c64711b7e..b164867061baa6a235c9270f1269b204a4c95b3b 100644 --- a/src/backends/x11/nested/meta-stage-x11-nested.c +++ b/src/backends/x11/nested/meta-stage-x11-nested.c @@ -168,6 +168,7 @@ meta_stage_x11_nested_finish_frame (ClutterStageWindow *stage_window, MetaRenderer *renderer = meta_backend_get_renderer (backend); ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); CoglFramebuffer *onscreen = COGL_FRAMEBUFFER (stage_x11->onscreen); + CoglContext *context = cogl_framebuffer_get_context (onscreen); GList *l; CoglFrameInfo *frame_info; @@ -195,7 +196,7 @@ meta_stage_x11_nested_finish_frame (ClutterStageWindow *stage_window, draw_view (stage_nested, renderer_view, texture); } - frame_info = cogl_frame_info_new (0); + frame_info = cogl_frame_info_new (context, 0); cogl_onscreen_swap_buffers (stage_x11->onscreen, frame_info, frame); if (!clutter_frame_has_result (frame)) diff --git a/src/tests/clutter/conform/frame-clock-timeline.c b/src/tests/clutter/conform/frame-clock-timeline.c index bf99cd17b429b53bdf5815e414104476aa3eff1e..0f9f04d794984f83d084822ebfa7f194b7be1ba9 100644 --- a/src/tests/clutter/conform/frame-clock-timeline.c +++ b/src/tests/clutter/conform/frame-clock-timeline.c @@ -64,6 +64,7 @@ frame_clock_timeline_basic (void) main_loop = g_main_loop_new (NULL, FALSE); frame_clock = clutter_frame_clock_new (refresh_rate, + 0, &timeline_frame_listener_iface, NULL); g_object_add_weak_pointer (G_OBJECT (frame_clock), (gpointer *) &frame_clock); @@ -143,10 +144,12 @@ frame_clock_timeline_switch (void) main_loop = g_main_loop_new (NULL, FALSE); frame_clock1 = clutter_frame_clock_new (refresh_rate, + 0, &timeline_frame_listener_iface, NULL); g_object_add_weak_pointer (G_OBJECT (frame_clock1), (gpointer *) &frame_clock1); frame_clock2 = clutter_frame_clock_new (refresh_rate, + 0, &timeline_frame_listener_iface, NULL); g_object_add_weak_pointer (G_OBJECT (frame_clock2), (gpointer *) &frame_clock2); diff --git a/src/tests/clutter/conform/frame-clock.c b/src/tests/clutter/conform/frame-clock.c index 45d21ccec204bf02c4d743daaca35b70633ae07b..810c39a02cbb811bf998fee713b09ea9e8796c20 100644 --- a/src/tests/clutter/conform/frame-clock.c +++ b/src/tests/clutter/conform/frame-clock.c @@ -146,6 +146,7 @@ frame_clock_schedule_update (void) test.main_loop = g_main_loop_new (NULL, FALSE); frame_clock = clutter_frame_clock_new (refresh_rate, + 0, &frame_listener_iface, &test); @@ -228,6 +229,7 @@ frame_clock_immediate_present (void) main_loop = g_main_loop_new (NULL, FALSE); frame_clock = clutter_frame_clock_new (refresh_rate, + 0, &immediate_frame_listener_iface, main_loop); @@ -306,6 +308,7 @@ frame_clock_delayed_damage (void) test.main_loop = g_main_loop_new (NULL, FALSE); frame_clock = clutter_frame_clock_new (refresh_rate, + 0, &delayed_damage_frame_listener_iface, &test); @@ -366,6 +369,7 @@ frame_clock_no_damage (void) main_loop = g_main_loop_new (NULL, FALSE); frame_clock = clutter_frame_clock_new (refresh_rate, + 0, &no_damage_frame_listener_iface, NULL); @@ -452,6 +456,7 @@ frame_clock_schedule_update_now (void) test.base.main_loop = g_main_loop_new (NULL, FALSE); frame_clock = clutter_frame_clock_new (refresh_rate, + 0, &update_now_frame_listener_iface, &test); @@ -534,6 +539,7 @@ frame_clock_before_frame (void) main_loop = g_main_loop_new (NULL, FALSE); frame_clock = clutter_frame_clock_new (refresh_rate, + 0, &before_frame_frame_listener_iface, &expected_frame_count); @@ -615,6 +621,7 @@ frame_clock_inhibit (void) test.main_loop = g_main_loop_new (NULL, FALSE); test.frame_clock = clutter_frame_clock_new (refresh_rate, + 0, &inhibit_frame_listener_iface, &test); @@ -678,6 +685,7 @@ frame_clock_reschedule_on_idle (void) test.base.main_loop = g_main_loop_new (NULL, FALSE); frame_clock = clutter_frame_clock_new (refresh_rate, + 0, &reschedule_on_idle_listener_iface, &test); fake_hw_clock = fake_hw_clock_new (frame_clock, NULL, NULL); @@ -714,6 +722,7 @@ frame_clock_destroy_signal (void) /* Test that the destroy signal is emitted when removing last reference. */ frame_clock = clutter_frame_clock_new (refresh_rate, + 0, &dummy_frame_listener_iface, NULL); @@ -732,6 +741,7 @@ frame_clock_destroy_signal (void) */ frame_clock = clutter_frame_clock_new (refresh_rate, + 0, &dummy_frame_listener_iface, NULL); frame_clock_backup = frame_clock; @@ -802,6 +812,7 @@ frame_clock_notify_ready (void) main_loop = g_main_loop_new (NULL, FALSE); frame_clock = clutter_frame_clock_new (refresh_rate, + 0, &frame_clock_ready_listener_iface, main_loop); diff --git a/src/tests/kms-utils-unit-tests.c b/src/tests/kms-utils-unit-tests.c index 324166d684fab361198b065f93be3b5e120cc3ec..40b768b3e69515bf4c9d2c36ff59a34ca648b683 100644 --- a/src/tests/kms-utils-unit-tests.c +++ b/src/tests/kms-utils-unit-tests.c @@ -26,9 +26,9 @@ typedef struct { drmModeModeInfo drm_mode; float expected_refresh_rate; -} ModeInfoTestCase; +} RefreshRateTestCase; -static const ModeInfoTestCase test_cases[] = { +static const RefreshRateTestCase refresh_rate_test_cases[] = { /* "cvt 640 480" */ { .drm_mode = { @@ -125,9 +125,9 @@ meta_test_kms_refresh_rate (void) { size_t index; - for (index = 0; index < G_N_ELEMENTS(test_cases); index++) + for (index = 0; index < G_N_ELEMENTS (refresh_rate_test_cases); index++) { - const ModeInfoTestCase *test_case = test_cases + index; + const RefreshRateTestCase *test_case = refresh_rate_test_cases + index; float refresh_rate; refresh_rate = @@ -138,6 +138,105 @@ meta_test_kms_refresh_rate (void) } } +typedef struct +{ + drmModeModeInfo drm_mode; + int64_t expected_vblank_duration_us; +} VblankDurationTestCase; + +static const VblankDurationTestCase vblank_duration_test_cases[] = { + /* "cvt 640 480" */ + { + .drm_mode = { + .clock = 23975, + .hdisplay = 640, + .hsync_start = 664, + .hsync_end = 720, + .htotal = 800, + .vdisplay = 480, + .vsync_start = 483, + .vsync_end = 487, + .vtotal = 500, + .vscan = 0, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC, + }, + .expected_vblank_duration_us = 668, + }, + + /* "cvt 640 480" with htotal 0 */ + { + .drm_mode = { + .clock = 23975, + .hdisplay = 640, + .hsync_start = 664, + .hsync_end = 720, + .htotal = 0, + .vdisplay = 480, + .vsync_start = 483, + .vsync_end = 487, + .vtotal = 500, + .vscan = 0, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC, + }, + .expected_vblank_duration_us = 0, + }, + + /* "cvt 640 480" with vtotal 0 */ + { + .drm_mode = { + .clock = 23975, + .hdisplay = 640, + .hsync_start = 664, + .hsync_end = 720, + .htotal = 800, + .vdisplay = 480, + .vsync_start = 483, + .vsync_end = 487, + .vtotal = 0, + .vscan = 0, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC, + }, + .expected_vblank_duration_us = 0, + }, + + /* "cvt 640 480" with DBLSCAN */ + { + .drm_mode = { + .clock = 23975, + .hdisplay = 640, + .hsync_start = 664, + .hsync_end = 720, + .htotal = 800, + .vdisplay = 480, + .vsync_start = 483, + .vsync_end = 487, + .vtotal = 500, + .vscan = 0, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC | + DRM_MODE_FLAG_DBLSCAN, + }, + .expected_vblank_duration_us = 1335, + }, +}; + +static void +meta_test_kms_vblank_duration (void) +{ + size_t index; + + for (index = 0; index < G_N_ELEMENTS (vblank_duration_test_cases); index++) + { + const VblankDurationTestCase *test_case = vblank_duration_test_cases + index; + int64_t vblank_duration_us; + + vblank_duration_us = + meta_calculate_drm_mode_vblank_duration_us (&test_case->drm_mode); + g_assert_cmpint (vblank_duration_us, + ==, + test_case->expected_vblank_duration_us); + } +} + static void meta_test_kms_update_fixed16 (void) { @@ -152,6 +251,8 @@ init_kms_utils_tests (void) { g_test_add_func ("/backends/native/kms/refresh-rate", meta_test_kms_refresh_rate); + g_test_add_func ("/backends/native/kms/vblank-duration", + meta_test_kms_vblank_duration); g_test_add_func ("/backends/native/kms/update/fixed16", meta_test_kms_update_fixed16); }