From patchwork Mon Jul 6 12:35:33 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: "Lespiau, Damien" X-Patchwork-Id: 6723311 Return-Path: X-Original-To: patchwork-intel-gfx@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 8AB13C05AD for ; Mon, 6 Jul 2015 12:35:57 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 9688E20628 for ; Mon, 6 Jul 2015 12:35:55 +0000 (UTC) Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) by mail.kernel.org (Postfix) with ESMTP id 6A6B120601 for ; Mon, 6 Jul 2015 12:35:53 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id EBC2C6E871; Mon, 6 Jul 2015 05:35:52 -0700 (PDT) X-Original-To: intel-gfx@lists.freedesktop.org Delivered-To: intel-gfx@lists.freedesktop.org Received: from mga02.intel.com (mga02.intel.com [134.134.136.20]) by gabe.freedesktop.org (Postfix) with ESMTP id D57336E876 for ; Mon, 6 Jul 2015 05:35:51 -0700 (PDT) Received: from fmsmga001.fm.intel.com ([10.253.24.23]) by orsmga101.jf.intel.com with ESMTP; 06 Jul 2015 05:35:52 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.15,414,1432623600"; d="scan'208";a="741421007" Received: from magarwal-mobl.amr.corp.intel.com (HELO strange.amr.corp.intel.com) ([10.254.77.111]) by fmsmga001.fm.intel.com with ESMTP; 06 Jul 2015 05:35:50 -0700 From: Damien Lespiau To: intel-gfx@lists.freedesktop.org Date: Mon, 6 Jul 2015 13:35:33 +0100 Message-Id: <1436186144-19665-6-git-send-email-damien.lespiau@intel.com> X-Mailer: git-send-email 2.1.0 In-Reply-To: <1436186144-19665-1-git-send-email-damien.lespiau@intel.com> References: <1436186144-19665-1-git-send-email-damien.lespiau@intel.com> MIME-Version: 1.0 Subject: [Intel-gfx] [PATCH i-g-t 05/16] plot: Draw nice plots! X-BeenThere: intel-gfx@lists.freedesktop.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: Intel graphics driver community testing & development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: intel-gfx-bounces@lists.freedesktop.org Sender: "Intel-gfx" X-Spam-Status: No, score=-4.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Signed-off-by: Damien Lespiau --- .../intel-gpu-tools/intel-gpu-tools-docs.xml | 1 + lib/Makefile.sources | 2 + lib/igt_plot.c | 607 +++++++++++++++++++++ lib/igt_plot.h | 122 +++++ lib/tests/.gitignore | 4 + lib/tests/Makefile.sources | 1 + lib/tests/igt_plot.c | 98 ++++ 7 files changed, 835 insertions(+) create mode 100644 lib/igt_plot.c create mode 100644 lib/igt_plot.h create mode 100644 lib/tests/igt_plot.c diff --git a/docs/reference/intel-gpu-tools/intel-gpu-tools-docs.xml b/docs/reference/intel-gpu-tools/intel-gpu-tools-docs.xml index 0992308..83f7d29 100644 --- a/docs/reference/intel-gpu-tools/intel-gpu-tools-docs.xml +++ b/docs/reference/intel-gpu-tools/intel-gpu-tools-docs.xml @@ -19,6 +19,7 @@ + diff --git a/lib/Makefile.sources b/lib/Makefile.sources index 205a9aa..9fabd44 100644 --- a/lib/Makefile.sources +++ b/lib/Makefile.sources @@ -12,6 +12,8 @@ libintel_tools_la_SOURCES = \ igt_aux.h \ igt_gt.c \ igt_gt.h \ + igt_plot.c \ + igt_plot.h \ igt_stats.c \ igt_stats.h \ igt_types.h \ diff --git a/lib/igt_plot.c b/lib/igt_plot.c new file mode 100644 index 0000000..f7187f6 --- /dev/null +++ b/lib/igt_plot.c @@ -0,0 +1,607 @@ +/* + * Copyright © 2015 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "igt_plot.h" +#include "igt_aux.h" /* min() */ + +/** + * SECTION:igt_plot + * @short_description: Draw various plots + * @title: Plots + * @include: igt_plots.h + * + * A drawing is better than a long speech. Plotting data can reveal surprises + * and the igt_plot_t object let you do just that. + */ + +/* snap to pixel */ +#define SNAP(d) ((uint64_t)(d) + 0.5) + +static igt_vector_t *igt_vector_new_internal(unsigned int n) +{ + igt_vector_t *v; + + /* single allocation for both the vector and data */ + v = malloc(sizeof(igt_vector_t) + n * sizeof(double)); + v->ref = 1; + v->n = n; + + return v; +} + +/** + * igt_vector_new: + * @n: Number of samples + * + * Creates a zeroed vector of size @n. + */ +igt_vector_t *igt_vector_new(unsigned int n) +{ + igt_vector_t *v; + + v = igt_vector_new_internal(n); + memset(v->values, 0, n * sizeof(double)); + + return v; +} + +/** + * igt_vector_new_from_array: + * @array: (array length=n): C array of doubles + * @n: Size of the array + * + * Creates a new vector from @array, copying the data. + */ +igt_vector_t *igt_vector_new_from_array(const double *array, unsigned int n) +{ + igt_vector_t *v; + + v = igt_vector_new_internal(n); + memcpy(v->values, array, n * sizeof(double)); + + return v; +} + +/** + * igt_vector_new_from_array_64: + * @array: (array length=n): C array of doubles + * @n: Size of the array + * + * Like igt_vector_new_from_array() but for uint64_t. + */ +igt_vector_t *igt_vector_new_from_array_u64(const uint64_t *array, + unsigned int n) +{ + igt_vector_t *v; + unsigned int i; + + v = igt_vector_new_internal(n); + for (i = 0; i < n; i++) + v->values[i] = (double)array[i]; + + return v; +} + +/** + * igt_vector_ref: + * @v: An #igt_vector_t + * + * Takes a reference on @v. + */ +igt_vector_t *igt_vector_ref(igt_vector_t *v) +{ + v->ref++; + return v; +} + +/** + * igt_vector_unref: + * @v: An #igt_vector_t + * + * Releases a reference on @v, freeing the object if the reference reaches 0. + */ +void igt_vector_unref(igt_vector_t *v) +{ + if (--v->ref == 0) + free(v); +} + +/** + * igt_vector_get_min_max: + * @v: An #igt_vector_t + * @min: (out): The minimum value in @v + * @max: (out): The maximum value in @v + * + * Finds the mininum and maximum value in @v. + */ +void igt_vector_get_min_max(const igt_vector_t *v, double *min, double *max) +{ + unsigned int i; + double small, big; + + /* + * Make sure we deal with an even number of samples for the second step + */ + if (v->n % 2 == 1){ + *min = v->values[0]; + *max = v->values[0]; + i = 1; + } else { + *min = DBL_MAX; + *max = -DBL_MAX; + i = 0; + } + + for (; i < v->n; i += 2) { + if (v->values[i] < v->values[i + 1]) { + small = v->values[i]; + big = v->values[i + 1]; + } else { + small = v->values[i + 1]; + big = v->values[i]; + } + + if (*min > small) + *min = small; + if (*max < big) + *max = big; + } +} + +/** + * igt_vector_linear: + * @min: Lower bound + * @max: Upper bound + * @n: Number of samples to generate + * + * Creates a vector of @n values evenly spaced between @min and @max (both + * inclusive). + */ +igt_vector_t *igt_vector_linear(double min, double max, unsigned int n) +{ + igt_vector_t *v; + unsigned int i; + + v = igt_vector_new_internal(n); + for (i = 0; i < n; i++) + v->values[i] = min + i * (max - min) / (n - 1); + + return v; +} + +static void igt_plot_axis_init(igt_plot_axis_t *axis, + igt_orientation_t orientation) +{ + memset(axis, 0, sizeof(*axis)); + + axis->n_ticks = 5; + axis->orientation = orientation; + axis->min = DBL_MAX; + axis->max = -DBL_MAX; +} + +static void igt_plot_axis_fini(igt_plot_axis_t *axis) +{ + +} + +static void igt_plot_axis_add_range(igt_plot_axis_t *axis, + double min, double max) +{ + if (min < axis->min) + axis->min = min; + if (max > axis->max) + axis->max = max; +} + +static void igt_plot_ctx_init(igt_plot_ctx_t *ctx) +{ + memset(ctx, 0, sizeof(*ctx)); + + ctx->line_width = 1.5; +} + +static void igt_plot_ctx_fini(igt_plot_ctx_t *ctx) +{ + igt_vector_unref(ctx->x); + ctx->x = NULL; + igt_vector_unref(ctx->y); + ctx->y = NULL; +} + +/** + * igt_plot_init: + * @plot: An #igt_plot_t instance + * @width: Width of the plot (in pixels) + * @height: Height of the plot (in pixels) + * + * Initializes an igt_plot_t object. Use igt_plot_fini() when finished with it. + */ +void igt_plot_init(igt_plot_t *plot, unsigned int width, unsigned int height) +{ + memset(plot, 0, sizeof(*plot)); + + plot->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + width, height); + plot->width = width; + plot->height = height; + plot->cr = cairo_create(plot->surface); + + igt_plot_axis_init(&plot->x_axis, IGT_ORIENTATION_HORIZONTAL); + igt_plot_axis_init(&plot->y_axis, IGT_ORIENTATION_VERTICAL); + + plot->ctx = &plot->contexts[0]; + igt_plot_ctx_init(plot->ctx); +} + +/** + * igt_plot_fini: + * @plot: An #igt_plot_t instance + * + * Frees resources allocated during the life time of @plot. + */ +void igt_plot_fini(igt_plot_t *plot) +{ + unsigned int i; + + for (i = 0; i < plot->n_valid_contexts; i++) + igt_plot_ctx_fini(&plot->contexts[i]); + + igt_plot_axis_fini(&plot->x_axis); + igt_plot_axis_fini(&plot->y_axis); + + cairo_destroy(plot->cr); + cairo_surface_destroy(plot->surface); +} + +/** + * igt_plot_set_line_width: + * @plot: An #igt_plot_t instance + * @width: The new line width to use + * + * Set the color to use when drawing plots. + */ +void igt_plot_set_line_width(igt_plot_t *plot, double width) +{ + igt_plot_ctx_t *ctx = plot->ctx; + + ctx->line_width = width; +} + +static void igt_plot_set_vectors(igt_plot_t *plot, + igt_vector_t *x, igt_vector_t *y) +{ + igt_plot_ctx_t *ctx = plot->ctx; + double x_min, x_max, y_min, y_max; + + ctx->x = igt_vector_ref(x); + ctx->y = igt_vector_ref(y); + + /* X axis (sorted data) */ + x_min = x->values[0]; + x_max = x->values[x->n - 1]; + ctx->x_range = x_max - x_min; + igt_plot_axis_add_range(&plot->x_axis, x_min, x_max); + + /* Y axis */ + igt_vector_get_min_max(y, &y_min, &y_max); + igt_plot_axis_add_range(&plot->y_axis, y_min, y_max); + ctx->y_range = y_max - y_min; +} + +static void igt_plot_next_ctx(igt_plot_t *plot) +{ + unsigned int new_ctx_idx; + igt_plot_ctx_t *ctx; + + new_ctx_idx = ++plot->n_valid_contexts; + assert(new_ctx_idx < IGT_PLOT_MAX_PLOTS); + + /* keep most states for the next plot */ + memcpy(&plot->contexts[new_ctx_idx], plot->ctx, sizeof(*plot->ctx)); + ctx = plot->ctx = &plot->contexts[new_ctx_idx]; + + /* but reset the vector data */ + ctx->x = ctx->y = NULL; + ctx->x_range = ctx->y_range = 0.0; +} + +/** + * igt_plot_draw: + * @plot: An #igt_plot_t instance + * @x: X-axis data + * @y: Y-axis data + * + * Draw things on the @plot. + */ +void igt_plot_draw(igt_plot_t *plot, igt_vector_t *x, igt_vector_t *y) +{ + igt_plot_set_vectors(plot, x, y); + igt_plot_next_ctx(plot); +} + +typedef struct { + char *text; + cairo_text_extents_t extents; + igt_align_t halign, valign; +} igt_label_t; + +typedef struct { + double tick_label_padding; /* padding between label and axis */ + double tick_label_font_size; + igt_box_t plot_area; + igt_label_t *x_tick_labels; + igt_label_t *y_tick_labels; +} flush_t; + +static double plot_length(igt_plot_t *plot, double percent) +{ + return round(percent * min(plot->width, plot->height)); +} + +static void +igt_plot_draw_text(igt_plot_t *plot, double x, double y, igt_label_t *label) +{ + /* XXX: bearings? */ + + switch (label->halign) { + case IGT_ALIGN_LEFT: + break; + case IGT_ALIGN_RIGHT: + x -= label->extents.width + label->extents.x_bearing; + break; + case IGT_ALIGN_CENTER: + default: + x -= (label->extents.width + label->extents.x_bearing) / 2; + break; + } + + switch (label->valign) { + case IGT_ALIGN_TOP: + y += label->extents.height; + break; + case IGT_ALIGN_BOTTOM: + break; + case IGT_ALIGN_CENTER: + default: + y += label->extents.height / 2; + break; + } + + cairo_move_to(plot->cr, x, y); + cairo_show_text(plot->cr, label->text); +} + +static double fit(double p, double start, double range, double scale) +{ + return start + (range / 2 + p) * scale; +} + +static void +igt_plot_draw_one(igt_plot_t *plot, igt_plot_ctx_t *ctx, flush_t *flush) +{ + igt_box_t *area = &flush->plot_area; + igt_vector_t *x = ctx->x; + igt_vector_t *y = ctx->y; + double x_min, y_min, x_range, y_range, x_scale, y_scale, area_width, + area_height; + unsigned int i; + + /* + * We don't use cairo's CTM to fit the data into the drawing area + * as we may want to draw screen-space things (ie. markers) at the + * same time. Also it's a bit impractical to derive things like + * line_width, which are screen-space dimensions, into the curve data + * space. + */ + area_width = area->x2 - area->x1; + area_height = area->y2 - area->y1; + x_min = plot->x_axis.min; + y_min = plot->y_axis.min; + x_range = plot->x_axis.max - x_min; + y_range = plot->y_axis.max - y_min; + x_scale = area_width / x_range; + y_scale = area_height / y_range; + + cairo_move_to(plot->cr, + fit(x->values[0], area->x1, x_range, x_scale), + fit(y->values[0], area->y2, y_range, -y_scale)); + for (i = 1; i < x->n; i++) + cairo_line_to(plot->cr, + fit(x->values[i], area->x1, x_range, x_scale), + fit(y->values[i], area->y2, y_range, -y_scale)); + cairo_set_line_cap(plot->cr, CAIRO_LINE_CAP_BUTT); + cairo_set_line_width(plot->cr, ctx->line_width); + cairo_stroke(plot->cr); + +} + +static void igt_plot_draw_ticks(igt_plot_t *plot, igt_plot_axis_t *axis, + double tick_length, flush_t *flush) +{ + unsigned int i; + igt_box_t *area = &flush->plot_area; + double area_width, area_height; + + area_width = area->x2 - area->x1; + area_height = area->y2 - area->y1; + + cairo_set_line_cap(plot->cr, CAIRO_LINE_CAP_SQUARE); + cairo_set_line_width(plot->cr, 1.0); + + for (i = 0; i < axis->n_ticks; i++) { + double x, y; + igt_label_t *label; + + if (axis->orientation == IGT_ORIENTATION_HORIZONTAL) { + x = area->x1 + i * area_width / (axis->n_ticks - 1); + y = area->y2; + + cairo_move_to(plot->cr, SNAP(x), y); + cairo_line_to(plot->cr, SNAP(x), SNAP(y - tick_length)); + cairo_stroke(plot->cr); + + label = &flush->x_tick_labels[i]; + y += flush->tick_label_padding; + } else { + x = area->x1; + y = area->y2 - i * area_height / (axis->n_ticks - 1); + + cairo_move_to(plot->cr, x, SNAP(y)); + cairo_line_to(plot->cr, SNAP(x + tick_length), SNAP(y)); + cairo_stroke(plot->cr); + + label = &flush->y_tick_labels[i]; + x -= flush->tick_label_padding; + } + + cairo_set_font_size(plot->cr, flush->tick_label_font_size); + igt_plot_draw_text(plot, x, y, label); + } +} + +static void igt_plot_draw_axis(igt_plot_t *plot, flush_t *flush) +{ + igt_box_t *area = &flush->plot_area; + const double tick_length = plot_length(plot, 0.01); + + /* X-axis */ + cairo_move_to(plot->cr, area->x1, area->y2); + cairo_line_to(plot->cr, area->x2, area->y2); + igt_plot_draw_ticks(plot, &plot->x_axis, tick_length, flush); + + /* Y-axis */ + cairo_move_to(plot->cr, area->x1, area->y2); + cairo_line_to(plot->cr, area->x1, area->y1); + igt_plot_draw_ticks(plot, &plot->y_axis, tick_length, flush); + +} + +static void igt_plot_layout_tick_labels(igt_plot_t *plot, + igt_plot_axis_t *axis, + igt_label_t *labels, + double *max_size) +{ + unsigned int i; + + *max_size = -DBL_MAX; + + for (i = 0; i < axis->n_ticks; i++) { + igt_label_t *label = &labels[i]; + double v = axis->min + + (axis->max - axis->min) * i / (axis->n_ticks - 1); + + asprintf(&label->text, "%.02lf", v); + cairo_text_extents(plot->cr, label->text, &label->extents); + + if (axis->orientation == IGT_ORIENTATION_HORIZONTAL) { + label->halign = IGT_ALIGN_CENTER; + label->valign = IGT_ALIGN_TOP; + if (label->extents.height > *max_size) + *max_size = label->extents.height; + } else { + label->halign = IGT_ALIGN_RIGHT; + label->valign = IGT_ALIGN_CENTER; + if (label->extents.width > *max_size) + *max_size = label->extents.width; + } + } +} + +static void igt_plot_layout(igt_plot_t *plot, flush_t *flush) +{ + const double outer_padding = 0.10; + double max_width, max_height; + + flush->tick_label_padding = plot_length(plot, 0.02); + flush->tick_label_font_size = round(0.015 * plot->width); + + /* outer padding */ + flush->plot_area.x1 = SNAP(plot->width * outer_padding); + flush->plot_area.y1 = SNAP(plot->height * outer_padding); + flush->plot_area.x2 = SNAP(plot->width * (1.0 - outer_padding)); + flush->plot_area.y2 = SNAP(plot->height * (1.0 - outer_padding)); + + /* measure tick labels and adjust the plot area */ + cairo_set_font_size(plot->cr, flush->tick_label_font_size); + igt_plot_layout_tick_labels(plot, &plot->x_axis, flush->x_tick_labels, + &max_height); + flush->plot_area.y2 -= max_height - flush->tick_label_padding; + igt_plot_layout_tick_labels(plot, &plot->y_axis, flush->y_tick_labels, + &max_width); + flush->plot_area.x1 += max_width + flush->tick_label_padding; +} + +static void igt_plot_flush_init(igt_plot_t *plot, flush_t *flush) +{ + memset(flush, 0, sizeof(*flush)); + + flush->x_tick_labels = malloc(plot->x_axis.n_ticks * + sizeof(*flush->x_tick_labels)); + flush->y_tick_labels = malloc(plot->y_axis.n_ticks * + sizeof(*flush->y_tick_labels)); +} + +static void igt_plot_flush_fini(igt_plot_t *plot, flush_t *flush) +{ + free(flush->x_tick_labels); + free(flush->y_tick_labels); +} + +/** + * igt_plot_write: + * @plot: An #igt_plot_t instance + * @filename: File name + * + * Write @plot onto the disk in a file named @filename. + */ +void igt_plot_write(igt_plot_t *plot, const char *filename) +{ + flush_t flush; + unsigned int i; + + igt_plot_flush_init(plot, &flush); + + igt_plot_layout(plot, &flush); + + igt_plot_draw_axis(plot, &flush); + + for (i = 0; i < plot->n_valid_contexts; i++) + igt_plot_draw_one(plot, &plot->contexts[i], &flush); + + cairo_surface_write_to_png(plot->surface, filename); + + igt_plot_flush_fini(plot, &flush); +} diff --git a/lib/igt_plot.h b/lib/igt_plot.h new file mode 100644 index 0000000..82ad10a --- /dev/null +++ b/lib/igt_plot.h @@ -0,0 +1,122 @@ +/* + * Copyright © 2015 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +#ifndef __IGT_PLOT_H__ +#define __IGT_PLOT_H__ + +#include + +#include + +#include "igt_types.h" + +/** + * igt_vector_t: + * @values: Array of doubles + * @n: Length of @values + * + * A ref-counted, fixed-length, array of doubles + * + * A simple, fixed-sized, vector of doubles + */ +typedef struct { + /*< private >*/ + int ref; + /*< public >*/ + unsigned int n; + double values[]; +} igt_vector_t; + +igt_vector_t *igt_vector_new(unsigned int n); +igt_vector_t *igt_vector_new_from_array(const double *array, unsigned int n); +igt_vector_t *igt_vector_new_from_array_u64(const uint64_t *array, + unsigned int n); +igt_vector_t *igt_vector_linear(double min, double max, unsigned n); +igt_vector_t *igt_vector_ref(igt_vector_t *v); +void igt_vector_unref(igt_vector_t *v); +void igt_vector_get_min_max(const igt_vector_t *v, double *min, double *max); + +/** + * igt_plot_axis_t: + * + * An axis. + */ +typedef struct { + /*< private >*/ + igt_orientation_t orientation; + unsigned int n_ticks; + double min, max; /* range of the values on this axis */ +} igt_plot_axis_t; + +#define IGT_PLOT_MAX_PLOTS 32 + +typedef struct { + /*< private >*/ + igt_vector_t *x, *y; + double x_range, y_range; + double line_width; +} igt_plot_ctx_t; + +/** + * igt_plot_t: + * + * Draw nice plots! + */ +typedef struct { + /*< private >*/ + + /* Cairo's corner */ + cairo_surface_t *surface; + cairo_t *cr; + + /* plot-wide states */ + unsigned int width, height; + igt_trbl_t margin; + igt_plot_axis_t x_axis, y_axis; + + /* per draw command contexts */ + igt_plot_ctx_t contexts[IGT_PLOT_MAX_PLOTS + 1]; + unsigned int n_valid_contexts; + igt_plot_ctx_t *ctx; +} igt_plot_t; + +/** + * igt_plot_style_t: + * @IGT_PLOT_LINE: Draw lines between data points + * @IGT_PLOT_POINT: Draw points at each data point + * + * The different types of plots we can draw. + */ +typedef enum { + IGT_PLOT_LINE, + IGT_PLOT_POINT, +} igt_plot_style_t; + +void igt_plot_init(igt_plot_t *plot, unsigned int width, unsigned int height); +void igt_plot_fini(igt_plot_t *plot); +void igt_plot_set_line_width(igt_plot_t *plot, double width); +void igt_plot_draw(igt_plot_t *plot, igt_vector_t *x, igt_vector_t *y); +void igt_plot_write(igt_plot_t *plot, const char *filename); + +#endif /* __IGT_PLOT_H__ */ diff --git a/lib/tests/.gitignore b/lib/tests/.gitignore index 6519406..d7a9174 100644 --- a/lib/tests/.gitignore +++ b/lib/tests/.gitignore @@ -5,8 +5,12 @@ igt_list_only igt_no_exit igt_no_exit_list_only igt_no_subtest +igt_plot igt_segfault igt_simple_test_subtests igt_simulation igt_stats igt_timeout + +# files generated by igt_plot +test_*.png diff --git a/lib/tests/Makefile.sources b/lib/tests/Makefile.sources index 58ae36b..9c5cf3f 100644 --- a/lib/tests/Makefile.sources +++ b/lib/tests/Makefile.sources @@ -4,6 +4,7 @@ check_PROGRAMS = \ igt_fork_helper \ igt_list_only \ igt_no_subtest \ + igt_plot \ igt_simulation \ igt_simple_test_subtests \ igt_stats \ diff --git a/lib/tests/igt_plot.c b/lib/tests/igt_plot.c new file mode 100644 index 0000000..be0e132 --- /dev/null +++ b/lib/tests/igt_plot.c @@ -0,0 +1,98 @@ +/* + * Copyright © 2015 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +#include +#include + +#include "igt_core.h" +#include "igt_plot.h" + +#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0])) + +#define SNAP_TO_PIXEL(d) ((uint64_t)(d) + 0.5) + +static void test_snap_to_pixel(void) +{ + static const struct { double input; double expected; } test_data[] = { + { 1.0, 1.5 }, { 1.1, 1.5 }, { 1.2, 1.5 }, { 1.3, 1.5 }, + { 1.4, 1.5 }, { 1.5, 1.5 }, { 1.6, 1.5 }, { 1.7, 1.5 }, + { 1.8, 1.5 }, { 1.9, 1.5 }, { 2.0, 2.5 }, { 2.1, 2.5 }, + }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(test_data); i++) + igt_assert_eq_double(SNAP_TO_PIXEL(test_data[i].input), + test_data[i].expected); +} + +static void test_min_max(void) +{ + static const uint64_t s1[] = + { 47, 49, 6, 7, 15, 36, 39, 40, 41, 42, 43 }; + static const uint64_t s2[] = { 40, 41, 7, 15, 36, 39 }; + igt_vector_t *v1, *v2; + double min, max; + + v1 = igt_vector_new_from_array_u64(s1, ARRAY_SIZE(s1)); + v2 = igt_vector_new_from_array_u64(s2, ARRAY_SIZE(s2)); + + igt_vector_get_min_max(v1, &min, &max); + igt_assert_eq_double(min, 6); + igt_assert_eq_double(max, 49); + + igt_vector_get_min_max(v2, &min, &max); + igt_assert_eq_double(min, 7); + igt_assert_eq_double(max, 41); + + igt_vector_unref(v1); + igt_vector_unref(v2); +} + +static void test_simple_plot(void) +{ + igt_vector_t *x, *y; + unsigned int i; + igt_plot_t plot; + + x = igt_vector_linear(-1.0, 1.0, 200); + + y = igt_vector_new(200); + for (i = 0; i < y->n; i++) + y->values[i] = sin(2 * M_PI * x->values[i]); + + igt_plot_init(&plot, 800, 600); + igt_plot_draw(&plot, x, y); + igt_plot_write(&plot, "test_simple_plot.png"); + + igt_plot_fini(&plot); + igt_vector_unref(x); + igt_vector_unref(y); +} + +igt_simple_main +{ + test_snap_to_pixel(); + test_min_max(); + test_simple_plot(); +}