1 /*
2 * Copyright © 2008 Chris Wilson
3 *
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
14 * warranty.
15 *
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
23 * SOFTWARE.
24 *
25 * Authors: Chris Wilson <chris@chris-wilson.co.uk>
26 */
27
28 #include "cairo-perf.h"
29 #include "cairo-perf-graph.h"
30
31 #include <gtk/gtk.h>
32
33 struct _GraphView {
34 GtkWidget widget;
35
36 test_case_t *cases;
37 cairo_perf_report_t *reports;
38 int num_reports;
39 double ymin, ymax;
40
41 int selected_report;
42 };
43
44 typedef struct _GraphViewClass {
45 GtkWidgetClass parent_class;
46 } GraphViewClass;
47
48 static GType graph_view_get_type (void);
49
50 enum {
51 REPORT_SELECTED,
52 LAST_SIGNAL
53 };
54
55 static guint signals[LAST_SIGNAL];
56
G_DEFINE_TYPE(GraphView,graph_view,GTK_TYPE_WIDGET)57 G_DEFINE_TYPE (GraphView, graph_view, GTK_TYPE_WIDGET)
58
59 static void
60 draw_baseline_performance (test_case_t *cases,
61 cairo_perf_report_t *reports,
62 int num_reports,
63 cairo_t *cr,
64 const cairo_matrix_t *m)
65 {
66 test_report_t **tests;
67 double dots[2] = { 0, 1.};
68 int i;
69
70 tests = xmalloc (num_reports * sizeof (test_report_t *));
71 for (i = 0; i < num_reports; i++)
72 tests[i] = reports[i].tests;
73
74 while (cases->backend != NULL) {
75 test_report_t *min_test;
76 double baseline, last_y;
77 double x, y;
78
79 if (! cases->shown) {
80 cases++;
81 continue;
82 }
83
84 min_test = cases->min_test;
85
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)
89 {
90 tests[i]++;
91 }
92 }
93
94 /* first the stroke */
95 cairo_save (cr);
96 cairo_set_line_width (cr, 2.);
97 gdk_cairo_set_source_color (cr, &cases->color);
98 for (i = 0; i < num_reports; i++) {
99 if (tests[i]->name &&
100 test_report_cmp_backend_then_name (tests[i], min_test) == 0)
101 {
102 baseline = tests[i]->stats.min_ticks;
103
104 x = i; y = 0;
105 cairo_matrix_transform_point (m, &x, &y);
106 x = floor (x);
107 y = floor (y);
108 cairo_move_to (cr, x, y);
109 last_y = y;
110 break;
111 }
112 }
113
114 for (++i; i < num_reports; i++) {
115 if (tests[i]->name &&
116 test_report_cmp_backend_then_name (tests[i], min_test) == 0)
117 {
118 x = i, y = tests[i]->stats.min_ticks / baseline;
119
120 if (y < 1.)
121 y = -1./y + 1;
122 else
123 y -= 1;
124
125 cairo_matrix_transform_point (m, &x, &y);
126 x = floor (x);
127 y = floor (y);
128 cairo_line_to (cr, x, last_y);
129 cairo_line_to (cr, x, y);
130 last_y = y;
131 }
132 }
133 {
134 x = num_reports, y = 0;
135 cairo_matrix_transform_point (m, &x, &y);
136 x = floor (x);
137 cairo_line_to (cr, x, last_y);
138 }
139
140 cairo_set_line_width (cr, 1.);
141 cairo_stroke (cr);
142
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)
147 {
148 baseline = tests[i]->stats.min_ticks;
149
150 x = i; y = 0;
151 cairo_matrix_transform_point (m, &x, &y);
152 x = floor (x);
153 y = floor (y);
154 cairo_move_to (cr, x, y);
155 cairo_close_path (cr);
156 last_y = y;
157
158 tests[i]++;
159 break;
160 }
161 }
162
163 for (++i; i < num_reports; i++) {
164 if (tests[i]->name &&
165 test_report_cmp_backend_then_name (tests[i], min_test) == 0)
166 {
167 x = i, y = tests[i]->stats.min_ticks / baseline;
168
169 if (y < 1.)
170 y = -1./y + 1;
171 else
172 y -= 1;
173
174 cairo_matrix_transform_point (m, &x, &y);
175 x = floor (x);
176 y = floor (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);
181 last_y = y;
182
183 tests[i]++;
184 }
185 }
186 {
187 x = num_reports, y = 0;
188 cairo_matrix_transform_point (m, &x, &y);
189 x = floor (x);
190 cairo_move_to (cr, x, last_y);
191 cairo_close_path (cr);
192 }
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);
197 cairo_stroke (cr);
198 cairo_restore (cr);
199
200 cases++;
201 }
202 free (tests);
203 }
204
205 static void
draw_hline(cairo_t * cr,const cairo_matrix_t * m,double y0,double xmin,double xmax)206 draw_hline (cairo_t *cr,
207 const cairo_matrix_t *m,
208 double y0,
209 double xmin,
210 double xmax)
211 {
212 double x, y;
213 double py_offset;
214
215 py_offset = fmod (cairo_get_line_width (cr) / 2., 1.);
216
217 x = xmin; y = y0;
218 cairo_matrix_transform_point (m, &x, &y);
219 cairo_move_to (cr, floor (x), floor (y) + py_offset);
220
221 x = xmax; y = y0;
222 cairo_matrix_transform_point (m, &x, &y);
223 cairo_line_to (cr, ceil (x), floor (y) + py_offset);
224
225 cairo_stroke (cr);
226 }
227
228 static void
draw_label(cairo_t * cr,const cairo_matrix_t * m,double y0,double xmin,double xmax)229 draw_label (cairo_t *cr,
230 const cairo_matrix_t *m,
231 double y0,
232 double xmin,
233 double xmax)
234 {
235 double x, y;
236 char buf[80];
237 cairo_text_extents_t extents;
238
239 snprintf (buf, sizeof (buf), "%.0fx", fabs (y0));
240 cairo_text_extents (cr, buf, &extents);
241
242 x = xmin; y = y0;
243 cairo_matrix_transform_point (m, &x, &y);
244 cairo_move_to (cr,
245 x - extents.width - 4,
246 y - (extents.height/2. + extents.y_bearing));
247 cairo_show_text (cr, buf);
248
249
250 snprintf (buf, sizeof (buf), "%.0fx", fabs (y0));
251 cairo_text_extents (cr, buf, &extents);
252
253 x = xmax; y = y0;
254 cairo_matrix_transform_point (m, &x, &y);
255 cairo_move_to (cr,
256 x + 4,
257 y - (extents.height/2. + extents.y_bearing));
258 cairo_show_text (cr, buf);
259 }
260
261 #define ALIGN_X(v) ((v)<<0)
262 #define ALIGN_Y(v) ((v)<<2)
263 static void
draw_rotated_label(cairo_t * cr,const char * text,double x,double y,double angle,int align)264 draw_rotated_label (cairo_t *cr,
265 const char *text,
266 double x,
267 double y,
268 double angle,
269 int align)
270 {
271 cairo_text_extents_t extents;
272
273 cairo_text_extents (cr, text, &extents);
274
275 cairo_save (cr); {
276 cairo_translate (cr, x, y);
277 cairo_rotate (cr, angle);
278 switch (align) {
279 case ALIGN_X(0) | ALIGN_Y(0):
280 cairo_move_to (cr,
281 -extents.x_bearing,
282 -extents.y_bearing);
283 break;
284 case ALIGN_X(0) | ALIGN_Y(1):
285 cairo_move_to (cr,
286 -extents.x_bearing,
287 - (extents.height/2. + extents.y_bearing));
288 break;
289 case ALIGN_X(0) | ALIGN_Y(2):
290 cairo_move_to (cr,
291 -extents.x_bearing,
292 - (extents.height + extents.y_bearing));
293 break;
294
295 case ALIGN_X(1) | ALIGN_Y(0):
296 cairo_move_to (cr,
297 - (extents.width/2. + extents.x_bearing),
298 -extents.y_bearing);
299 break;
300 case ALIGN_X(1) | ALIGN_Y(1):
301 cairo_move_to (cr,
302 - (extents.width/2. + extents.x_bearing),
303 - (extents.height/2. + extents.y_bearing));
304 break;
305 case ALIGN_X(1) | ALIGN_Y(2):
306 cairo_move_to (cr,
307 - (extents.width/2. + extents.x_bearing),
308 - (extents.height + extents.y_bearing));
309 break;
310
311 case ALIGN_X(2) | ALIGN_Y(0):
312 cairo_move_to (cr,
313 - (extents.width + extents.x_bearing),
314 -extents.y_bearing);
315 break;
316 case ALIGN_X(2) | ALIGN_Y(1):
317 cairo_move_to (cr,
318 - (extents.width + extents.x_bearing),
319 - (extents.height/2. + extents.y_bearing));
320 break;
321 case ALIGN_X(2) | ALIGN_Y(2):
322 cairo_move_to (cr,
323 - (extents.width + extents.x_bearing),
324 - (extents.height + extents.y_bearing));
325 break;
326 }
327 cairo_show_text (cr, text);
328 } cairo_restore (cr);
329 }
330
331 #define PAD 36
332 static void
graph_view_draw(GraphView * self,cairo_t * cr)333 graph_view_draw (GraphView *self,
334 cairo_t *cr)
335 {
336 cairo_matrix_t m;
337 const double dash[2] = {4, 4};
338 double range;
339 int i;
340
341 if (self->widget.allocation.width < 4 *PAD)
342 return;
343 if (self->widget.allocation.height < 3 *PAD)
344 return;
345
346 range = floor (self->ymax+1) - ceil (self->ymin-1);
347
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));
353
354 if (self->selected_report != -1) {
355 cairo_save (cr); {
356 double x0, x1, y;
357 x0 = self->selected_report; y = 0;
358 cairo_matrix_transform_point (&m, &x0, &y);
359 x0 = floor (x0);
360 x1 = self->selected_report + 1; y = 0;
361 cairo_matrix_transform_point (&m, &x1, &y);
362 x1 = ceil (x1);
363 y = (x1 - x0) / 8;
364 y = MIN (y, PAD / 2);
365 x0 -= y;
366 x1 += y;
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]);
369 cairo_fill (cr);
370 } cairo_restore (cr);
371 }
372
373 cairo_save (cr); {
374 cairo_pattern_t *linear;
375 double x, y;
376
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);
381
382 cairo_set_line_width (cr, 1.);
383 cairo_set_dash (cr, NULL, 0, 0);
384
385 for (i = ceil (self->ymin-1); i <= floor (self->ymax+1); i++) {
386 if (i != 0)
387 draw_hline (cr, &m, i, 0, self->num_reports);
388 }
389
390 cairo_set_font_size (cr, 11);
391
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);
397
398 for (i = ceil (self->ymin-1); i <= floor (self->ymax+1); i++) {
399 if (i != 0)
400 draw_label (cr, &m, i, 0, self->num_reports);
401 }
402
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,
406 270./360 * 2 * G_PI,
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,
411 270./360 * 2 * G_PI,
412 ALIGN_X(2) | ALIGN_Y(1));
413
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,
417 90./360 * 2 * G_PI,
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,
422 90./360 * 2 * G_PI,
423 ALIGN_X(2) | ALIGN_Y(1));
424 } cairo_restore (cr);
425
426 draw_baseline_performance (self->cases,
427 self->reports, self->num_reports,
428 cr, &m);
429
430 cairo_save (cr); {
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);
436 }
437
438 static gboolean
graph_view_expose(GtkWidget * w,GdkEventExpose * ev)439 graph_view_expose (GtkWidget *w,
440 GdkEventExpose *ev)
441 {
442 GraphView *self = (GraphView *) w;
443 cairo_t *cr;
444
445 cr = gdk_cairo_create (w->window);
446 gdk_cairo_set_source_color (cr, &w->style->base[GTK_WIDGET_STATE (w)]);
447 cairo_paint (cr);
448
449 graph_view_draw (self, cr);
450
451 cairo_destroy (cr);
452
453 return FALSE;
454 }
455
456 static gboolean
graph_view_button_press(GtkWidget * w,GdkEventButton * ev)457 graph_view_button_press (GtkWidget *w,
458 GdkEventButton *ev)
459 {
460 GraphView *self = (GraphView *) w;
461 cairo_matrix_t m;
462 double x,y;
463 int i;
464
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);
469
470 x = ev->x;
471 y = ev->y;
472 cairo_matrix_transform_point (&m, &x, &y);
473
474 i = floor (x);
475 if (i < 0 || i >= self->num_reports)
476 i = -1;
477
478 if (i != self->selected_report) {
479 self->selected_report = i;
480 gtk_widget_queue_draw (w);
481
482 g_signal_emit (w, signals[REPORT_SELECTED], 0, i);
483 }
484
485 return FALSE;
486 }
487
488 static gboolean
graph_view_button_release(GtkWidget * w,GdkEventButton * ev)489 graph_view_button_release (GtkWidget *w,
490 GdkEventButton *ev)
491 {
492 GraphView *self = (GraphView *) w;
493
494 return FALSE;
495 }
496
497 static void
graph_view_realize(GtkWidget * widget)498 graph_view_realize (GtkWidget *widget)
499 {
500 GdkWindowAttr attributes;
501
502 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
503
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 |
515 GDK_EXPOSURE_MASK;
516
517 widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
518 &attributes,
519 GDK_WA_X | GDK_WA_Y |
520 GDK_WA_VISUAL | GDK_WA_COLORMAP);
521 gdk_window_set_user_data (widget->window, widget);
522
523 widget->style = gtk_style_attach (widget->style, widget->window);
524 gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
525 }
526
527 static void
graph_view_finalize(GObject * obj)528 graph_view_finalize (GObject *obj)
529 {
530 G_OBJECT_CLASS (graph_view_parent_class)->finalize (obj);
531 }
532
533 static void
graph_view_class_init(GraphViewClass * klass)534 graph_view_class_init (GraphViewClass *klass)
535 {
536 GObjectClass *object_class = (GObjectClass *) klass;
537 GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
538
539 object_class->finalize = graph_view_finalize;
540
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;
545
546 signals[REPORT_SELECTED] =
547 g_signal_new ("report-selected",
548 G_TYPE_FROM_CLASS (object_class),
549 G_SIGNAL_RUN_FIRST,
550 0,//G_STRUCT_OFFSET (GraphView, report_selected),
551 NULL, NULL,
552 g_cclosure_marshal_VOID__INT,
553 G_TYPE_NONE, 1, G_TYPE_INT);
554 }
555
556 static void
graph_view_init(GraphView * self)557 graph_view_init (GraphView *self)
558 {
559 self->selected_report = -1;
560 }
561
562 GtkWidget *
graph_view_new(void)563 graph_view_new (void)
564 {
565 return g_object_new (graph_view_get_type (), NULL);
566 }
567
568 void
graph_view_update_visible(GraphView * gv)569 graph_view_update_visible (GraphView *gv)
570 {
571 double min, max;
572 test_case_t *cases;
573
574 cases = gv->cases;
575
576 min = max = 1.;
577 while (cases->name != NULL) {
578 if (cases->shown) {
579 if (cases->min < min)
580 min = cases->min;
581 if (cases->max > max)
582 max = cases->max;
583 }
584 cases++;
585 }
586 gv->ymin = -1/min + 1;
587 gv->ymax = max - 1;
588
589 gtk_widget_queue_draw (&gv->widget);
590 }
591
592 void
graph_view_set_reports(GraphView * gv,test_case_t * cases,cairo_perf_report_t * reports,int num_reports)593 graph_view_set_reports (GraphView *gv,
594 test_case_t *cases,
595 cairo_perf_report_t *reports,
596 int num_reports)
597 {
598 /* XXX ownership? */
599 gv->cases = cases;
600 gv->reports = reports;
601 gv->num_reports = num_reports;
602
603 graph_view_update_visible (gv);
604 }
605