summaryrefslogtreecommitdiff
path: root/perf/cairo-perf-graph-widget.c
diff options
context:
space:
mode:
Diffstat (limited to 'perf/cairo-perf-graph-widget.c')
-rw-r--r--perf/cairo-perf-graph-widget.c604
1 files changed, 604 insertions, 0 deletions
diff --git a/perf/cairo-perf-graph-widget.c b/perf/cairo-perf-graph-widget.c
new file mode 100644
index 000000000..41311f7ee
--- /dev/null
+++ b/perf/cairo-perf-graph-widget.c
@@ -0,0 +1,604 @@
+/*
+ * Copyright © 2008 Chris Wilson
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of the
+ * copyright holders not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no
+ * representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ *
+ * Authors: Chris Wilson <chris@chris-wilson.co.uk>
+ */
+
+#include "cairo-perf.h"
+#include "cairo-perf-graph.h"
+
+#include <gtk/gtk.h>
+
+struct _GraphView {
+ GtkWidget widget;
+
+ test_case_t *cases;
+ cairo_perf_report_t *reports;
+ int num_reports;
+ double ymin, ymax;
+
+ int selected_report;
+};
+
+typedef struct _GraphViewClass {
+ GtkWidgetClass parent_class;
+} GraphViewClass;
+
+static GType graph_view_get_type (void);
+
+enum {
+ REPORT_SELECTED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (GraphView, graph_view, GTK_TYPE_WIDGET)
+
+static void
+draw_baseline_performance (test_case_t *cases,
+ cairo_perf_report_t *reports,
+ int num_reports,
+ cairo_t *cr,
+ const cairo_matrix_t *m)
+{
+ test_report_t **tests;
+ double dots[2] = { 0, 1.};
+ int i;
+
+ tests = xmalloc (num_reports * sizeof (test_report_t *));
+ for (i = 0; i < num_reports; i++)
+ tests[i] = reports[i].tests;
+
+ while (cases->backend != NULL) {
+ test_report_t *min_test;
+ double baseline, last_y;
+ double x, y;
+
+ if (! cases->shown) {
+ cases++;
+ continue;
+ }
+
+ min_test = cases->min_test;
+
+ for (i = 0; i < num_reports; i++) {
+ while (tests[i]->name &&
+ test_report_cmp_backend_then_name (tests[i], min_test) < 0)
+ {
+ tests[i]++;
+ }
+ }
+
+ /* first the stroke */
+ cairo_save (cr);
+ cairo_set_line_width (cr, 2.);
+ gdk_cairo_set_source_color (cr, &cases->color);
+ for (i = 0; i < num_reports; i++) {
+ if (tests[i]->name &&
+ test_report_cmp_backend_then_name (tests[i], min_test) == 0)
+ {
+ baseline = tests[i]->stats.min_ticks;
+
+ x = i; y = 0;
+ cairo_matrix_transform_point (m, &x, &y);
+ x = floor (x);
+ y = floor (y);
+ cairo_move_to (cr, x, y);
+ last_y = y;
+ break;
+ }
+ }
+
+ for (++i; i < num_reports; i++) {
+ if (tests[i]->name &&
+ test_report_cmp_backend_then_name (tests[i], min_test) == 0)
+ {
+ x = i, y = tests[i]->stats.min_ticks / baseline;
+
+ if (y < 1.)
+ y = -1./y + 1;
+ else
+ y -= 1;
+
+ cairo_matrix_transform_point (m, &x, &y);
+ x = floor (x);
+ y = floor (y);
+ cairo_line_to (cr, x, last_y);
+ cairo_line_to (cr, x, y);
+ last_y = y;
+ }
+ }
+ {
+ x = num_reports, y = 0;
+ cairo_matrix_transform_point (m, &x, &y);
+ x = floor (x);
+ cairo_line_to (cr, x, last_y);
+ }
+
+ cairo_set_line_width (cr, 1.);
+ cairo_stroke (cr);
+
+ /* then draw the points */
+ for (i = 0; i < num_reports; i++) {
+ if (tests[i]->name &&
+ test_report_cmp_backend_then_name (tests[i], min_test) == 0)
+ {
+ baseline = tests[i]->stats.min_ticks;
+
+ x = i; y = 0;
+ cairo_matrix_transform_point (m, &x, &y);
+ x = floor (x);
+ y = floor (y);
+ cairo_move_to (cr, x, y);
+ cairo_close_path (cr);
+ last_y = y;
+
+ tests[i]++;
+ break;
+ }
+ }
+
+ for (++i; i < num_reports; i++) {
+ if (tests[i]->name &&
+ test_report_cmp_backend_then_name (tests[i], min_test) == 0)
+ {
+ x = i, y = tests[i]->stats.min_ticks / baseline;
+
+ if (y < 1.)
+ y = -1./y + 1;
+ else
+ y -= 1;
+
+ cairo_matrix_transform_point (m, &x, &y);
+ x = floor (x);
+ y = floor (y);
+ cairo_move_to (cr, x, last_y);
+ cairo_close_path (cr);
+ cairo_move_to (cr, x, y);
+ cairo_close_path (cr);
+ last_y = y;
+
+ tests[i]++;
+ }
+ }
+ {
+ x = num_reports, y = 0;
+ cairo_matrix_transform_point (m, &x, &y);
+ x = floor (x);
+ cairo_move_to (cr, x, last_y);
+ cairo_close_path (cr);
+ }
+ cairo_set_source_rgba (cr, 0, 0, 0, .5);
+ cairo_set_dash (cr, dots, 2, 0.);
+ cairo_set_line_width (cr, 3.);
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
+ cairo_stroke (cr);
+ cairo_restore (cr);
+
+ cases++;
+ }
+ free (tests);
+}
+
+static void
+draw_hline (cairo_t *cr,
+ const cairo_matrix_t *m,
+ double y0,
+ double xmin,
+ double xmax)
+{
+ double x, y;
+ double py_offset;
+
+ py_offset = fmod (cairo_get_line_width (cr) / 2., 1.);
+
+ x = xmin; y = y0;
+ cairo_matrix_transform_point (m, &x, &y);
+ cairo_move_to (cr, floor (x), floor (y) + py_offset);
+
+ x = xmax; y = y0;
+ cairo_matrix_transform_point (m, &x, &y);
+ cairo_line_to (cr, ceil (x), floor (y) + py_offset);
+
+ cairo_stroke (cr);
+}
+
+static void
+draw_label (cairo_t *cr,
+ const cairo_matrix_t *m,
+ double y0,
+ double xmin,
+ double xmax)
+{
+ double x, y;
+ char buf[80];
+ cairo_text_extents_t extents;
+
+ snprintf (buf, sizeof (buf), "%.0fx", fabs (y0));
+ cairo_text_extents (cr, buf, &extents);
+
+ x = xmin; y = y0;
+ cairo_matrix_transform_point (m, &x, &y);
+ cairo_move_to (cr,
+ x - extents.width - 4,
+ y - (extents.height/2. + extents.y_bearing));
+ cairo_show_text (cr, buf);
+
+
+ snprintf (buf, sizeof (buf), "%.0fx", fabs (y0));
+ cairo_text_extents (cr, buf, &extents);
+
+ x = xmax; y = y0;
+ cairo_matrix_transform_point (m, &x, &y);
+ cairo_move_to (cr,
+ x + 4,
+ y - (extents.height/2. + extents.y_bearing));
+ cairo_show_text (cr, buf);
+}
+
+#define ALIGN_X(v) ((v)<<0)
+#define ALIGN_Y(v) ((v)<<2)
+static void
+draw_rotated_label (cairo_t *cr,
+ const char *text,
+ double x,
+ double y,
+ double angle,
+ int align)
+{
+ cairo_text_extents_t extents;
+
+ cairo_text_extents (cr, text, &extents);
+
+ cairo_save (cr); {
+ cairo_translate (cr, x, y);
+ cairo_rotate (cr, angle);
+ switch (align) {
+ case ALIGN_X(0) | ALIGN_Y(0):
+ cairo_move_to (cr,
+ -extents.x_bearing,
+ -extents.y_bearing);
+ break;
+ case ALIGN_X(0) | ALIGN_Y(1):
+ cairo_move_to (cr,
+ -extents.x_bearing,
+ - (extents.height/2. + extents.y_bearing));
+ break;
+ case ALIGN_X(0) | ALIGN_Y(2):
+ cairo_move_to (cr,
+ -extents.x_bearing,
+ - (extents.height + extents.y_bearing));
+ break;
+
+ case ALIGN_X(1) | ALIGN_Y(0):
+ cairo_move_to (cr,
+ - (extents.width/2. + extents.x_bearing),
+ -extents.y_bearing);
+ break;
+ case ALIGN_X(1) | ALIGN_Y(1):
+ cairo_move_to (cr,
+ - (extents.width/2. + extents.x_bearing),
+ - (extents.height/2. + extents.y_bearing));
+ break;
+ case ALIGN_X(1) | ALIGN_Y(2):
+ cairo_move_to (cr,
+ - (extents.width/2. + extents.x_bearing),
+ - (extents.height + extents.y_bearing));
+ break;
+
+ case ALIGN_X(2) | ALIGN_Y(0):
+ cairo_move_to (cr,
+ - (extents.width + extents.x_bearing),
+ -extents.y_bearing);
+ break;
+ case ALIGN_X(2) | ALIGN_Y(1):
+ cairo_move_to (cr,
+ - (extents.width + extents.x_bearing),
+ - (extents.height/2. + extents.y_bearing));
+ break;
+ case ALIGN_X(2) | ALIGN_Y(2):
+ cairo_move_to (cr,
+ - (extents.width + extents.x_bearing),
+ - (extents.height + extents.y_bearing));
+ break;
+ }
+ cairo_show_text (cr, text);
+ } cairo_restore (cr);
+}
+
+#define PAD 36
+static void
+graph_view_draw (GraphView *self,
+ cairo_t *cr)
+{
+ cairo_matrix_t m;
+ const double dash[2] = {4, 4};
+ double range;
+ int i;
+
+ if (self->widget.allocation.width < 4 *PAD)
+ return;
+ if (self->widget.allocation.height < 3 *PAD)
+ return;
+
+ range = floor (self->ymax+1) - ceil (self->ymin-1);
+
+ cairo_matrix_init_translate (&m, PAD, self->widget.allocation.height - PAD);
+ cairo_matrix_scale (&m,
+ (self->widget.allocation.width-2*PAD)/(self->num_reports),
+ -(self->widget.allocation.height-2*PAD)/range);
+ cairo_matrix_translate (&m, 0, floor (self->ymax+1));
+
+ if (self->selected_report != -1) {
+ cairo_save (cr); {
+ double x0, x1, y;
+ x0 = self->selected_report; y = 0;
+ cairo_matrix_transform_point (&m, &x0, &y);
+ x0 = floor (x0);
+ x1 = self->selected_report + 1; y = 0;
+ cairo_matrix_transform_point (&m, &x1, &y);
+ x1 = ceil (x1);
+ y = (x1 - x0) / 8;
+ y = MIN (y, PAD / 2);
+ x0 -= y;
+ x1 += y;
+ cairo_rectangle (cr, x0, PAD/2, x1-x0, self->widget.allocation.height-2*PAD + PAD);
+ gdk_cairo_set_source_color (cr, &self->widget.style->base[GTK_STATE_SELECTED]);
+ cairo_fill (cr);
+ } cairo_restore (cr);
+ }
+
+ cairo_save (cr); {
+ cairo_pattern_t *linear;
+ double x, y;
+
+ gdk_cairo_set_source_color (cr,
+ &self->widget.style->fg[GTK_WIDGET_STATE (self)]);
+ cairo_set_line_width (cr, 2.);
+ draw_hline (cr, &m, 0, 0, self->num_reports);
+
+ cairo_set_line_width (cr, 1.);
+ cairo_set_dash (cr, NULL, 0, 0);
+
+ for (i = ceil (self->ymin-1); i <= floor (self->ymax+1); i++) {
+ if (i != 0)
+ draw_hline (cr, &m, i, 0, self->num_reports);
+ }
+
+ cairo_set_font_size (cr, 11);
+
+ linear = cairo_pattern_create_linear (0, PAD, 0, self->widget.allocation.height-2*PAD);
+ cairo_pattern_add_color_stop_rgb (linear, 0, 0, 1, 0);
+ cairo_pattern_add_color_stop_rgb (linear, 1, 1, 0, 0);
+ cairo_set_source (cr, linear);
+ cairo_pattern_destroy (linear);
+
+ for (i = ceil (self->ymin-1); i <= floor (self->ymax+1); i++) {
+ if (i != 0)
+ draw_label (cr, &m, i, 0, self->num_reports);
+ }
+
+ x = 0, y = floor (self->ymax+1);
+ cairo_matrix_transform_point (&m, &x, &y);
+ draw_rotated_label (cr, "Faster", x - 7, y + 14,
+ 270./360 * 2 * G_PI,
+ ALIGN_X(2) | ALIGN_Y(1));
+ x = self->num_reports, y = floor (self->ymax+1);
+ cairo_matrix_transform_point (&m, &x, &y);
+ draw_rotated_label (cr, "Faster", x + 11, y + 14,
+ 270./360 * 2 * G_PI,
+ ALIGN_X(2) | ALIGN_Y(1));
+
+ x = 0, y = ceil (self->ymin-1);
+ cairo_matrix_transform_point (&m, &x, &y);
+ draw_rotated_label (cr, "Slower", x - 7, y - 14,
+ 90./360 * 2 * G_PI,
+ ALIGN_X(2) | ALIGN_Y(1));
+ x = self->num_reports, y = ceil (self->ymin-1);
+ cairo_matrix_transform_point (&m, &x, &y);
+ draw_rotated_label (cr, "Slower", x + 11, y - 14,
+ 90./360 * 2 * G_PI,
+ ALIGN_X(2) | ALIGN_Y(1));
+ } cairo_restore (cr);
+
+ draw_baseline_performance (self->cases,
+ self->reports, self->num_reports,
+ cr, &m);
+
+ cairo_save (cr); {
+ cairo_set_source_rgb (cr, 0.7, 0.7, 0.7);
+ cairo_set_line_width (cr, 1.);
+ cairo_set_dash (cr, dash, 2, 0);
+ draw_hline (cr, &m, 0, 0, self->num_reports);
+ } cairo_restore (cr);
+}
+
+static gboolean
+graph_view_expose (GtkWidget *w,
+ GdkEventExpose *ev)
+{
+ GraphView *self = (GraphView *) w;
+ cairo_t *cr;
+
+ cr = gdk_cairo_create (w->window);
+ gdk_cairo_set_source_color (cr, &w->style->base[GTK_WIDGET_STATE (w)]);
+ cairo_paint (cr);
+
+ graph_view_draw (self, cr);
+
+ cairo_destroy (cr);
+
+ return FALSE;
+}
+
+static gboolean
+graph_view_button_press (GtkWidget *w,
+ GdkEventButton *ev)
+{
+ GraphView *self = (GraphView *) w;
+ cairo_matrix_t m;
+ double x,y;
+ int i;
+
+ cairo_matrix_init_translate (&m, PAD, self->widget.allocation.height-PAD);
+ cairo_matrix_scale (&m, (self->widget.allocation.width-2*PAD)/self->num_reports, -(self->widget.allocation.height-2*PAD)/(self->ymax - self->ymin));
+ cairo_matrix_translate (&m, 0, -self->ymin);
+ cairo_matrix_invert (&m);
+
+ x = ev->x;
+ y = ev->y;
+ cairo_matrix_transform_point (&m, &x, &y);
+
+ i = floor (x);
+ if (i < 0 || i >= self->num_reports)
+ i = -1;
+
+ if (i != self->selected_report) {
+ self->selected_report = i;
+ gtk_widget_queue_draw (w);
+
+ g_signal_emit (w, signals[REPORT_SELECTED], 0, i);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+graph_view_button_release (GtkWidget *w,
+ GdkEventButton *ev)
+{
+ GraphView *self = (GraphView *) w;
+
+ return FALSE;
+}
+
+static void
+graph_view_realize (GtkWidget *widget)
+{
+ GdkWindowAttr attributes;
+
+ GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.x = widget->allocation.x;
+ attributes.y = widget->allocation.y;
+ attributes.width = widget->allocation.width;
+ attributes.height = widget->allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual (widget);
+ attributes.colormap = gtk_widget_get_colormap (widget);
+ attributes.event_mask = gtk_widget_get_events (widget) |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_EXPOSURE_MASK;
+
+ widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
+ &attributes,
+ GDK_WA_X | GDK_WA_Y |
+ GDK_WA_VISUAL | GDK_WA_COLORMAP);
+ gdk_window_set_user_data (widget->window, widget);
+
+ widget->style = gtk_style_attach (widget->style, widget->window);
+ gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
+}
+
+static void
+graph_view_finalize (GObject *obj)
+{
+ G_OBJECT_CLASS (graph_view_parent_class)->finalize (obj);
+}
+
+static void
+graph_view_class_init (GraphViewClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
+
+ object_class->finalize = graph_view_finalize;
+
+ widget_class->realize = graph_view_realize;
+ widget_class->expose_event = graph_view_expose;
+ widget_class->button_press_event = graph_view_button_press;
+ widget_class->button_release_event = graph_view_button_release;
+
+ signals[REPORT_SELECTED] =
+ g_signal_new ("report-selected",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_FIRST,
+ 0,//G_STRUCT_OFFSET (GraphView, report_selected),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__INT,
+ G_TYPE_NONE, 1, G_TYPE_INT);
+}
+
+static void
+graph_view_init (GraphView *self)
+{
+ self->selected_report = -1;
+}
+
+GtkWidget *
+graph_view_new (void)
+{
+ return g_object_new (graph_view_get_type (), NULL);
+}
+
+void
+graph_view_update_visible (GraphView *gv)
+{
+ double min, max;
+ test_case_t *cases;
+
+ cases = gv->cases;
+
+ min = max = 1.;
+ while (cases->name != NULL) {
+ if (cases->shown) {
+ if (cases->min < min)
+ min = cases->min;
+ if (cases->max > max)
+ max = cases->max;
+ }
+ cases++;
+ }
+ gv->ymin = -1/min + 1;
+ gv->ymax = max - 1;
+
+ gtk_widget_queue_draw (&gv->widget);
+}
+
+void
+graph_view_set_reports (GraphView *gv,
+ test_case_t *cases,
+ cairo_perf_report_t *reports,
+ int num_reports)
+{
+ /* XXX ownership? */
+ gv->cases = cases;
+ gv->reports = reports;
+ gv->num_reports = num_reports;
+
+ graph_view_update_visible (gv);
+}