| /* bench.c -- native benchmark for Cairo library (meant to test java2d) |
| Copyright (C) 2006 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath examples. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. */ |
| |
| #include "bench.h" |
| #include <math.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <gtk/gtk.h> |
| #include <sys/timeb.h> |
| |
| G_DEFINE_TYPE (Benchmark, benchmark, GTK_TYPE_DRAWING_AREA); |
| |
| // Needed for the gtk widget, but not used: |
| static void |
| benchmark_class_init (BenchmarkClass *klass) |
| { |
| } |
| |
| static void |
| benchmark_init (Benchmark *obj) |
| { |
| } |
| |
| // The Arc2D's PathIterator uses some transforms, so we condense the required |
| // functionality of AffineTransform |
| static void |
| doTransform (double rx, double ry, double theta, double *cvec) |
| { |
| // Define identity matrix (corresponds to new AffineTransform()) |
| double m00 = 1; |
| double m10 = 0; |
| double m01 = 0; |
| double m11 = 1; |
| double m02 = 0; |
| double m12 = 0; |
| |
| // AffineTransform.scale(rx, ry) |
| m00 = m00 * rx; |
| m01 = m01 * ry; |
| m10 = m10 * rx; |
| m11 = m11 * ry; |
| |
| // AffineTransform.rotate(theta) |
| double c = cos(theta); |
| double s = sin(theta); |
| double n00 = m00 * c + m01 * s; |
| double n01 = m00 * -s + m01 * c; |
| double n10 = m10 * c + m11 * s; |
| double n11 = m10 * -s + m11 * c; |
| |
| m00 = n00; |
| m01 = n01; |
| m10 = n10; |
| m11 = n11; |
| |
| // AffineTransform.transform(cvec, 0, cvec, 0, 1) |
| double dstPts[2]; |
| dstPts[0] = (float) (m00 * cvec[0] + m01 * cvec[1] + m02); |
| dstPts[1] = (float) (m10 * cvec[0] + m11 * cvec[1] + m12); |
| cvec[0] = dstPts[0]; |
| cvec[1] = dstPts[1]; |
| } |
| |
| // Place an arc on the cairo path, simulating java2d's Arc2D |
| static void |
| setupArc(cairo_t *cr, GtkWidget *bench, int shift) |
| { |
| double x, y; |
| |
| // Normally passed into the Arc2D constructor |
| x = bench->allocation.x + (rand() % (bench->allocation.width - minSize + 1)); |
| y = bench->allocation.y + (rand() % (bench->allocation.height - minSize + 1)); |
| |
| int angle = rand() % 360; |
| int length = (rand() % 360) - angle; |
| int width = rand() % (int)((bench->allocation.width - x - 10) + 10); |
| int height = rand() % (int)((bench->allocation.height - y - 10) + 10); |
| |
| // This is from the ArcPath iterator |
| double start = angle * (M_PI / 180); |
| double extent = length * (M_PI / 180); |
| |
| if (extent < 0) |
| { |
| extent = -extent; |
| start = 2 * M_PI - extent + start; |
| } |
| |
| int limit; |
| if (width < 0 || height < 0) // We assume type == 0; ie, Arc2D.OPEN |
| limit = -1; |
| else if (extent == 0) |
| limit = 0; |
| else if (extent <= M_PI / 2.0) |
| limit = 1; |
| else if (extent <= M_PI) |
| limit = 2; |
| else if (extent <= 3.0 * (M_PI / 2.0)) |
| limit = 3; |
| else |
| limit = 4; |
| |
| // This is from CairoGraphics2D.walkPath |
| double xnew = 0; |
| double ynew = 0; |
| double coords[6]; |
| |
| cairo_fill_rule_t cfillrule = CAIRO_FILL_RULE_WINDING; |
| cairo_set_fill_rule(cr, cfillrule); |
| |
| // First iteration will move to the starting point |
| double rx = width / 2; |
| double ry = height / 2; |
| double xmid = x + rx; |
| double ymid = y + ry; |
| coords[0] = xmid + rx * cos(start); |
| coords[1] = ymid - ry * sin(start); |
| |
| if (shift == 1) |
| { |
| xnew = floor(coords[0]) + 0.5; |
| ynew = floor(coords[1]) + 0.5; |
| } |
| else |
| { |
| xnew = coords[0]; |
| ynew = coords[1]; |
| } |
| |
| cairo_move_to(cr, xnew, ynew); |
| |
| // Iterate through segments of the arc |
| int current; |
| for (current = 1; current <= limit; current++) |
| { |
| // Back to the ArcPath iterator's getCurrent |
| double kappa = (sqrt(2.0) - 1.0) * (4.0 / 3.0); |
| double quad = (M_PI / 2.0); |
| |
| double curr_begin = start + (current - 1) * quad; |
| double curr_extent; |
| |
| if (start + extent - curr_begin < quad) |
| curr_extent = (start + extent) - curr_begin; |
| else |
| curr_extent = quad; |
| |
| double portion_of_a_quadrant = curr_extent / quad; |
| |
| double x0 = xmid + rx * cos(curr_begin); |
| double y0 = ymid - ry * sin(curr_begin); |
| |
| double x1 = xmid + rx * cos(curr_begin + curr_extent); |
| double y1 = ymid - ry * sin(curr_begin + curr_extent); |
| |
| double cvec[2]; |
| double len = kappa * portion_of_a_quadrant; |
| double angle = curr_begin; |
| |
| cvec[0] = 0; |
| cvec[1] = len; |
| doTransform(rx, ry, angle, cvec); |
| coords[0] = x0 + cvec[0]; |
| coords[1] = y0 - cvec[1]; |
| |
| cvec[0] = 0; |
| cvec[1] = -len; |
| doTransform(rx, ry, angle, cvec); |
| doTransform(1, 1, curr_extent, cvec); |
| coords[2] = x1 + cvec[0]; |
| coords[3] = y1 - cvec[1]; |
| |
| coords[4] = x1; |
| coords[5] = y1; |
| |
| // draw it, from CairoGraphics2D.walkPath |
| if (shift == 1) |
| { |
| xnew = floor(coords[4]) + 0.5; |
| ynew = floor(coords[5]) + 0.5; |
| cairo_curve_to(cr, floor(coords[0]) + 0.5, floor(coords[1]) + 0.5, |
| floor(coords[2]) + 0.5, floor(coords[3]) + 0.5, |
| xnew, ynew); |
| } |
| else |
| { |
| xnew = coords[4]; |
| ynew = coords[5]; |
| cairo_curve_to(cr, coords[0], coords[1], coords[2], |
| coords[3], xnew, ynew); |
| } |
| } |
| |
| // Randomize the colour, just for asthetics =) |
| cairo_set_source_rgb(cr, (rand() % 100 / (float)100), |
| (rand() % 100 / (float)100), |
| (rand() % 100 / (float)100)); |
| |
| } |
| |
| // Place a beizer curve on the cairo path, simulating java2d's CubicCurve2D |
| static void |
| setupCurve(cairo_t *cr, GtkWidget *bench, int shift) |
| { |
| // These are options when creating a new curve |
| int x1 = bench->allocation.x + (rand() % (bench->allocation.width - minSize)); |
| int y1 = bench->allocation.y + (rand() % (bench->allocation.height - minSize)); |
| int xc1 = bench->allocation.x + (rand() % (bench->allocation.width - minSize)); |
| int yc1 = bench->allocation.y + (rand() % (bench->allocation.height - minSize)); |
| int xc2 = bench->allocation.x + (rand() % (bench->allocation.width - minSize)); |
| int yc2 = bench->allocation.y + (rand() % (bench->allocation.height - minSize)); |
| int x2 = bench->allocation.x + (rand() % (bench->allocation.width - minSize)); |
| int y2 = bench->allocation.y + (rand() % (bench->allocation.height - minSize)); |
| |
| // From CairoGraphics2D.walkPath |
| double xnew = 0; |
| double ynew = 0; |
| double coords[6]; |
| |
| cairo_fill_rule_t cfillrule = CAIRO_FILL_RULE_WINDING; |
| cairo_set_fill_rule(cr, cfillrule); |
| |
| // And into CubicCurve's PathIterator... |
| // start by moving to the starting coordinate |
| coords[0] = (float) x1; |
| coords[1] = (float) y1; |
| |
| if (shift == 1) |
| { |
| xnew = floor(coords[0]) + 0.5; |
| ynew = floor(coords[1]) + 0.5; |
| } |
| else |
| { |
| xnew = coords[0]; |
| ynew = coords[1]; |
| } |
| |
| cairo_move_to(cr, xnew, ynew); |
| |
| // Now the curve itself |
| coords[0] = (float) xc1; |
| coords[1] = (float) yc1; |
| coords[2] = (float) xc2; |
| coords[3] = (float) yc2; |
| coords[4] = (float) x2; |
| coords[5] = (float) y2; |
| |
| if (shift == 1) |
| { |
| xnew = floor(coords[4]) + 0.5; |
| ynew = floor(coords[5]) + 0.5; |
| cairo_curve_to(cr, floor(coords[0]) + 0.5, floor(coords[1]) + 0.5, |
| floor(coords[2]) + 0.5, floor(coords[3]) + 0.5, |
| xnew, ynew); |
| } |
| else |
| { |
| xnew = coords[4]; |
| ynew = coords[5]; |
| cairo_curve_to(cr, coords[0], coords[1], coords[2], |
| coords[3], xnew, ynew); |
| } |
| |
| // Randomize colour for asthetics |
| cairo_set_source_rgb(cr, (rand() % 100 / (float)100), |
| (rand() % 100 / (float)100), |
| (rand() % 100 / (float)100)); |
| } |
| |
| // Place a line on the cairo path, simulating java2d's Line2D |
| static void |
| setupLine(cairo_t *cr, GtkWidget *bench, int shift) |
| { |
| // These are set when you create a line |
| int x1 = bench->allocation.x + (rand() % (bench->allocation.width - minSize)); |
| int y1 = bench->allocation.y + (rand() % (bench->allocation.height - minSize)); |
| int x2 = bench->allocation.x + (rand() % (bench->allocation.width - minSize)); |
| int y2 = bench->allocation.y + (rand() % (bench->allocation.height - minSize)); |
| |
| // This is from CairoGraphics2D.walkPath |
| double xnew = 0; |
| double ynew = 0; |
| double coords[6]; |
| |
| cairo_fill_rule_t cfillrule = CAIRO_FILL_RULE_WINDING; |
| cairo_set_fill_rule(cr, cfillrule); |
| |
| // And into Line2D's PathIterator |
| coords[0] = (float) x1; |
| coords[1] = (float) y1; |
| |
| if (shift == 1) |
| { |
| xnew = floor(coords[0]) + 0.5; |
| ynew = floor(coords[1]) + 0.5; |
| } |
| else |
| { |
| xnew = coords[0]; |
| ynew = coords[1]; |
| } |
| |
| cairo_move_to(cr, xnew, ynew); |
| |
| coords[0] = (float) x2; |
| coords[1] = (float) y2; |
| |
| if (shift == 1) |
| { |
| xnew = floor(coords[0]) + 0.5; |
| ynew = floor(coords[1]) + 0.5; |
| } |
| else |
| { |
| xnew = coords[0]; |
| ynew = coords[1]; |
| } |
| |
| cairo_line_to(cr, xnew, ynew); |
| |
| // Randomize colour for asthetics |
| cairo_set_source_rgb(cr, (rand() % 100 / (float)100), |
| (rand() % 100 / (float)100), |
| (rand() % 100 / (float)100)); |
| } |
| |
| // Place a rectangle on the cairo path, simulating java2d's Rectangle2D |
| static void |
| setupRect(cairo_t *cr, GtkWidget *bench, int shift) |
| { |
| // These are set when you create a rectangle |
| int x1 = bench->allocation.x + (rand() % (bench->allocation.width - minSize)); |
| int y1 = bench->allocation.y + (rand() % (bench->allocation.height - minSize)); |
| int x2 = bench->allocation.x + (rand() % (bench->allocation.width - minSize)); |
| int y2 = bench->allocation.y + (rand() % (bench->allocation.height - minSize)); |
| |
| // draw() and fill() have been optimized to ignore the PathIterator. |
| // We do the same here. |
| double xnew = 0; |
| double ynew = 0; |
| |
| if (shift == 1) |
| { |
| xnew = floor(x1) + 0.5; |
| ynew = floor(y1) + 0.5; |
| } |
| else |
| { |
| xnew = x1; |
| ynew = y1; |
| } |
| |
| cairo_rectangle(cr, x1, y1, x2, y2); |
| |
| // Randomize colour for asthetics |
| cairo_set_source_rgb(cr, (rand() % 100 / (float)100), |
| (rand() % 100 / (float)100), |
| (rand() % 100 / (float)100)); |
| } |
| |
| // The real work gets done here: this function is called when the widget |
| // is drawn on screen. |
| static void |
| draw (GtkWidget *bench, cairo_t *cr) |
| { |
| // Setup |
| struct timeb t1, t2; |
| int i, timeElapsed; |
| |
| cairo_set_line_width(cr, lineWidth); |
| |
| if (antialias == 0) |
| cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE); |
| else |
| cairo_set_antialias(cr, CAIRO_ANTIALIAS_GRAY); |
| |
| // Tell the user what's going on |
| printf("Testing native cairo drawing..\n"); |
| printf(" Screen size is %d x %d \n", screenWidth, screenHeight); |
| printf(" Line width is %d\n", lineWidth); |
| printf(" Test size: %d\n", testSize); |
| |
| if (antialias == 0) |
| printf(" Anti-alias is off\n"); |
| else |
| printf(" Anti-alias is on\n"); |
| |
| printf("\n"); |
| fflush(stdout); |
| |
| // Draw & fill Arc |
| if (arcTest == 1) |
| { |
| // Draw |
| ftime(&t1); |
| for (i = 0; i < testSize; i++) |
| { |
| setupArc(cr, bench, 1); |
| cairo_stroke (cr); |
| } |
| |
| ftime(&t2); |
| timeElapsed = 1000 * (t2.time - t1.time) + (t2.millitm - t1.millitm); |
| printf("Draw arc: %d ms\n", timeElapsed); |
| fflush(stdout); |
| |
| // Fill |
| ftime(&t1); |
| for (i = 0; i < testSize; i++) |
| { |
| setupArc(cr, bench, 0); |
| cairo_fill (cr); |
| } |
| |
| ftime(&t2); |
| timeElapsed = 1000 * (t2.time - t1.time) + (t2.millitm - t1.millitm); |
| printf("Fill arc: %d ms\n", timeElapsed); |
| } |
| |
| // Draw cubic curve |
| if (curveTest == 1) |
| { |
| ftime(&t1); |
| for (i = 0; i < testSize; i++) |
| { |
| setupCurve(cr, bench, 1); |
| cairo_stroke (cr); |
| } |
| |
| ftime(&t2); |
| timeElapsed = 1000 * (t2.time - t1.time) + (t2.millitm - t1.millitm); |
| printf("Draw cubic curve: %d ms\n", timeElapsed); |
| } |
| |
| // Ellipse: skip; this is just a special case of arc |
| // General path: skip; this doesn't even work in java2d |
| |
| // Draw Line |
| if (lineTest == 1) |
| { |
| ftime(&t1); |
| for (i = 0; i < testSize; i++) |
| { |
| setupLine(cr, bench, 1); |
| cairo_stroke (cr); |
| } |
| |
| ftime(&t2); |
| timeElapsed = 1000 * (t2.time - t1.time) + (t2.millitm - t1.millitm); |
| printf("Draw line: %d ms\n", timeElapsed); |
| } |
| |
| // Draw & fill Rectangle |
| if (rectTest == 1) |
| { |
| // Draw |
| ftime(&t1); |
| for (i = 0; i < testSize; i++) |
| { |
| setupRect(cr, bench, 1); |
| cairo_stroke (cr); |
| } |
| |
| ftime(&t2); |
| timeElapsed = 1000 * (t2.time - t1.time) + (t2.millitm - t1.millitm); |
| printf("Draw rectangle: %d ms\n", timeElapsed); |
| |
| // Fill |
| ftime(&t1); |
| for (i = 0; i < testSize; i++) |
| { |
| setupRect(cr, bench, 0); |
| cairo_fill (cr); |
| } |
| |
| ftime(&t2); |
| timeElapsed = 1000 * (t2.time - t1.time) + (t2.millitm - t1.millitm); |
| printf("Fill rectangle: %d ms\n", timeElapsed); |
| } |
| |
| // Round rectangle: skip, it's just a combination of lines and curves |
| // Image: skip? |
| |
| printf("\n"); |
| } |
| |
| GtkWidget * |
| benchmark_new (void) |
| { |
| return g_object_new (BENCHMARK_TYPE, NULL); |
| } |
| |
| int |
| main (int argc, char **argv) |
| { |
| // Set defaults |
| minSize = 10; |
| arcTest = 0; |
| curveTest = 0; |
| lineTest = 0; |
| rectTest = 0; |
| screenWidth = 320; |
| screenHeight = 240; |
| testSize = 1000; |
| antialias = 0; |
| lineWidth = 1; |
| |
| // Process any command-line user options |
| int i; |
| for (i = 1; i < argc; i++) |
| { |
| // Process options first |
| if (!strcmp(argv[i], "-a")) |
| antialias = 1; |
| else if (!strcmp(argv[i], "-h")) |
| screenHeight = atoi(argv[++i]); |
| else if (!strcmp(argv[i], "-l")) |
| lineWidth = atoi(argv[++i]); |
| else if (!strcmp(argv[i], "-t")) |
| testSize = atoi(argv[++i]); |
| else if (!strcmp(argv[i], "-w")) |
| screenWidth = atoi(argv[++i]); |
| else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--h") |
| || !strcmp(argv[i], "-help") || !strcmp(argv[i], "--help")) |
| { |
| printf("Cairo benchmarker, meant to measure JNI overhead\n"); |
| printf("Usage: bench [-a] [-h height] [-t test size] [-w width] [tests...]\n"); |
| printf("\n"); |
| printf(" Valid options: -a turn on anti-aliasing (default off)\n"); |
| printf(" -h set screen height (default 240)\n"); |
| printf(" -l set stroke line width (default 1)\n"); |
| printf(" -t set test size (default 1000)\n"); |
| printf(" -w set screen width (default 320)\n"); |
| printf(" -h | --help\n"); |
| printf(" Valid tests: arc\n"); |
| printf(" curve\n"); |
| printf(" line\n"); |
| printf(" rect\n"); |
| printf(" (default: run all)\n"); |
| exit (0); |
| } |
| |
| // Process tests |
| else if (!strcmp(argv[i], "arc")) |
| arcTest = 1; |
| else if (!strcmp(argv[i], "curve")) |
| curveTest = 1; |
| else if (!strcmp(argv[i], "line")) |
| lineTest = 1; |
| else if (!strcmp(argv[i], "rect")) |
| rectTest = 1; |
| } |
| |
| // If no tests were specified, we default to running all of them |
| if (arcTest == 0 && curveTest == 0 && lineTest == 0 && rectTest == 0) |
| { |
| arcTest = 1; |
| curveTest = 1; |
| lineTest = 1; |
| rectTest = 1; |
| } |
| |
| // Set up gtk widget |
| GtkWidget *window, *bench; |
| gtk_init (&argc, &argv); |
| |
| window = gtk_window_new (GTK_WINDOW_TOPLEVEL); |
| gtk_window_resize(GTK_WINDOW(window), screenWidth, screenHeight); |
| gtk_window_set_title(GTK_WINDOW(window), "cairo benchmark"); |
| |
| // Set up benchmkar and cairo surface |
| bench = benchmark_new (); |
| gtk_container_add (GTK_CONTAINER (window), bench); |
| gtk_widget_show_all (window); |
| |
| cairo_t *cr; |
| cr = gdk_cairo_create (bench->window); |
| |
| // Run tests |
| draw (bench, cr); |
| |
| // Hold output on screen until user exits. |
| printf("Press any key to exit.\n"); |
| getchar(); |
| exit(0); |
| gtk_main(); |
| } |