41311f7eed0bcbef9d88e58a12e1fac9514d0805
[framework/graphics/cairo.git] / perf / cairo-perf-graph-widget.c
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
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
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
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
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
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
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
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
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
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
528 graph_view_finalize (GObject *obj)
529 {
530     G_OBJECT_CLASS (graph_view_parent_class)->finalize (obj);
531 }
532
533 static void
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
557 graph_view_init (GraphView *self)
558 {
559     self->selected_report = -1;
560 }
561
562 GtkWidget *
563 graph_view_new (void)
564 {
565     return g_object_new (graph_view_get_type (), NULL);
566 }
567
568 void
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
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 }