2 * Copyright © 2008 Chris Wilson
4 * Permission to use, copy, modify, distribute, and sell this software
5 * and its documentation for any purpose is hereby granted without
6 * fee, provided that the above copyright notice appear in all copies
7 * and that both that copyright notice and this permission notice
8 * appear in supporting documentation, and that the name of the
9 * copyright holders not be used in advertising or publicity
10 * pertaining to distribution of the software without specific,
11 * written prior permission. The copyright holders make no
12 * representations about the suitability of this software for any
13 * purpose. It is provided "as is" without express or implied
16 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
17 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
18 * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
21 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
22 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
25 * Authors: Chris Wilson <chris@chris-wilson.co.uk>
28 #include "cairo-perf.h"
29 #include "cairo-perf-graph.h"
37 cairo_perf_report_t *reports;
44 typedef struct _GraphViewClass {
45 GtkWidgetClass parent_class;
48 static GType graph_view_get_type (void);
55 static guint signals[LAST_SIGNAL];
57 G_DEFINE_TYPE (GraphView, graph_view, GTK_TYPE_WIDGET)
60 draw_baseline_performance (test_case_t *cases,
61 cairo_perf_report_t *reports,
64 const cairo_matrix_t *m)
66 test_report_t **tests;
67 double dots[2] = { 0, 1.};
70 tests = xmalloc (num_reports * sizeof (test_report_t *));
71 for (i = 0; i < num_reports; i++)
72 tests[i] = reports[i].tests;
74 while (cases->backend != NULL) {
75 test_report_t *min_test;
76 double baseline, last_y;
84 min_test = cases->min_test;
86 for (i = 0; i < num_reports; i++) {
87 while (tests[i]->name &&
88 test_report_cmp_backend_then_name (tests[i], min_test) < 0)
94 /* first the stroke */
96 cairo_set_line_width (cr, 2.);
97 gdk_cairo_set_source_color (cr, &cases->color);
98 for (i = 0; i < num_reports; i++) {
100 test_report_cmp_backend_then_name (tests[i], min_test) == 0)
102 baseline = tests[i]->stats.min_ticks;
105 cairo_matrix_transform_point (m, &x, &y);
108 cairo_move_to (cr, x, y);
114 for (++i; i < num_reports; i++) {
115 if (tests[i]->name &&
116 test_report_cmp_backend_then_name (tests[i], min_test) == 0)
118 x = i, y = tests[i]->stats.min_ticks / baseline;
125 cairo_matrix_transform_point (m, &x, &y);
128 cairo_line_to (cr, x, last_y);
129 cairo_line_to (cr, x, y);
134 x = num_reports, y = 0;
135 cairo_matrix_transform_point (m, &x, &y);
137 cairo_line_to (cr, x, last_y);
140 cairo_set_line_width (cr, 1.);
143 /* then draw the points */
144 for (i = 0; i < num_reports; i++) {
145 if (tests[i]->name &&
146 test_report_cmp_backend_then_name (tests[i], min_test) == 0)
148 baseline = tests[i]->stats.min_ticks;
151 cairo_matrix_transform_point (m, &x, &y);
154 cairo_move_to (cr, x, y);
155 cairo_close_path (cr);
163 for (++i; i < num_reports; i++) {
164 if (tests[i]->name &&
165 test_report_cmp_backend_then_name (tests[i], min_test) == 0)
167 x = i, y = tests[i]->stats.min_ticks / baseline;
174 cairo_matrix_transform_point (m, &x, &y);
177 cairo_move_to (cr, x, last_y);
178 cairo_close_path (cr);
179 cairo_move_to (cr, x, y);
180 cairo_close_path (cr);
187 x = num_reports, y = 0;
188 cairo_matrix_transform_point (m, &x, &y);
190 cairo_move_to (cr, x, last_y);
191 cairo_close_path (cr);
193 cairo_set_source_rgba (cr, 0, 0, 0, .5);
194 cairo_set_dash (cr, dots, 2, 0.);
195 cairo_set_line_width (cr, 3.);
196 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
206 draw_hline (cairo_t *cr,
207 const cairo_matrix_t *m,
215 py_offset = fmod (cairo_get_line_width (cr) / 2., 1.);
218 cairo_matrix_transform_point (m, &x, &y);
219 cairo_move_to (cr, floor (x), floor (y) + py_offset);
222 cairo_matrix_transform_point (m, &x, &y);
223 cairo_line_to (cr, ceil (x), floor (y) + py_offset);
229 draw_label (cairo_t *cr,
230 const cairo_matrix_t *m,
237 cairo_text_extents_t extents;
239 snprintf (buf, sizeof (buf), "%.0fx", fabs (y0));
240 cairo_text_extents (cr, buf, &extents);
243 cairo_matrix_transform_point (m, &x, &y);
245 x - extents.width - 4,
246 y - (extents.height/2. + extents.y_bearing));
247 cairo_show_text (cr, buf);
250 snprintf (buf, sizeof (buf), "%.0fx", fabs (y0));
251 cairo_text_extents (cr, buf, &extents);
254 cairo_matrix_transform_point (m, &x, &y);
257 y - (extents.height/2. + extents.y_bearing));
258 cairo_show_text (cr, buf);
261 #define ALIGN_X(v) ((v)<<0)
262 #define ALIGN_Y(v) ((v)<<2)
264 draw_rotated_label (cairo_t *cr,
271 cairo_text_extents_t extents;
273 cairo_text_extents (cr, text, &extents);
276 cairo_translate (cr, x, y);
277 cairo_rotate (cr, angle);
279 case ALIGN_X(0) | ALIGN_Y(0):
284 case ALIGN_X(0) | ALIGN_Y(1):
287 - (extents.height/2. + extents.y_bearing));
289 case ALIGN_X(0) | ALIGN_Y(2):
292 - (extents.height + extents.y_bearing));
295 case ALIGN_X(1) | ALIGN_Y(0):
297 - (extents.width/2. + extents.x_bearing),
300 case ALIGN_X(1) | ALIGN_Y(1):
302 - (extents.width/2. + extents.x_bearing),
303 - (extents.height/2. + extents.y_bearing));
305 case ALIGN_X(1) | ALIGN_Y(2):
307 - (extents.width/2. + extents.x_bearing),
308 - (extents.height + extents.y_bearing));
311 case ALIGN_X(2) | ALIGN_Y(0):
313 - (extents.width + extents.x_bearing),
316 case ALIGN_X(2) | ALIGN_Y(1):
318 - (extents.width + extents.x_bearing),
319 - (extents.height/2. + extents.y_bearing));
321 case ALIGN_X(2) | ALIGN_Y(2):
323 - (extents.width + extents.x_bearing),
324 - (extents.height + extents.y_bearing));
327 cairo_show_text (cr, text);
328 } cairo_restore (cr);
333 graph_view_draw (GraphView *self,
337 const double dash[2] = {4, 4};
341 if (self->widget.allocation.width < 4 *PAD)
343 if (self->widget.allocation.height < 3 *PAD)
346 range = floor (self->ymax+1) - ceil (self->ymin-1);
348 cairo_matrix_init_translate (&m, PAD, self->widget.allocation.height - PAD);
349 cairo_matrix_scale (&m,
350 (self->widget.allocation.width-2*PAD)/(self->num_reports),
351 -(self->widget.allocation.height-2*PAD)/range);
352 cairo_matrix_translate (&m, 0, floor (self->ymax+1));
354 if (self->selected_report != -1) {
357 x0 = self->selected_report; y = 0;
358 cairo_matrix_transform_point (&m, &x0, &y);
360 x1 = self->selected_report + 1; y = 0;
361 cairo_matrix_transform_point (&m, &x1, &y);
364 y = MIN (y, PAD / 2);
367 cairo_rectangle (cr, x0, PAD/2, x1-x0, self->widget.allocation.height-2*PAD + PAD);
368 gdk_cairo_set_source_color (cr, &self->widget.style->base[GTK_STATE_SELECTED]);
370 } cairo_restore (cr);
374 cairo_pattern_t *linear;
377 gdk_cairo_set_source_color (cr,
378 &self->widget.style->fg[GTK_WIDGET_STATE (self)]);
379 cairo_set_line_width (cr, 2.);
380 draw_hline (cr, &m, 0, 0, self->num_reports);
382 cairo_set_line_width (cr, 1.);
383 cairo_set_dash (cr, NULL, 0, 0);
385 for (i = ceil (self->ymin-1); i <= floor (self->ymax+1); i++) {
387 draw_hline (cr, &m, i, 0, self->num_reports);
390 cairo_set_font_size (cr, 11);
392 linear = cairo_pattern_create_linear (0, PAD, 0, self->widget.allocation.height-2*PAD);
393 cairo_pattern_add_color_stop_rgb (linear, 0, 0, 1, 0);
394 cairo_pattern_add_color_stop_rgb (linear, 1, 1, 0, 0);
395 cairo_set_source (cr, linear);
396 cairo_pattern_destroy (linear);
398 for (i = ceil (self->ymin-1); i <= floor (self->ymax+1); i++) {
400 draw_label (cr, &m, i, 0, self->num_reports);
403 x = 0, y = floor (self->ymax+1);
404 cairo_matrix_transform_point (&m, &x, &y);
405 draw_rotated_label (cr, "Faster", x - 7, y + 14,
407 ALIGN_X(2) | ALIGN_Y(1));
408 x = self->num_reports, y = floor (self->ymax+1);
409 cairo_matrix_transform_point (&m, &x, &y);
410 draw_rotated_label (cr, "Faster", x + 11, y + 14,
412 ALIGN_X(2) | ALIGN_Y(1));
414 x = 0, y = ceil (self->ymin-1);
415 cairo_matrix_transform_point (&m, &x, &y);
416 draw_rotated_label (cr, "Slower", x - 7, y - 14,
418 ALIGN_X(2) | ALIGN_Y(1));
419 x = self->num_reports, y = ceil (self->ymin-1);
420 cairo_matrix_transform_point (&m, &x, &y);
421 draw_rotated_label (cr, "Slower", x + 11, y - 14,
423 ALIGN_X(2) | ALIGN_Y(1));
424 } cairo_restore (cr);
426 draw_baseline_performance (self->cases,
427 self->reports, self->num_reports,
431 cairo_set_source_rgb (cr, 0.7, 0.7, 0.7);
432 cairo_set_line_width (cr, 1.);
433 cairo_set_dash (cr, dash, 2, 0);
434 draw_hline (cr, &m, 0, 0, self->num_reports);
435 } cairo_restore (cr);
439 graph_view_expose (GtkWidget *w,
442 GraphView *self = (GraphView *) w;
445 cr = gdk_cairo_create (w->window);
446 gdk_cairo_set_source_color (cr, &w->style->base[GTK_WIDGET_STATE (w)]);
449 graph_view_draw (self, cr);
457 graph_view_button_press (GtkWidget *w,
460 GraphView *self = (GraphView *) w;
465 cairo_matrix_init_translate (&m, PAD, self->widget.allocation.height-PAD);
466 cairo_matrix_scale (&m, (self->widget.allocation.width-2*PAD)/self->num_reports, -(self->widget.allocation.height-2*PAD)/(self->ymax - self->ymin));
467 cairo_matrix_translate (&m, 0, -self->ymin);
468 cairo_matrix_invert (&m);
472 cairo_matrix_transform_point (&m, &x, &y);
475 if (i < 0 || i >= self->num_reports)
478 if (i != self->selected_report) {
479 self->selected_report = i;
480 gtk_widget_queue_draw (w);
482 g_signal_emit (w, signals[REPORT_SELECTED], 0, i);
489 graph_view_button_release (GtkWidget *w,
492 GraphView *self = (GraphView *) w;
498 graph_view_realize (GtkWidget *widget)
500 GdkWindowAttr attributes;
502 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
504 attributes.window_type = GDK_WINDOW_CHILD;
505 attributes.x = widget->allocation.x;
506 attributes.y = widget->allocation.y;
507 attributes.width = widget->allocation.width;
508 attributes.height = widget->allocation.height;
509 attributes.wclass = GDK_INPUT_OUTPUT;
510 attributes.visual = gtk_widget_get_visual (widget);
511 attributes.colormap = gtk_widget_get_colormap (widget);
512 attributes.event_mask = gtk_widget_get_events (widget) |
513 GDK_BUTTON_PRESS_MASK |
514 GDK_BUTTON_RELEASE_MASK |
517 widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
519 GDK_WA_X | GDK_WA_Y |
520 GDK_WA_VISUAL | GDK_WA_COLORMAP);
521 gdk_window_set_user_data (widget->window, widget);
523 widget->style = gtk_style_attach (widget->style, widget->window);
524 gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
528 graph_view_finalize (GObject *obj)
530 G_OBJECT_CLASS (graph_view_parent_class)->finalize (obj);
534 graph_view_class_init (GraphViewClass *klass)
536 GObjectClass *object_class = (GObjectClass *) klass;
537 GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
539 object_class->finalize = graph_view_finalize;
541 widget_class->realize = graph_view_realize;
542 widget_class->expose_event = graph_view_expose;
543 widget_class->button_press_event = graph_view_button_press;
544 widget_class->button_release_event = graph_view_button_release;
546 signals[REPORT_SELECTED] =
547 g_signal_new ("report-selected",
548 G_TYPE_FROM_CLASS (object_class),
550 0,//G_STRUCT_OFFSET (GraphView, report_selected),
552 g_cclosure_marshal_VOID__INT,
553 G_TYPE_NONE, 1, G_TYPE_INT);
557 graph_view_init (GraphView *self)
559 self->selected_report = -1;
563 graph_view_new (void)
565 return g_object_new (graph_view_get_type (), NULL);
569 graph_view_update_visible (GraphView *gv)
577 while (cases->name != NULL) {
579 if (cases->min < min)
581 if (cases->max > max)
586 gv->ymin = -1/min + 1;
589 gtk_widget_queue_draw (&gv->widget);
593 graph_view_set_reports (GraphView *gv,
595 cairo_perf_report_t *reports,
600 gv->reports = reports;
601 gv->num_reports = num_reports;
603 graph_view_update_visible (gv);