From a9b642540cd6fc0de938ab388ae0fec0e2001e2e Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Fri, 22 Mar 2019 01:29:36 +0000 Subject: [PATCH] Add frame time chart Add a chart at the bottom 10% of the screen where each bar represents the time the frame took to be painted. The red line represents the maximum time to draw. For example, if you are on a 60Hz monitor, the red line means 16.6667 miliseconds. This covers both layout time (green) and paint time (blue). Enjoy. https://gitlab.gnome.org/GNOME/mutter/merge_requests/502 --- clutter/clutter/clutter-main.c | 1 + clutter/clutter/clutter-main.h | 1 + clutter/clutter/clutter-stage-private.h | 4 + clutter/clutter/clutter-stage.c | 9 + clutter/clutter/cogl/clutter-stage-cogl.c | 217 ++++++++++++++++++++++ clutter/clutter/cogl/clutter-stage-cogl.h | 3 + 6 files changed, 235 insertions(+) diff --git a/clutter/clutter/clutter-main.c b/clutter/clutter/clutter-main.c index 7616eaae0..cff072331 100644 --- a/clutter/clutter/clutter-main.c +++ b/clutter/clutter/clutter-main.c @@ -143,6 +143,7 @@ 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 }, + { "frame-time", CLUTTER_DEBUG_PAINT_FRAME_TIME }, }; static void diff --git a/clutter/clutter/clutter-main.h b/clutter/clutter/clutter-main.h index e0f10c892..6846cef7a 100644 --- a/clutter/clutter/clutter-main.h +++ b/clutter/clutter/clutter-main.h @@ -74,6 +74,7 @@ typedef enum CLUTTER_DEBUG_CONTINUOUS_REDRAW = 1 << 6, CLUTTER_DEBUG_PAINT_DEFORM_TILES = 1 << 7, CLUTTER_DEBUG_PAINT_DAMAGE_REGION = 1 << 8, + CLUTTER_DEBUG_PAINT_FRAME_TIME = 1 << 9, } ClutterDrawDebugFlag; /** diff --git a/clutter/clutter/clutter-stage-private.h b/clutter/clutter/clutter-stage-private.h index cb887ea32..abe83c321 100644 --- a/clutter/clutter/clutter-stage-private.h +++ b/clutter/clutter/clutter-stage-private.h @@ -136,6 +136,10 @@ GList * _clutter_stage_peek_stage_views (ClutterStage *stage); int clutter_stage_get_sync_delay (ClutterStage *stage); +void clutter_stage_get_frame_times (ClutterStage *stage, + double *paint_time, + double *layout_time); + G_END_DECLS #endif /* __CLUTTER_STAGE_PRIVATE_H__ */ diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c index 1cb5cb9b6..2eda55595 100644 --- a/clutter/clutter/clutter-stage.c +++ b/clutter/clutter/clutter-stage.c @@ -5099,3 +5099,12 @@ _clutter_stage_get_max_view_scale_factor_for_rect (ClutterStage *stage, *view_scale = scale; return TRUE; } + +void +clutter_stage_get_frame_times (ClutterStage *stage, + double *paint_time, + double *layout_time) +{ + *paint_time = stage->priv->last_paint_time; + *layout_time = stage->priv->last_layout_time; +} diff --git a/clutter/clutter/cogl/clutter-stage-cogl.c b/clutter/clutter/cogl/clutter-stage-cogl.c index e0c39185b..7eff2ca07 100644 --- a/clutter/clutter/cogl/clutter-stage-cogl.c +++ b/clutter/clutter/cogl/clutter-stage-cogl.c @@ -405,6 +405,215 @@ paint_damage_region (ClutterStageWindow *stage_window, cogl_framebuffer_pop_matrix (framebuffer); } +#define CHART_COLUMN_WIDTH 6 //px +#define THRESHOLD_LINE_HEIGHT 2 //px +#define MAX_FRAME_TIME_INFO_ENTRIES 1000 + +typedef struct +{ + double layout_time; + double paint_time; +} FrameTimeInfo; + +static void +get_frame_time_chart_region (ClutterStageWindow *stage_window, + ClutterStageView *view, + cairo_rectangle_int_t *region) +{ + ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window); + cairo_rectangle_int_t painted_region; + cairo_rectangle_int_t layout; + double refresh_rate_bar_height; + double ms_per_frame; + float green_line_y; + int sync_delay; + int n_points; + int i; + + clutter_stage_view_get_layout (view, &layout); + + painted_region = (cairo_rectangle_int_t) { + .x = layout.x, + .y = layout.y + layout.height, + .width = layout.width, + .height = 0, + }; + + n_points = layout.width / CHART_COLUMN_WIDTH; + + /* Chart */ + sync_delay = clutter_stage_get_sync_delay (stage_cogl->wrapper); + ms_per_frame = 1000.0 / stage_cogl->refresh_rate - sync_delay; + refresh_rate_bar_height = layout.height * 0.1; + + for (i = 0; i < MIN (stage_cogl->frame_times->len, n_points); i++) + { + int element = stage_cogl->frame_times->len - i - 1; + FrameTimeInfo *info = &g_array_index (stage_cogl->frame_times, FrameTimeInfo, element); + double layout_bar_height; + double paint_bar_height; + + /* Layout time section */ + layout_bar_height = + info->layout_time / ms_per_frame * refresh_rate_bar_height; + + /* Paint time section */ + paint_bar_height = + info->paint_time / ms_per_frame * refresh_rate_bar_height; + + painted_region.height = MAX (painted_region.height, + paint_bar_height + layout_bar_height); + } + + /* Green line (16.667ms) */ + green_line_y = layout.height * 0.9; + + painted_region.height = MAX (painted_region.height, green_line_y); + + /* Update the swap region rectangle */ + painted_region.y -= MIN (layout.height, painted_region.height); + + _clutter_util_rectangle_union (region, &painted_region, region); +} + +static void +paint_frame_time_chart (ClutterStageWindow *stage_window, + ClutterStageView *view) +{ + CoglFramebuffer *framebuffer = clutter_stage_view_get_onscreen (view); + CoglContext *ctx = cogl_framebuffer_get_context (framebuffer); + ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window); + ClutterActor *actor = CLUTTER_ACTOR (stage_cogl->wrapper); + cairo_rectangle_int_t layout; + static CoglPipeline *threshold_pipeline = NULL; + static CoglPipeline *paint_time_pipeline = NULL; + static CoglPipeline *layout_time_pipeline = NULL; + CoglMatrix modelview; + double refresh_rate_bar_height; + double ms_per_frame; + float green_line_y; + int sync_delay; + int n_points; + int i; + + if (G_UNLIKELY (paint_time_pipeline == NULL)) + { + paint_time_pipeline = cogl_pipeline_new (ctx); + cogl_pipeline_set_color4ub (paint_time_pipeline, 0x00, 0x00, 0x60, 0xa0); + } + + if (G_UNLIKELY (layout_time_pipeline == NULL)) + { + layout_time_pipeline = cogl_pipeline_new (ctx); + cogl_pipeline_set_color4ub (layout_time_pipeline, 0x00, 0x60, 0x00, 0xa0); + } + + if (G_UNLIKELY (threshold_pipeline == NULL)) + { + threshold_pipeline = cogl_pipeline_new (ctx); + cogl_pipeline_set_color4ub (threshold_pipeline, 0x40, 0x00, 0x00, 0x80); + } + + cogl_framebuffer_push_matrix (framebuffer); + cogl_matrix_init_identity (&modelview); + _clutter_actor_apply_modelview_transform (actor, &modelview); + cogl_framebuffer_set_modelview_matrix (framebuffer, &modelview); + + clutter_stage_view_get_layout (view, &layout); + + n_points = layout.width / CHART_COLUMN_WIDTH; + + /* Chart */ + sync_delay = clutter_stage_get_sync_delay (stage_cogl->wrapper); + ms_per_frame = 1000.0 / stage_cogl->refresh_rate - sync_delay; + refresh_rate_bar_height = layout.height * 0.1; + + for (i = 0; i < MIN (stage_cogl->frame_times->len, n_points); i++) + { + int element = stage_cogl->frame_times->len - i - 1; + FrameTimeInfo *info = &g_array_index (stage_cogl->frame_times, FrameTimeInfo, element); + double x_1 = layout.width - (i + 1) * CHART_COLUMN_WIDTH; + double x_2 = layout.width - i * CHART_COLUMN_WIDTH; + double layout_bar_height; + double paint_bar_height; + + /* Layout time section */ + layout_bar_height = + info->layout_time / ms_per_frame * refresh_rate_bar_height; + cogl_framebuffer_draw_rectangle (framebuffer, + layout_time_pipeline, + x_1, + layout.height - layout_bar_height, + x_2, + layout.height); + + /* Paint time section */ + paint_bar_height = + info->paint_time / ms_per_frame * refresh_rate_bar_height; + cogl_framebuffer_draw_rectangle (framebuffer, + paint_time_pipeline, + x_1, + layout.height - paint_bar_height - layout_bar_height, + x_2, + layout.height - layout_bar_height); + } + + /* Green line (16.667ms) */ + green_line_y = layout.height * 0.9; + cogl_framebuffer_draw_rectangle (framebuffer, + threshold_pipeline, + 0.0f, + green_line_y, + layout.width, + green_line_y + THRESHOLD_LINE_HEIGHT); + + cogl_framebuffer_pop_matrix (framebuffer); +} + +static inline void +append_frame_time_info (ClutterStageCogl *stage_cogl) +{ + FrameTimeInfo previous_frame_info; + + if (G_LIKELY (!(clutter_paint_debug_flags & CLUTTER_DEBUG_PAINT_FRAME_TIME))) + return; + + if (G_UNLIKELY (stage_cogl->frame_times == NULL)) + stage_cogl->frame_times = g_array_sized_new (FALSE, TRUE, + sizeof (FrameTimeInfo), + MAX_FRAME_TIME_INFO_ENTRIES); + + clutter_stage_get_frame_times (stage_cogl->wrapper, + &previous_frame_info.paint_time, + &previous_frame_info.layout_time); + + g_array_append_val (stage_cogl->frame_times, previous_frame_info); + + if (stage_cogl->frame_times->len > MAX_FRAME_TIME_INFO_ENTRIES) + g_array_remove_range (stage_cogl->frame_times, 0, + stage_cogl->frame_times->len - MAX_FRAME_TIME_INFO_ENTRIES); + +} + +static inline void +maybe_paint_frame_times (ClutterStageWindow *stage_window, + ClutterStageView *view) +{ + ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window); + + if (G_UNLIKELY ((clutter_paint_debug_flags & CLUTTER_DEBUG_PAINT_FRAME_TIME))) + { + paint_frame_time_chart (stage_window, view); + stage_cogl->painting_frame_chart = TRUE; + } + else if (stage_cogl->painting_frame_chart) + { + g_array_free (stage_cogl->frame_times, TRUE); + stage_cogl->frame_times = NULL; + stage_cogl->painting_frame_chart = FALSE; + } +} + static gboolean swap_framebuffer (ClutterStageWindow *stage_window, ClutterStageView *view, @@ -672,6 +881,9 @@ clutter_stage_cogl_redraw_view (ClutterStageWindow *stage_window, fb_clip_region = (cairo_rectangle_int_t) { 0 }; } + if (G_UNLIKELY ((clutter_paint_debug_flags & CLUTTER_DEBUG_PAINT_FRAME_TIME))) + get_frame_time_chart_region (stage_window, view, &fb_clip_region); + if (may_use_clipped_redraw && G_LIKELY (!(clutter_paint_debug_flags & CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS))) use_clipped_redraw = TRUE; @@ -827,6 +1039,9 @@ clutter_stage_cogl_redraw_view (ClutterStageWindow *stage_window, else paint_stage (stage_cogl, view, &view_rect); } + + maybe_paint_frame_times (stage_window, view); + cogl_pop_framebuffer (); if (may_use_clipped_redraw && @@ -931,6 +1146,8 @@ clutter_stage_cogl_redraw (ClutterStageWindow *stage_window) gboolean swap_event = FALSE; GList *l; + append_frame_time_info (stage_cogl); + for (l = _clutter_stage_window_get_views (stage_window); l; l = l->next) { ClutterStageView *view = l->data; diff --git a/clutter/clutter/cogl/clutter-stage-cogl.h b/clutter/clutter/cogl/clutter-stage-cogl.h index 17958cd24..6c56abb85 100644 --- a/clutter/clutter/cogl/clutter-stage-cogl.h +++ b/clutter/clutter/cogl/clutter-stage-cogl.h @@ -66,6 +66,9 @@ struct _ClutterStageCogl /* TRUE if the current paint cycle has a clipped redraw. In that case bounding_redraw_clip specifies the the bounds. */ guint using_clipped_redraw : 1; + + gboolean painting_frame_chart; + GArray *frame_times; }; struct _ClutterStageCoglClass