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
#define GLIB_DISABLE_DEPRECATION_WARNINGS
29

            
30
#include "cairo-perf.h"
31
#include "cairo-perf-graph.h"
32

            
33
#include <gtk/gtk.h>
34

            
35
struct _GraphView {
36
    GtkWidget widget;
37

            
38
    test_case_t *cases;
39
    cairo_perf_report_t *reports;
40
    int num_reports;
41
    double ymin, ymax;
42

            
43
    int selected_report;
44
};
45

            
46
typedef struct _GraphViewClass {
47
    GtkWidgetClass parent_class;
48
} GraphViewClass;
49

            
50
static GType graph_view_get_type (void);
51

            
52
enum {
53
    REPORT_SELECTED,
54
    LAST_SIGNAL
55
};
56

            
57
static guint signals[LAST_SIGNAL];
58

            
59
G_DEFINE_TYPE (GraphView, graph_view, GTK_TYPE_WIDGET)
60

            
61
static void
62
draw_baseline_performance (test_case_t		*cases,
63
			   cairo_perf_report_t	*reports,
64
			   int			 num_reports,
65
			   cairo_t		*cr,
66
			   const cairo_matrix_t *m)
67
{
68
    test_report_t **tests;
69
    double dots[2] = { 0, 1.};
70
    int i;
71

            
72
    tests = xmalloc (num_reports * sizeof (test_report_t *));
73
    for (i = 0; i < num_reports; i++)
74
	tests[i] = reports[i].tests;
75

            
76
    while (cases->backend != NULL) {
77
	test_report_t *min_test;
78
	double baseline, last_y;
79
	double x, y;
80

            
81
	if (! cases->shown) {
82
	    cases++;
83
	    continue;
84
	}
85

            
86
	min_test = cases->min_test;
87

            
88
	for (i = 0; i < num_reports; i++) {
89
	    while (tests[i]->name &&
90
		test_report_cmp_backend_then_name (tests[i], min_test) < 0)
91
	    {
92
		tests[i]++;
93
	    }
94
	}
95

            
96
	/* first the stroke */
97
	cairo_save (cr);
98
	cairo_set_line_width (cr, 2.);
99
	gdk_cairo_set_source_color (cr, &cases->color);
100
	for (i = 0; i < num_reports; i++) {
101
	    if (tests[i]->name &&
102
		test_report_cmp_backend_then_name (tests[i], min_test) == 0)
103
	    {
104
		baseline = tests[i]->stats.min_ticks;
105

            
106
		x = i; y = 0;
107
		cairo_matrix_transform_point (m, &x, &y);
108
		x = floor (x);
109
		y = floor (y);
110
		cairo_move_to (cr, x, y);
111
		last_y = y;
112
		break;
113
	    }
114
	}
115

            
116
	for (++i; i < num_reports; i++) {
117
	    if (tests[i]->name &&
118
		test_report_cmp_backend_then_name (tests[i], min_test) == 0)
119
	    {
120
		x = i, y = tests[i]->stats.min_ticks / baseline;
121

            
122
		if (y < 1.)
123
		    y = -1./y + 1;
124
		else
125
		    y -= 1;
126

            
127
		cairo_matrix_transform_point (m, &x, &y);
128
		x = floor (x);
129
		y = floor (y);
130
		cairo_line_to (cr, x, last_y);
131
		cairo_line_to (cr, x, y);
132
		last_y = y;
133
	    }
134
	}
135
	{
136
	    x = num_reports, y = 0;
137
	    cairo_matrix_transform_point (m, &x, &y);
138
	    x = floor (x);
139
	    cairo_line_to (cr, x, last_y);
140
	}
141

            
142
	cairo_set_line_width (cr, 1.);
143
	cairo_stroke (cr);
144

            
145
	/* then draw the points */
146
	for (i = 0; i < num_reports; i++) {
147
	    if (tests[i]->name &&
148
		test_report_cmp_backend_then_name (tests[i], min_test) == 0)
149
	    {
150
		baseline = tests[i]->stats.min_ticks;
151

            
152
		x = i; y = 0;
153
		cairo_matrix_transform_point (m, &x, &y);
154
		x = floor (x);
155
		y = floor (y);
156
		cairo_move_to (cr, x, y);
157
		cairo_close_path (cr);
158
		last_y = y;
159

            
160
		tests[i]++;
161
		break;
162
	    }
163
	}
164

            
165
	for (++i; i < num_reports; i++) {
166
	    if (tests[i]->name &&
167
		test_report_cmp_backend_then_name (tests[i], min_test) == 0)
168
	    {
169
		x = i, y = tests[i]->stats.min_ticks / baseline;
170

            
171
		if (y < 1.)
172
		    y = -1./y + 1;
173
		else
174
		    y -= 1;
175

            
176
		cairo_matrix_transform_point (m, &x, &y);
177
		x = floor (x);
178
		y = floor (y);
179
		cairo_move_to (cr, x, last_y);
180
		cairo_close_path (cr);
181
		cairo_move_to (cr, x, y);
182
		cairo_close_path (cr);
183
		last_y = y;
184

            
185
		tests[i]++;
186
	    }
187
	}
188
	{
189
	    x = num_reports, y = 0;
190
	    cairo_matrix_transform_point (m, &x, &y);
191
	    x = floor (x);
192
	    cairo_move_to (cr, x, last_y);
193
	    cairo_close_path (cr);
194
	}
195
	cairo_set_source_rgba (cr, 0, 0, 0, .5);
196
	cairo_set_dash (cr, dots, 2, 0.);
197
	cairo_set_line_width (cr, 3.);
198
	cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
199
	cairo_stroke (cr);
200
	cairo_restore (cr);
201

            
202
	cases++;
203
    }
204
    free (tests);
205
}
206

            
207
static void
208
draw_hline (cairo_t		 *cr,
209
	    const cairo_matrix_t *m,
210
	    double		  y0,
211
	    double		  xmin,
212
	    double		  xmax)
213
{
214
    double x, y;
215
    double py_offset;
216

            
217
    py_offset = fmod (cairo_get_line_width (cr) / 2., 1.);
218

            
219
    x = xmin; y = y0;
220
    cairo_matrix_transform_point (m, &x, &y);
221
    cairo_move_to (cr, floor (x), floor (y) + py_offset);
222

            
223
    x = xmax; y = y0;
224
    cairo_matrix_transform_point (m, &x, &y);
225
    cairo_line_to (cr, ceil (x), floor (y) + py_offset);
226

            
227
    cairo_stroke (cr);
228
}
229

            
230
static void
231
draw_label (cairo_t		 *cr,
232
	    const cairo_matrix_t *m,
233
	    double		  y0,
234
	    double		  xmin,
235
	    double		  xmax)
236
{
237
    double x, y;
238
    char buf[80];
239
    cairo_text_extents_t extents;
240

            
241
    snprintf (buf, sizeof (buf), "%.0fx", fabs (y0));
242
    cairo_text_extents (cr, buf, &extents);
243

            
244
    x = xmin; y = y0;
245
    cairo_matrix_transform_point (m, &x, &y);
246
    cairo_move_to (cr,
247
		   x - extents.width - 4,
248
		   y - (extents.height/2. + extents.y_bearing));
249
    cairo_show_text (cr, buf);
250

            
251

            
252
    snprintf (buf, sizeof (buf), "%.0fx", fabs (y0));
253
    cairo_text_extents (cr, buf, &extents);
254

            
255
    x = xmax; y = y0;
256
    cairo_matrix_transform_point (m, &x, &y);
257
    cairo_move_to (cr,
258
		   x + 4,
259
		   y - (extents.height/2. + extents.y_bearing));
260
    cairo_show_text (cr, buf);
261
}
262

            
263
#define ALIGN_X(v) ((v)<<0)
264
#define ALIGN_Y(v) ((v)<<2)
265
static void
266
draw_rotated_label (cairo_t    *cr,
267
		    const char *text,
268
		    double	x,
269
		    double	y,
270
		    double	angle,
271
		    int 	align)
272
{
273
    cairo_text_extents_t extents;
274

            
275
    cairo_text_extents (cr, text, &extents);
276

            
277
    cairo_save (cr); {
278
	cairo_translate (cr, x, y);
279
	cairo_rotate (cr, angle);
280
	switch (align) {
281
	case ALIGN_X(0) | ALIGN_Y(0):
282
	    cairo_move_to (cr,
283
			   -extents.x_bearing,
284
			   -extents.y_bearing);
285
	    break;
286
	case ALIGN_X(0) | ALIGN_Y(1):
287
	    cairo_move_to (cr,
288
			   -extents.x_bearing,
289
			   - (extents.height/2. + extents.y_bearing));
290
	    break;
291
	case ALIGN_X(0) | ALIGN_Y(2):
292
	    cairo_move_to (cr,
293
			   -extents.x_bearing,
294
			   - (extents.height + extents.y_bearing));
295
	    break;
296

            
297
	case ALIGN_X(1) | ALIGN_Y(0):
298
	    cairo_move_to (cr,
299
			   - (extents.width/2. + extents.x_bearing),
300
			   -extents.y_bearing);
301
	    break;
302
	case ALIGN_X(1) | ALIGN_Y(1):
303
	    cairo_move_to (cr,
304
			   - (extents.width/2. + extents.x_bearing),
305
			   - (extents.height/2. + extents.y_bearing));
306
	    break;
307
	case ALIGN_X(1) | ALIGN_Y(2):
308
	    cairo_move_to (cr,
309
			   - (extents.width/2. + extents.x_bearing),
310
			   - (extents.height + extents.y_bearing));
311
	    break;
312

            
313
	case ALIGN_X(2) | ALIGN_Y(0):
314
	    cairo_move_to (cr,
315
			   - (extents.width + extents.x_bearing),
316
			   -extents.y_bearing);
317
	    break;
318
	case ALIGN_X(2) | ALIGN_Y(1):
319
	    cairo_move_to (cr,
320
			   - (extents.width + extents.x_bearing),
321
			   - (extents.height/2. + extents.y_bearing));
322
	    break;
323
	case ALIGN_X(2) | ALIGN_Y(2):
324
	    cairo_move_to (cr,
325
			   - (extents.width + extents.x_bearing),
326
			   - (extents.height + extents.y_bearing));
327
	    break;
328
	}
329
	cairo_show_text (cr, text);
330
    } cairo_restore (cr);
331
}
332

            
333
#define PAD 36
334
static void
335
graph_view_draw (GraphView *self,
336
		 cairo_t   *cr)
337
{
338
    cairo_matrix_t m;
339
    const double dash[2] = {4, 4};
340
    double range;
341
    int i;
342

            
343
    if (self->widget.allocation.width < 4 *PAD)
344
	return;
345
    if (self->widget.allocation.height < 3 *PAD)
346
	return;
347

            
348
    range = floor (self->ymax+1) - ceil (self->ymin-1);
349

            
350
    cairo_matrix_init_translate (&m, PAD, self->widget.allocation.height - PAD);
351
    cairo_matrix_scale (&m,
352
			(self->widget.allocation.width-2*PAD)/(self->num_reports),
353
			-(self->widget.allocation.height-2*PAD)/range);
354
    cairo_matrix_translate (&m, 0,   floor (self->ymax+1));
355

            
356
    if (self->selected_report != -1) {
357
	cairo_save (cr); {
358
	    double x0, x1, y;
359
	    x0 = self->selected_report; y = 0;
360
	    cairo_matrix_transform_point (&m, &x0, &y);
361
	    x0 = floor (x0);
362
	    x1 = self->selected_report + 1; y = 0;
363
	    cairo_matrix_transform_point (&m, &x1, &y);
364
	    x1 = ceil (x1);
365
	    y = (x1 - x0) / 8;
366
	    y = MIN (y, PAD / 2);
367
	    x0 -= y;
368
	    x1 += y;
369
	    cairo_rectangle (cr, x0, PAD/2, x1-x0, self->widget.allocation.height-2*PAD + PAD);
370
	    gdk_cairo_set_source_color (cr, &self->widget.style->base[GTK_STATE_SELECTED]);
371
	    cairo_fill (cr);
372
	} cairo_restore (cr);
373
    }
374

            
375
    cairo_save (cr); {
376
	cairo_pattern_t *linear;
377
	double x, y;
378

            
379
	gdk_cairo_set_source_color (cr,
380
				    &self->widget.style->fg[GTK_WIDGET_STATE (self)]);
381
	cairo_set_line_width (cr, 2.);
382
	draw_hline (cr, &m, 0, 0, self->num_reports);
383

            
384
	cairo_set_line_width (cr, 1.);
385
	cairo_set_dash (cr, NULL, 0, 0);
386

            
387
	for (i = ceil (self->ymin-1); i <= floor (self->ymax+1); i++) {
388
	    if (i != 0)
389
		draw_hline (cr, &m, i, 0, self->num_reports);
390
	}
391

            
392
	cairo_set_font_size (cr, 11);
393

            
394
	linear = cairo_pattern_create_linear (0, PAD, 0, self->widget.allocation.height-2*PAD);
395
	cairo_pattern_add_color_stop_rgb (linear, 0, 0, 1, 0);
396
	cairo_pattern_add_color_stop_rgb (linear, 1, 1, 0, 0);
397
	cairo_set_source (cr, linear);
398
	cairo_pattern_destroy (linear);
399

            
400
	for (i = ceil (self->ymin-1); i <= floor (self->ymax+1); i++) {
401
	    if (i != 0)
402
		draw_label (cr, &m, i, 0, self->num_reports);
403
	}
404

            
405
	x = 0, y = floor (self->ymax+1);
406
	cairo_matrix_transform_point (&m, &x, &y);
407
	draw_rotated_label (cr, "Faster", x - 7, y + 14,
408
			    270./360 * 2 * G_PI,
409
			    ALIGN_X(2) | ALIGN_Y(1));
410
	x = self->num_reports, y = floor (self->ymax+1);
411
	cairo_matrix_transform_point (&m, &x, &y);
412
	draw_rotated_label (cr, "Faster", x + 11, y + 14,
413
			    270./360 * 2 * G_PI,
414
			    ALIGN_X(2) | ALIGN_Y(1));
415

            
416
	x = 0, y = ceil (self->ymin-1);
417
	cairo_matrix_transform_point (&m, &x, &y);
418
	draw_rotated_label (cr, "Slower", x - 7, y - 14,
419
			    90./360 * 2 * G_PI,
420
			    ALIGN_X(2) | ALIGN_Y(1));
421
	x = self->num_reports, y = ceil (self->ymin-1);
422
	cairo_matrix_transform_point (&m, &x, &y);
423
	draw_rotated_label (cr, "Slower", x + 11, y - 14,
424
			    90./360 * 2 * G_PI,
425
			    ALIGN_X(2) | ALIGN_Y(1));
426
    } cairo_restore (cr);
427

            
428
    draw_baseline_performance (self->cases,
429
			       self->reports, self->num_reports,
430
			       cr, &m);
431

            
432
    cairo_save (cr); {
433
	cairo_set_source_rgb (cr, 0.7, 0.7, 0.7);
434
	cairo_set_line_width (cr, 1.);
435
	cairo_set_dash (cr, dash, 2, 0);
436
	draw_hline (cr, &m, 0, 0, self->num_reports);
437
    } cairo_restore (cr);
438
}
439

            
440
static gboolean
441
graph_view_expose (GtkWidget	  *w,
442
		   GdkEventExpose *ev)
443
{
444
    GraphView *self = (GraphView *) w;
445
    cairo_t *cr;
446

            
447
    cr = gdk_cairo_create (w->window);
448
    gdk_cairo_set_source_color (cr, &w->style->base[GTK_WIDGET_STATE (w)]);
449
    cairo_paint (cr);
450

            
451
    graph_view_draw (self, cr);
452

            
453
    cairo_destroy (cr);
454

            
455
    return FALSE;
456
}
457

            
458
static gboolean
459
graph_view_button_press (GtkWidget	*w,
460
			 GdkEventButton *ev)
461
{
462
    GraphView *self = (GraphView *) w;
463
    cairo_matrix_t m;
464
    double x,y;
465
    int i;
466

            
467
    cairo_matrix_init_translate (&m, PAD, self->widget.allocation.height-PAD);
468
    cairo_matrix_scale (&m, (self->widget.allocation.width-2*PAD)/self->num_reports, -(self->widget.allocation.height-2*PAD)/(self->ymax - self->ymin));
469
    cairo_matrix_translate (&m, 0, -self->ymin);
470
    cairo_matrix_invert (&m);
471

            
472
    x = ev->x;
473
    y = ev->y;
474
    cairo_matrix_transform_point (&m, &x, &y);
475

            
476
    i = floor (x);
477
    if (i < 0 || i >= self->num_reports)
478
	i = -1;
479

            
480
    if (i != self->selected_report) {
481
	self->selected_report = i;
482
	gtk_widget_queue_draw (w);
483

            
484
	g_signal_emit (w, signals[REPORT_SELECTED], 0, i);
485
    }
486

            
487
    return FALSE;
488
}
489

            
490
static gboolean
491
graph_view_button_release (GtkWidget	  *w,
492
			   GdkEventButton *ev)
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
}