diff --git a/test/jdk/performance/client/RenderPerfTest/src/renderperf/RenderPerfTest.java b/test/jdk/performance/client/RenderPerfTest/src/renderperf/RenderPerfTest.java index 51beaba5172..15a5cdf5665 100644 --- a/test/jdk/performance/client/RenderPerfTest/src/renderperf/RenderPerfTest.java +++ b/test/jdk/performance/client/RenderPerfTest/src/renderperf/RenderPerfTest.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2022, JetBrains s.r.o.. All rights reserved. + * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, JetBrains s.r.o.. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,20 +24,33 @@ package renderperf; +import java.awt.AWTException; import java.awt.AlphaComposite; import java.awt.Color; +import java.awt.Composite; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; import java.awt.Image; +import java.awt.Insets; import java.awt.LinearGradientPaint; +import java.awt.Point; import java.awt.RadialGradientPaint; +import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Robot; +import java.awt.Toolkit; +import java.awt.Transparency; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; + import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.Point2D; @@ -48,67 +61,206 @@ import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.DataBufferInt; import java.awt.image.DataBufferShort; +import java.awt.image.VolatileImage; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.HashSet; -import java.util.Objects; -import java.util.concurrent.CountDownLatch; +import java.io.PrintWriter; +import java.nio.charset.Charset; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.IntBinaryOperator; +import java.util.regex.Pattern; + import javax.imageio.ImageIO; import javax.swing.JFrame; +import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.WindowConstants; -public class RenderPerfTest { - private static HashSet ignoredTests = new HashSet<>(); + +public final class RenderPerfTest { + + private final static String VERSION = "RenderPerfTest 2024.02"; + + private static final HashSet ignoredTests = new HashSet<>(); static { - // add ignored tests here - // ignoredTests.add("testMyIgnoredTest"); + // add ignored tests here + // ignoredTests.add("testMyIgnoredTest"); + ignoredTests.add("testCalibration"); // not from command line } - private final static int N = 1000; + private final static String EXEC_MODE_ROBOT = "robot"; + private final static String EXEC_MODE_BUFFER = "buffer"; + private final static String EXEC_MODE_VOLATILE = "volatile"; + private final static String EXEC_MODE_DEFAULT = EXEC_MODE_ROBOT; + + public final static List EXEC_MODES = Arrays.asList(EXEC_MODE_ROBOT, EXEC_MODE_BUFFER, EXEC_MODE_VOLATILE); + + private static String EXEC_MODE = EXEC_MODE_DEFAULT; + + private final static String GC_MODE_DEF = "def"; + private final static String GC_MODE_ALL = "all"; + + private static String GC_MODE = GC_MODE_DEF; + + // System properties: + private final static boolean CALIBRATION = "true".equalsIgnoreCase(System.getProperty("CALIBRATION", "false")); + private final static boolean REPORT_OVERALL_FPS = "true".equalsIgnoreCase(System.getProperty("REPORT_OVERALL_FPS", "false")); + + private final static boolean DUMP_SAMPLES = "true".equalsIgnoreCase(System.getProperty("DUMP_SAMPLES", "false")); + private final static boolean TRACE = "true".equalsIgnoreCase(System.getProperty("TRACE", "false")); + private final static boolean TRACE_CONFIGURE = "true".equalsIgnoreCase(System.getProperty("TRACE_CONFIGURE", "false")); + private final static boolean TRACE_SYNC = "true".equalsIgnoreCase(System.getProperty("TRACE_SYNC", "false")); + + private final static boolean DELAY_START = "true".equalsIgnoreCase(System.getProperty("DelayStart", "false")); + private final static boolean DELAY_TEST = "true".equalsIgnoreCase(System.getProperty("DelayTest", "false")); + + private final static boolean ROBOT_TIME_DELAY = "true".equalsIgnoreCase(System.getProperty("ROBOT_TIME_DELAY", "true")); + private final static boolean ROBOT_TIME_ROUND = "true".equalsIgnoreCase(System.getProperty("ROBOT_TIME_ROUND", "false")); + + private final static boolean TEXT_VERSION = "true".equalsIgnoreCase(System.getProperty("TEXT_VERSION", "true")); + + // time scale multiplier to get more samples so refined metrics: + private final static int TIME_SCALE = Integer.getInteger("TIME_SCALE", 1); + + // default settings: + private static boolean VERBOSE = false; + private static boolean VERBOSE_FONT_CONFIG = false; + private static boolean VERBOSE_GRAPHICS_CONFIG = false; + + private static int REPEATS = 1; + + private static boolean USE_FPS = true; + + private static int NW = 1; + + private final static int N_DEFAULT = 1000; + private static int N = N_DEFAULT; private final static float WIDTH = 800; private final static float HEIGHT = 800; private final static float R = 25; private final static int BW = 50; private final static int BH = 50; - private final static int COUNT = 600; - private final static int CYCLE_DELAY = 3; - private final static int MAX_FRAME_CYCLES = 3000/CYCLE_DELAY; + private final static int IMAGE_W = (int) (WIDTH + BW); + private final static int IMAGE_H = (int) (HEIGHT + BH); + + // Test attributes: + private static String TEXT_STR = TEXT_VERSION ? VERSION : "The quick brown fox jumps over the lazy dog"; + + private static String TEXT_FONT = Font.DIALOG; + private static int TEXT_SIZE_DEFAULT = 12; + private static int TEXT_SIZE_LARGE = 32; + + private final static int COUNT = 600 * TIME_SCALE; + private final static int MIN_COUNT = 20; + private final static int MAX_SAMPLE_COUNT = 2 * COUNT; + + private static int WARMUP_COUNT = MIN_COUNT; + + private final static int DELAY = 1; + private final static int CYCLE_DELAY = DELAY; + + private final static long MIN_MEASURE_TIME_NS = 1000L * 1000 * 1000 * TIME_SCALE; // 1s min + private final static long MAX_MEASURE_TIME_NS = 6000L * 1000 * 1000 * TIME_SCALE; // 6s max + private final static int MAX_FRAME_CYCLES = 1000 * TIME_SCALE / CYCLE_DELAY; private final static int COLOR_TOLERANCE = 10; - private final static int MAX_MEASURE_CYCLES = 6000/CYCLE_DELAY; - private final static Color[] marker = {Color.RED, Color.BLUE, Color.GREEN, Color.YELLOW, Color.ORANGE, Color.MAGENTA}; + private final static Color[] MARKER = {Color.RED, Color.BLUE, Color.GREEN}; + + private final static Toolkit TOOLKIT = Toolkit.getDefaultToolkit(); + + private final static long FRAME_MAX = 60; + private final static long FRAME_PREC_IN_NANOS = (1000L * 1000 * 1000) / (2L * FRAME_MAX); interface Configurable { - void configure(Graphics2D g2d); + void configure(Graphics2D g2d, boolean enabled); } - interface Renderable { - void setup(Graphics2D g2d); - void render(Graphics2D g2d); - void update(); + final static class ConfigurableAA implements Configurable { + @Override + public void configure(final Graphics2D g2d, final boolean enabled) { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + enabled ? RenderingHints.VALUE_ANTIALIAS_ON + : RenderingHints.VALUE_ANTIALIAS_OFF); + } } - static class Particles { - private float[] bx; - private float[] by; - private float[] vx; - private float[] vy; - private float r; - private int n; + final static class ConfigurableTextAA implements Configurable { + @Override + public void configure(final Graphics2D g2d, final boolean enabled) { + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + enabled ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON + : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); + } + } - private float x0; - private float y0; - private float width; - private float height; + final static class ConfigurableTextLCD implements Configurable { + @Override + public void configure(final Graphics2D g2d, final boolean enabled) { + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + enabled ? RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB + : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); + } + } + + final static class ConfigurableXORMode implements Configurable { + @Override + public void configure(final Graphics2D g2d, final boolean enabled) { + if (enabled) { + g2d.setXORMode(Color.WHITE); + } else { + g2d.setPaintMode(); + } + } + } + + final static class ConfigurableXORModeTextLCD implements Configurable { + @Override + public void configure(final Graphics2D g2d, final boolean enabled) { + if (enabled) { + g2d.setXORMode(Color.WHITE); + } else { + g2d.setPaintMode(); + } + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + enabled ? RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB + : RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT); + } + } + + final static class Particles { + private final float[] bx; + private final float[] by; + private final float[] vx; + private final float[] vy; + private final float r; + private final int n; + + private final float x0; + private final float y0; + private final float width; + private final float height; Particles(int n, float r, float x0, float y0, float width, float height) { bx = new float[n]; @@ -143,31 +295,35 @@ public class RenderPerfTest { by[i] += vy[i]; if (by[i] + r > height || by[i] - r < y0) vy[i] = -vy[i]; } - } - } - ParticleRenderable createPR(ParticleRenderer renderer) { - return new ParticleRenderable(renderer); + interface Renderable { + void setup(Graphics2D g2d, boolean enabled); + + void render(Graphics2D g2d); + + void update(); } - static class ParticleRenderable implements Renderable { - ParticleRenderer renderer; - Configurable configure; + final static class ParticleRenderable implements Renderable { + final Particles balls; + final ParticleRenderer renderer; + Configurable configure = null; - ParticleRenderable(ParticleRenderer renderer, Configurable configure) { + ParticleRenderable(final Particles balls, final ParticleRenderer renderer) { + this.balls = balls; this.renderer = renderer; - this.configure = configure; - } - - ParticleRenderable(ParticleRenderer renderer) { - this(renderer, null); } @Override - public void setup(Graphics2D g2d) { - if (configure != null) configure.configure(g2d); + public void setup(final Graphics2D g2d, final boolean enabled) { + if (configure != null) { + if (TRACE_CONFIGURE) { + System.out.println("configure(" + configure.getClass().getSimpleName() + "): " + enabled); + } + configure.configure(g2d, enabled); + } } @Override @@ -180,7 +336,7 @@ public class RenderPerfTest { balls.update(); } - public ParticleRenderable configure(Configurable configure) { + public ParticleRenderable configure(final Configurable configure) { this.configure = configure; return this; } @@ -188,7 +344,46 @@ public class RenderPerfTest { interface ParticleRenderer { void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy); + } + final static class CalibrationParticleRenderer implements ParticleRenderer { + + CalibrationParticleRenderer() { + } + + @Override + public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { + // no-op + } + } + + final static class MixedParticleRenderer implements ParticleRenderer { + + private final ParticleRenderer[] renderers; + + MixedParticleRenderer(ParticleRenderer... renderers) { + this.renderers = renderers; + } + + @Override + public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { + renderers[id % renderers.length].render(g2d, id, x, y, vx, vy); + } + } + + final static class BatchedParticleRenderer implements ParticleRenderer { + + private final ParticleRenderer[] renderers; + + BatchedParticleRenderer(ParticleRenderer... renderers) { + this.renderers = renderers; + } + + @Override + public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { + final int step = N / renderers.length; + renderers[(id / step) % renderers.length].render(g2d, id, x, y, vx, vy); + } } static class FlatParticleRenderer implements ParticleRenderer { @@ -207,7 +402,7 @@ public class RenderPerfTest { @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { g2d.setColor(colors[id % colors.length]); - g2d.fillOval((int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r)); + g2d.fillOval((int) (x[id] - r), (int) (y[id] - r), (int) (2 * r), (int) (2 * r)); } } @@ -228,11 +423,18 @@ public class RenderPerfTest { } } + static class WhiteTextParticleRenderer implements ParticleRenderer { - float r; + final float r; + final Font font; WhiteTextParticleRenderer(float r) { + this(r, TEXT_SIZE_DEFAULT); + } + + WhiteTextParticleRenderer(float r, int fontSize) { this.r = r; + font = new Font(TEXT_FONT, Font.PLAIN, fontSize); } void setPaint(Graphics2D g2d, int id) { @@ -242,12 +444,10 @@ public class RenderPerfTest { @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { setPaint(g2d, id); - g2d.drawString("The quick brown fox jumps over the lazy dog", - (int)(x[id] - r), (int)(y[id] - r)); - g2d.drawString("The quick brown fox jumps over the lazy dog", - (int)(x[id] - r), (int)y[id]); - g2d.drawString("The quick brown fox jumps over the lazy dog", - (int)(x[id] - r), (int)(y[id] + r)); + g2d.setFont(font); + g2d.drawString(TEXT_STR, (int) (x[id] - r), (int) (y[id] - r)); + g2d.drawString(TEXT_STR, (int) (x[id] - r), (int) y[id]); + g2d.drawString(TEXT_STR, (int) (x[id] - r), (int) (y[id] + r)); } } @@ -257,7 +457,11 @@ public class RenderPerfTest { float r; TextParticleRenderer(int n, float r) { - super(r); + this(n,r, TEXT_SIZE_DEFAULT); + } + + TextParticleRenderer(int n, float r, int fontSize) { + super(r, fontSize); colors = new Color[n]; this.r = r; for (int i = 0; i < n; i++) { @@ -274,27 +478,18 @@ public class RenderPerfTest { static class LargeTextParticleRenderer extends TextParticleRenderer { LargeTextParticleRenderer(int n, float r) { - super(n, r); + super(n, r, TEXT_SIZE_LARGE); } @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { - setPaint(g2d, id); if (id % 100 != 0) return; - Font font = new Font("LucidaGrande", Font.PLAIN, 32); - g2d.setFont(font); - g2d.drawString("The quick brown fox jumps over the lazy dog", - (int)(x[id] - r), (int)(y[id] - r)); - g2d.drawString("The quick brown fox jumps over the lazy dog", - (int)(x[id] - r), (int)y[id]); - g2d.drawString("The quick brown fox jumps over the lazy dog", - (int)(x[id] - r), (int)(y[id] + r)); + super.render(g2d, id, x, y, vx, vy); } } static class FlatOvalRotParticleRenderer extends FlatParticleRenderer { - FlatOvalRotParticleRenderer(int n, float r) { super(n, r); } @@ -314,10 +509,10 @@ public class RenderPerfTest { } g2d.translate(x[id], y[id]); g2d.rotate(Math.acos(l)); - g2d.fillOval(-(int)r, (int)(-0.5*r), (int) (2 * r), (int)r); + g2d.fillOval(-(int) r, (int) (-0.5 * r), (int) (2 * r), (int) r); g2d.setTransform(t); } else { - g2d.fillOval((int)(x[id] - r), (int)(y[id] - 0.5*r), + g2d.fillOval((int) (x[id] - r), (int) (y[id] - 0.5 * r), (int) (2 * r), (int) r); } } @@ -325,48 +520,43 @@ public class RenderPerfTest { static class LinGradOvalRotParticleRenderer extends FlatOvalRotParticleRenderer { - LinGradOvalRotParticleRenderer(int n, float r) { super(n, r); } @Override void setPaint(Graphics2D g2d, int id) { - Point2D start = new Point2D.Double(- r, - 0.5*r); - Point2D end = new Point2D.Double( 2 * r, r); + Point2D start = new Point2D.Double(-r, -0.5 * r); + Point2D end = new Point2D.Double(2 * r, r); float[] dist = {0.0f, 1.0f}; - Color[] cls = {colors[id %colors.length], colors[(colors.length - id) %colors.length]}; - LinearGradientPaint p = - new LinearGradientPaint(start, end, dist, cls); + Color[] cls = {colors[id % colors.length], colors[(colors.length - id) % colors.length]}; + LinearGradientPaint p = new LinearGradientPaint(start, end, dist, cls); g2d.setPaint(p); } } static class LinGrad3OvalRotParticleRenderer extends FlatOvalRotParticleRenderer { - LinGrad3OvalRotParticleRenderer(int n, float r) { super(n, r); } @Override void setPaint(Graphics2D g2d, int id) { - Point2D start = new Point2D.Double(- r, - 0.5*r); - Point2D end = new Point2D.Double( 2 * r, r); + Point2D start = new Point2D.Double(-r, -0.5 * r); + Point2D end = new Point2D.Double(2 * r, r); float[] dist = {0.0f, 0.5f, 1.0f}; Color[] cls = { - colors[id %colors.length], - colors[(colors.length - id) %colors.length], - colors[(id*5) %colors.length]}; - LinearGradientPaint p = - new LinearGradientPaint(start, end, dist, cls); + colors[id % colors.length], + colors[(colors.length - id) % colors.length], + colors[(id * 5) % colors.length]}; + LinearGradientPaint p = new LinearGradientPaint(start, end, dist, cls); g2d.setPaint(p); } } static class RadGrad3OvalRotParticleRenderer extends FlatOvalRotParticleRenderer { - RadGrad3OvalRotParticleRenderer(int n, float r) { super(n, r); } @@ -376,36 +566,33 @@ public class RenderPerfTest { Point2D start = new Point2D.Double(); float[] dist = {0.0f, 0.5f, 1.0f}; Color[] cls = { - colors[id %colors.length], - colors[(colors.length - id) %colors.length], - colors[(id*5) %colors.length]}; - RadialGradientPaint p = - new RadialGradientPaint(start, r, dist, cls); + colors[id % colors.length], + colors[(colors.length - id) % colors.length], + colors[(id * 5) % colors.length]}; + RadialGradientPaint p = new RadialGradientPaint(start, r, dist, cls); g2d.setPaint(p); } } static class FlatBoxParticleRenderer extends FlatParticleRenderer { - FlatBoxParticleRenderer(int n, float r) { super(n, r); } + @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { g2d.setColor(colors[id % colors.length]); - g2d.fillRect((int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r)); - + g2d.fillRect((int) (x[id] - r), (int) (y[id] - r), (int) (2 * r), (int) (2 * r)); } - } static class ClipFlatBoxParticleRenderer extends FlatParticleRenderer { - ClipFlatBoxParticleRenderer(int n, float r) { super(n, r); } + @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { if ((id % 10) == 0) { @@ -434,17 +621,54 @@ public class RenderPerfTest { @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { g2d.setColor(colors[id % colors.length]); - g2d.drawImage(dukeImg, (int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r), null); + g2d.drawImage(dukeImg, (int) (x[id] - r), (int) (y[id] - r), (int) (2 * r), (int) (2 * r), null); } } - static class FlatBoxRotParticleRenderer extends FlatParticleRenderer { + static class VolImgParticleRenderer extends ImgParticleRenderer { + VolatileImage volImg; + VolImgParticleRenderer(int n, float r) { + super(n, r); + } + + @Override + public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { + GraphicsConfiguration config = g2d.getDeviceConfiguration(); + if (volImg == null) { + volImg = config.createCompatibleVolatileImage(dukeImg.getWidth(), dukeImg.getHeight(), + Transparency.TRANSLUCENT); + Graphics2D g = volImg.createGraphics(); + g.setComposite(AlphaComposite.Src); + g.drawImage(dukeImg, null, null); + g.dispose(); + } else { + int status = volImg.validate(config); + if (status == VolatileImage.IMAGE_INCOMPATIBLE) { + volImg = config.createCompatibleVolatileImage(dukeImg.getWidth(), dukeImg.getHeight(), + Transparency.TRANSLUCENT); + } + if (status != VolatileImage.IMAGE_OK) { + Graphics2D g = volImg.createGraphics(); + g.setComposite(AlphaComposite.Src); + g.drawImage(dukeImg, null, null); + g.dispose(); + } + } + Composite savedComposite = g2d.getComposite(); + g2d.setComposite(AlphaComposite.SrcOver); + g2d.drawImage(volImg, (int) (x[id] - r), (int) (y[id] - r), (int) (2 * r), (int) (2 * r), null); + g2d.setComposite(savedComposite); + } + } + + static class FlatBoxRotParticleRenderer extends FlatParticleRenderer { FlatBoxRotParticleRenderer(int n, float r) { super(n, r); } + @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { g2d.setColor(colors[id % colors.length]); @@ -456,10 +680,10 @@ public class RenderPerfTest { } g2d.translate(x[id], y[id]); g2d.rotate(Math.acos(l)); - g2d.fillRect(-(int)r, -(int)r, (int) (2 * r), (int) (2 * r)); + g2d.fillRect(-(int) r, -(int) r, (int) (2 * r), (int) (2 * r)); g2d.setTransform(t); } else { - g2d.fillRect((int)(x[id] - r), (int)(y[id] - r), + g2d.fillRect((int) (x[id] - r), (int) (y[id] - r), (int) (2 * r), (int) (2 * r)); } } @@ -467,17 +691,17 @@ public class RenderPerfTest { static class WiredParticleRenderer extends FlatParticleRenderer { - WiredParticleRenderer(int n, float r) { super(n, r); } + @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { g2d.setColor(colors[id % colors.length]); - g2d.drawOval((int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r)); + g2d.drawOval((int) (x[id] - r), (int) (y[id] - r), (int) (2 * r), (int) (2 * r)); } - } + static class WiredBoxParticleRenderer extends FlatParticleRenderer { WiredBoxParticleRenderer(int n, float r) { @@ -487,10 +711,10 @@ public class RenderPerfTest { @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { g2d.setColor(colors[id % colors.length]); - g2d.drawRect((int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r)); + g2d.drawRect((int) (x[id] - r), (int) (y[id] - r), (int) (2 * r), (int) (2 * r)); } - } + static class SegParticleRenderer extends FlatParticleRenderer { SegParticleRenderer(int n, float r) { @@ -499,17 +723,15 @@ public class RenderPerfTest { @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { - double v = Math.sqrt(vx[id]*vx[id]+vy[id]*vy[id]); - float nvx = (float) (vx[id]/v); - float nvy = (float) (vy[id]/v); + double v = Math.sqrt(vx[id] * vx[id] + vy[id] * vy[id]); + float nvx = (float) (vx[id] / v); + float nvy = (float) (vy[id] / v); g2d.setColor(colors[id % colors.length]); - g2d.drawLine((int)(x[id] - r*nvx), (int)(y[id] - r*nvy), - (int)(x[id] + 2*r*nvx), (int)(y[id] + 2*r*nvy)); + g2d.drawLine((int) (x[id] - r * nvx), (int) (y[id] - r * nvy), + (int) (x[id] + 2 * r * nvx), (int) (y[id] + 2 * r * nvy)); } - } - static class WiredQuadParticleRenderer extends FlatParticleRenderer { WiredQuadParticleRenderer(int n, float r) { @@ -520,9 +742,8 @@ public class RenderPerfTest { public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { if (id > 2 && (id % 3) == 0) { g2d.setColor(colors[id % colors.length]); - g2d.draw(new QuadCurve2D.Float(x[id-3], y[id-3], x[id-2], y[id-2], x[id-1], y[id-1])); + g2d.draw(new QuadCurve2D.Float(x[id - 3], y[id - 3], x[id - 2], y[id - 2], x[id - 1], y[id - 1])); } - } } @@ -536,9 +757,8 @@ public class RenderPerfTest { public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { if (id > 2 && (id % 3) == 0) { g2d.setColor(colors[id % colors.length]); - g2d.fill(new QuadCurve2D.Float(x[id-3], y[id-3], x[id-2], y[id-2], x[id-1], y[id-1])); + g2d.fill(new QuadCurve2D.Float(x[id - 3], y[id - 3], x[id - 2], y[id - 2], x[id - 1], y[id - 1])); } - } } @@ -553,7 +773,7 @@ public class RenderPerfTest { @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { - g2d.drawImage(image, (int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r), null); + g2d.drawImage(image, (int) (x[id] - r), (int) (y[id] - r), (int) (2 * r), (int) (2 * r), null); } private static void fill(final Image image) { @@ -565,7 +785,6 @@ public class RenderPerfTest { } graphics.dispose(); } - } static class SwBlitImageParticleRenderer extends BlitImageParticleRenderer { @@ -602,398 +821,1594 @@ public class RenderPerfTest { } } - static class PerfMeter { - private String name; + final static class PerfMeter { + private final FrameHandler fh; + private final String name; + private final PerfMeterExecutor executor; - private JPanel panel; - - private double execTime = 0; - private AtomicInteger markerIdx = new AtomicInteger(0); - private int renderedMarkerIdx = -1; - private AtomicLong markerPaintTime = new AtomicLong(0); - - private double fps; - private int skippedFrame = 0; - - PerfMeter(String name) { + PerfMeter(final FrameHandler fh, String name) { + this.fh = fh; this.name = name; + executor = getExecutor(); } - PerfMeter exec(final Renderable renderable) throws Exception { - final CountDownLatch latchFrame = new CountDownLatch(1); + void exec(final Renderable renderable) throws Exception { + executor.exec(name, renderable); + } - final JFrame f = new JFrame(); - f.addWindowListener(new WindowAdapter() { - @Override - public void windowClosed(WindowEvent e) { - latchFrame.countDown(); + private PerfMeterExecutor getExecutor() { + switch (EXEC_MODE) { + default: + case EXEC_MODE_ROBOT: + return new PerfMeterRobot(fh); + case EXEC_MODE_BUFFER: + fh.prepareImageProvider(false); + return new PerfMeterImageProvider(fh); + case EXEC_MODE_VOLATILE: + fh.prepareImageProvider(true); + return new PerfMeterImageProvider(fh); + } + } + } + + static void paintTest(final Renderable renderable, final Graphics2D g2d, + final Color markerColor, final boolean doSync) { + // clip to frame: + g2d.setClip(0, 0, IMAGE_W, IMAGE_H); + // clear background: + g2d.setColor(Color.BLACK); + g2d.fillRect(0, 0, IMAGE_W, IMAGE_H); + + // render test: + renderable.setup(g2d, true); + renderable.render(g2d); + renderable.setup(g2d, false); + + // draw marker at end: + g2d.setClip(0, 0, BW, BH); + g2d.setColor(markerColor); + g2d.fillRect(0, 0, BW, BH); + + if (doSync) { + // synchronize toolkit: + TOOLKIT.sync(); + } + } + + final static class FrameHandler { + + private boolean calibrate = VERBOSE; + + private int threadId = -1; + private int frameId = -1; + + private final GraphicsConfiguration gc; + + private JFrame frame = null; + + private final CountDownLatch latchShownFrame = new CountDownLatch(1); + private final CountDownLatch latchClosedFrame = new CountDownLatch(1); + + private ImageProvider imageProvider = null; + + FrameHandler(GraphicsConfiguration gc) { + this.gc = gc; + } + + void setIds(int threadId, int frameId) { + this.threadId = threadId; + this.frameId = frameId; + } + + void prepareFrameEDT(final String title) { + if (frame == null) { + frame = new JFrame(gc); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.addComponentListener(new ComponentAdapter() { + @Override + public void componentShown(ComponentEvent e) { + latchShownFrame.countDown(); + } + }); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + latchClosedFrame.countDown(); + } + }); + } + frame.setTitle(title); + } + + void showFrameEDT(final JPanel panel) { + if (frame != null) { + panel.setPreferredSize(new Dimension(IMAGE_W, IMAGE_H)); + panel.setBackground(Color.BLACK); + + frame.getContentPane().removeAll(); + frame.getContentPane().add(panel); + frame.getContentPane().revalidate(); + + if (!frame.isVisible()) { + if (frameId != -1) { + final int off = (frameId - 1) * 100; + final Rectangle gcBounds = gc.getBounds(); + final int xoff = gcBounds.x + off; + final int yoff = gcBounds.y + off; + + if ((xoff != 0) || (yoff != 0)) { + frame.setLocation(xoff, yoff); + } + } + frame.pack(); + frame.setVisible(true); } - }); + } + } + + void waitFrameShown() throws Exception { + latchShownFrame.await(); + } + + void resetFrame() throws Exception { + if (frame != null) { + SwingUtilities.invokeAndWait(new Runnable() { + @Override + public void run() { + frame.getContentPane().removeAll(); + frame.getContentPane().revalidate(); + } + }); + } + } + + void repaintFrame() throws Exception { + if (frame != null) { + SwingUtilities.invokeAndWait(new Runnable() { + @Override + public void run() { + frame.repaint(); + } + }); + } + } + + private void waitFrameHidden() throws Exception { + latchClosedFrame.await(); + } + + void hideFrameAndWait() throws Exception { + if (frame != null) { + SwingUtilities.invokeAndWait(new Runnable() { + @Override + public void run() { + frame.setVisible(false); + frame.dispose(); + frame = null; + } + }); + waitFrameHidden(); + } + } + + void prepareImageProvider(final boolean useVolatile) { + if (this.imageProvider == null) { + this.imageProvider = new ImageProvider(useVolatile); + } + } + } + + static abstract class PerfMeterExecutor { + + protected final static int SCORE_MAIN = 0; + protected final static int SCORE_ERROR = 1; + protected final static int SCORE_OTHER = 2; + + protected final static int PCT_00 = 0; + protected final static int PCT_10 = 1; + protected final static int PCT_25 = 2; + protected final static int PCT_50 = 3; + protected final static int PCT_75 = 4; + protected final static int PCT_90 = 5; + protected final static int PCT_100 = 6; + + private final static IntBinaryOperator INC_MOD_FUNC = new IntBinaryOperator() { + public int applyAsInt(int x, int y) { + return (x + 1) % y; + } + }; + + private static final AtomicInteger headerMark = new AtomicInteger(1); + + /* members */ + protected final FrameHandler fh; + protected final boolean skipWait; + + protected final AtomicInteger paintIdx = new AtomicInteger(0); + protected final AtomicInteger markerIdx = new AtomicInteger(0); + protected final AtomicLong markerStartTime = new AtomicLong(0); + protected final AtomicLong markerPaintTime = new AtomicLong(0); + + protected String name = null; + protected int skippedFrames = 0; + + protected int frames = 0; + // test timestamp data: + protected long[] testTimestamp = new long[MAX_SAMPLE_COUNT]; + // test duration data (ns): + protected long[] testTime = new long[MAX_SAMPLE_COUNT]; + + protected final double[] scores = new double[SCORE_OTHER + 1]; + protected final double[] results = new double[PCT_100 + 1]; + + protected PerfMeterExecutor(final boolean skipWait, final FrameHandler fh) { + this.skipWait = skipWait; + this.fh = fh; + } + + protected void beforeExec() { + } + + protected void afterExec() { + } + + protected void reset() { + paintIdx.set(0); + markerIdx.set(0); + markerStartTime.set(0); + markerPaintTime.set(0); + } + + protected void updateMarkerIdx() { + markerIdx.accumulateAndGet(MARKER.length, INC_MOD_FUNC); + } + + protected final void exec(final String testName, final Renderable renderable) throws Exception { + if (TRACE) System.out.print("\n!"); + this.name = testName + (isMultiThreads() ? ("-" + fh.threadId) : ""); SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { + fh.prepareFrameEDT(name); + // call beforeExec() after frame is created: + beforeExec(); - panel = new JPanel() { + final JPanel panel = new JPanel() { @Override protected void paintComponent(Graphics g) { - super.paintComponent(g); - int idx = markerIdx.get(); - if (idx != renderedMarkerIdx) { - markerPaintTime.set(System.nanoTime()); - } - - Graphics2D g2d = (Graphics2D) g.create(); - renderable.setup(g2d); - renderable.render(g2d); - g2d.setClip(null); - g2d.setPaintMode(); - g2d.setColor(marker[idx]); - g2d.fillRect(0, 0, BW, BH); - renderedMarkerIdx = idx; + if (TRACE) System.out.print("P"); + paintPanel(renderable, g); + if (TRACE) System.out.print("Q"); } }; - - panel.setPreferredSize(new Dimension((int) (WIDTH + BW), (int) (HEIGHT + BH))); - panel.setBackground(Color.BLACK); - f.add(panel); - f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - f.pack(); - f.setVisible(true); + fh.showFrameEDT(panel); + if (TRACE) System.out.print(">>"); } }); - Robot robot = new Robot(); - int cycle = 0; - int frame = 0; - long paintTime = 0; - int maxFrameCycle = -1; - while (frame < COUNT) { + // Wait frame to be shown: + fh.waitFrameShown(); + + if (TRACE) System.out.print(":"); + + // Reset before warmup: + reset(); + + if (WARMUP_COUNT > 0) { + // Warmup to prepare frame synchronization: + for (int i = 0; i < WARMUP_COUNT; i++) { + updateMarkerIdx(); + renderable.update(); + fh.repaintFrame(); + sleep(10); + while (markerStartTime.get() == 0) { + if (TRACE) System.out.print("-"); + sleep(1); + } + markerStartTime.set(0); + } + // Reset before measurements: + reset(); + } + if (TRACE) System.out.print(":>>"); + + // signal thread is ready for test + readyCount.countDown(); + if (TRACE_SYNC) traceSync(name + " ready => waiting start signal..."); + + // wait start signal: + triggerStart.await(); + // Run Benchmark (all threads): + if (TRACE_SYNC) traceSync(name + " benchmark started"); + + int cycles = 0; + frames = 0; + long paintStartTime = 0L; + long paintElapsedTime = 0L; + long lastFrameTime = 0L; + + final long startTime = System.nanoTime(); + final long minTime = startTime + MIN_MEASURE_TIME_NS; + final long endTime = startTime + MAX_MEASURE_TIME_NS; + + // Start 1st measurement: + fh.repaintFrame(); + + for (; ; ) { long t; - if ((t = markerPaintTime.getAndSet(0)) > 0) { - paintTime = t; - maxFrameCycle = cycle + MAX_FRAME_CYCLES; + if ((t = markerStartTime.getAndSet(0L)) > 0L) { + paintStartTime = t; + if (TRACE) System.out.print("|"); } - if (paintTime > 0) { - Color c = robot.getPixelColor( - panel.getTopLevelAncestor().getX() + panel.getTopLevelAncestor().getInsets().left + BW / 2, - panel.getTopLevelAncestor().getY() + panel.getTopLevelAncestor().getInsets().top + BW / 2); + boolean wait = true; - if (isAlmostEqual(c, marker[markerIdx.get()])) { - execTime += System.nanoTime() - paintTime; - frame++; - paintTime = 0; - maxFrameCycle = -1; - markerIdx.accumulateAndGet(marker.length, (x, y) -> (x + 1) % y); + if (paintStartTime > 0L) { + // get optional elapsed time: + paintElapsedTime = markerPaintTime.get(); + + if (TRACE) System.out.print("."); + wait = !skipWait; + final Color c = getMarkerColor(); + + if (isAlmostEqual(c, MARKER[markerIdx.get()])) { + final long durationNs = getElapsedTime((paintElapsedTime != 0L) ? paintElapsedTime : paintStartTime); + if ((durationNs > 0L) && (frames < MAX_SAMPLE_COUNT)) { + testTimestamp[frames] = paintStartTime - startTime; + testTime[frames] = durationNs; + } + if (REPORT_OVERALL_FPS) { + lastFrameTime = System.nanoTime(); + } + if (TRACE) System.out.print("R"); + frames++; + paintStartTime = 0L; + paintElapsedTime = 0L; + cycles = 0; + updateMarkerIdx(); renderable.update(); - panel.getParent().repaint(); - } else if (cycle >= maxFrameCycle) { - skippedFrame++; - paintTime = 0; - maxFrameCycle = -1; - markerIdx.accumulateAndGet(marker.length, (x, y) -> (x + 1) % y); - panel.getParent().repaint(); + fh.repaintFrame(); + } else if (cycles >= MAX_FRAME_CYCLES) { + if (TRACE) System.out.print("M"); + skippedFrames++; + paintStartTime = 0L; + paintElapsedTime = 0L; + cycles = 0; + updateMarkerIdx(); + fh.repaintFrame(); + } else { + if (TRACE) System.out.print("-"); } } - try { - Thread.sleep(CYCLE_DELAY); - } catch (InterruptedException ex) { - ex.printStackTrace(); - } - if (cycle >= MAX_MEASURE_CYCLES) { + final long currentTime = System.nanoTime(); + if ((frames >= MIN_COUNT) && (currentTime >= endTime)) { break; } - cycle++; - } - SwingUtilities.invokeAndWait(() -> { - f.setVisible(false); - f.dispose(); - }); + if ((frames >= COUNT) && (currentTime >= minTime)) { + break; + } + if (wait) { + sleep(CYCLE_DELAY); + } + cycles++; + } // end measurements - latchFrame.await(); - if (execTime != 0 && frame != 0) { - fps = 1e9 / (execTime / frame); - } else { - fps = 0; + // signal test completed: + completedCount.countDown(); + if (TRACE_SYNC) traceSync(name + " completed => waiting stop signal..."); + + // wait stop signal: + triggerStop.await(); + // Stop Benchmark (all threads): + if (TRACE_SYNC) traceSync(name + " stopped"); + + if (DELAY_TEST) { + sleep(1000); + } + fh.resetFrame(); + + // Process results: + if (REPORT_OVERALL_FPS && (lastFrameTime != 0)) { + final double elapsedTime = (lastFrameTime - startTime); + final double elapsedFPS = 1000000000.0 * frames / elapsedTime; + + System.err.println(frames + " in " + (elapsedTime / 1000000) + " ms: ~ " + elapsedFPS + " FPS"); } - return this; + processTimes(); + + if (TRACE) System.out.print("<<\n"); + afterExec(); + + // Log header once: + if (headerMark.getAndDecrement() == 1) { + System.err.println(getHeader()); + } + + // Log report: + System.err.println(getResults()); + + // signal test done: + doneCount.countDown(); + if (TRACE_SYNC) traceSync(name + " done => waiting exit signal..."); + + // wait exit signal: + triggerExit.await(); + // Stop Benchmark (all threads): + if (TRACE_SYNC) traceSync(name + " exited"); } - private void report() { - if (skippedFrame > 0) { - System.err.println(skippedFrame + " frame(s) skipped"); - } - System.err.println(name + " : " + String.format("%.2f FPS", fps)); + protected abstract void paintPanel(final Renderable renderable, final Graphics g); + + protected abstract long getElapsedTime(long paintTime); + + protected abstract Color getMarkerColor() throws Exception; + + protected boolean isAlmostEqual(Color c1, Color c2) { + return (Math.abs(c1.getRed() - c2.getRed()) < COLOR_TOLERANCE) && + (Math.abs(c1.getGreen() - c2.getGreen()) < COLOR_TOLERANCE) && + (Math.abs(c1.getBlue() - c2.getBlue()) < COLOR_TOLERANCE); } - private boolean isAlmostEqual(Color c1, Color c2) { - return Math.abs(c1.getRed() - c2.getRed()) < COLOR_TOLERANCE && - Math.abs(c1.getGreen() - c2.getGreen()) < COLOR_TOLERANCE && - Math.abs(c1.getBlue() - c2.getBlue()) < COLOR_TOLERANCE; + protected void processTimes() { + if (frames != 0) { + frames = Math.min(frames, MAX_SAMPLE_COUNT); + if (DUMP_SAMPLES) { + // Dump all results: + final File file = new File("./rp-" + replaceNonFileNameChars(name) + "-samples.csv"); + System.out.println("Writing samples to : " + file.getAbsolutePath()); + + try (final PrintWriter w = new PrintWriter(file, Charset.forName("UTF-8"))) { + w.write("# "); + w.write(VERSION); + w.write(" - "); + w.write("Test: "); + w.write(name); + w.write('\n'); + + for (int i = 0; i < frames; i++) { + w.write(Double.toString(millis(testTimestamp[i]))); + w.write(','); + w.write(Double.toString(millis(testTime[i]))); + w.write('\n'); + } + } catch (IOException ioe) { + System.err.println("IO exception:"); + ioe.printStackTrace(); + } + } + // Ignore first 10% (warmup at the beginning): + final int first = (int) Math.floor(frames * 0.10); + final int last = frames - 1; + + // free testTimestamp to avoid any future usage: + testTimestamp = null; + + // note: testTime array is modified below: + // Sort values to get percentiles: + Arrays.sort(testTime, first, frames); + + final long[] pcts = getPercentiles(testTime, first, last); + + final long median = pcts[PCT_50]; + + if (USE_FPS) { + scores[SCORE_MAIN] = fps(median); + + results[PCT_100] = fps(pcts[PCT_00]); + results[PCT_90] = fps(pcts[PCT_10]); + results[PCT_75] = fps(pcts[PCT_25]); + results[PCT_50] = fps(pcts[PCT_50]); + results[PCT_25] = fps(pcts[PCT_75]); + results[PCT_10] = fps(pcts[PCT_90]); + results[PCT_00] = fps(pcts[PCT_100]); + + // STDDEV = IQR / 1.35 = (Q3 - Q1) * 20 / 27 + scores[SCORE_ERROR] = (results[PCT_75] - results[PCT_25]) * 20L / 27L; + scores[SCORE_OTHER] = millis(median); + } else { + scores[SCORE_MAIN] = millis(median); + + results[PCT_00] = millis(pcts[PCT_00]); + results[PCT_10] = millis(pcts[PCT_10]); + results[PCT_25] = millis(pcts[PCT_25]); + results[PCT_50] = millis(pcts[PCT_50]); + results[PCT_75] = millis(pcts[PCT_75]); + results[PCT_90] = millis(pcts[PCT_90]); + results[PCT_100] = millis(pcts[PCT_100]); + + // STDDEV = IQR / 1.35 = (Q3 - Q1) * 20 / 27 (normal distribution ?) + scores[SCORE_ERROR] = (results[PCT_75] - results[PCT_25]) * 20L / 27L; + scores[SCORE_OTHER] = fps(median); + + // System.out.println("stddev(IQR) = " + scores[SCORE_ERROR]); + + // MAD = Median Absolute Deviation: + for (int i = first; i <= last; i++) { + testTime[i] = Math.abs(testTime[i] - median); + } + // Sort values to get percentiles: + Arrays.sort(testTime, first, frames); + + // STDDEV = 1.4826 * MAD (normal distribution ?) + scores[SCORE_ERROR] = 1.4826 * millis(testTime[pctIndex(first, last, 0.50)]); // 50% (median) + + // System.out.println("stddev(MAD) = " + scores[SCORE_ERROR]); + } + // free testTime to avoid any future usage: + testTime = null; + } + } + + protected static String getHeader() { + if (VERBOSE) { + return String.format("%-25s : %s ± %s %s [%s] (p00: ... p10: ... p25: ... p50: ... p75: ... p90: ... p100: ... %s) (... frames)", + "Test Name", (USE_FPS ? "Median(FPS)" : "Median(TimeMs)"), (USE_FPS ? "Stddev(FPS)" : "Stddev(TimeMs)"), "Unit", + (!USE_FPS ? "Median(FPS)" : "Median(TimeMs)"), "Unit"); + } + return String.format("%-25s : %s ± %s %s", + "Test Name", (USE_FPS ? "Median(FPS)" : "Median(TimeMs)"), (USE_FPS ? "Stddev(FPS)" : "Stddev(TimeMs)"), "Unit"); + + } + + protected String getResults() { + if (skippedFrames > 0) { + System.err.println(name + " : " + skippedFrames + " frame(s) skipped"); + } + if (VERBOSE) { + return String.format("%-25s : %.3f ± %.3f %s [%.3f %s] (p00: %.3f p10: %.3f p25: %.3f p50: %.3f p75: %.3f p90: %.3f p100: %.3f %s) (%d frames)", + name, scores[SCORE_MAIN], scores[SCORE_ERROR], (USE_FPS ? "FPS" : "ms"), + scores[SCORE_OTHER], (USE_FPS ? "ms" : "FPS"), + results[PCT_00], results[PCT_10], results[PCT_25], results[PCT_50], results[PCT_75], results[PCT_90], results[PCT_100], + (USE_FPS ? "FPS" : "ms"), + frames); + } + return String.format("%-25s : %.3f ± %.3f %s", + name, scores[SCORE_MAIN], scores[SCORE_ERROR], (USE_FPS ? "FPS" : "ms")); + } + + protected double fps(long timeNs) { + return 1e9 / timeNs; + } + + protected double millis(long timeNs) { + return 1e-6 * timeNs; + } + + protected static long[] getPercentiles(final long[] data, final int first, final int last) { + final long[] pcts = new long[PCT_100 + 1]; + pcts[PCT_00] = data[first]; // 0% (min) + pcts[PCT_10] = data[pctIndex(first, last, 0.10)]; // 10% + pcts[PCT_25] = data[pctIndex(first, last, 0.25)]; // 25% (Q1) + pcts[PCT_50] = data[pctIndex(first, last, 0.50)]; // 50% (Median) + pcts[PCT_75] = data[pctIndex(first, last, 0.75)]; // 75% (Q3) + pcts[PCT_90] = data[pctIndex(first, last, 0.90)]; // 90% + pcts[PCT_100] = data[pctIndex(first, last, 1.00)]; // 100% (max) + return pcts; + } + + protected static int pctIndex(final int min, final int last, final double pct) { + return min + (int) Math.round((last - min) * pct); } } - private static final Particles balls = new Particles(N, R, BW, BH, WIDTH, HEIGHT); - private static final ParticleRenderer flatRenderer = new FlatParticleRenderer(N, R); - private static final ParticleRenderer clipFlatRenderer = new ClipFlatParticleRenderer(N, R); - private static final ParticleRenderer flatOvalRotRenderer = new FlatOvalRotParticleRenderer(N, R); - private static final ParticleRenderer flatBoxRenderer = new FlatBoxParticleRenderer(N, R); - private static final ParticleRenderer clipFlatBoxParticleRenderer = new ClipFlatBoxParticleRenderer(N, R); - private static final ParticleRenderer flatBoxRotRenderer = new FlatBoxRotParticleRenderer(N, R); - private static final ParticleRenderer linGradOvalRotRenderer = new LinGradOvalRotParticleRenderer(N, R); - private static final ParticleRenderer linGrad3OvalRotRenderer = new LinGrad3OvalRotParticleRenderer(N, R); - private static final ParticleRenderer radGrad3OvalRotRenderer = new RadGrad3OvalRotParticleRenderer(N, R); - private static final ParticleRenderer wiredRenderer = new WiredParticleRenderer(N, R); - private static final ParticleRenderer wiredBoxRenderer = new WiredBoxParticleRenderer(N, R); - private static final ParticleRenderer segRenderer = new SegParticleRenderer(N, R); - private static final ParticleRenderer flatQuadRenderer = new FlatQuadParticleRenderer(N, R); - private static final ParticleRenderer wiredQuadRenderer = new WiredQuadParticleRenderer(N, R); - private static final ParticleRenderer imgRenderer = new ImgParticleRenderer(N, R); - private static final ParticleRenderer textRenderer = new TextParticleRenderer(N, R); - private static final ParticleRenderer largeTextRenderer = new LargeTextParticleRenderer(N, R); - private static final ParticleRenderer whiteTextRenderer = new WhiteTextParticleRenderer(R); - private static final ParticleRenderer argbSwBlitImageRenderer = new SwBlitImageParticleRenderer(N, R, BufferedImage.TYPE_INT_ARGB); - private static final ParticleRenderer bgrSwBlitImageRenderer = new SwBlitImageParticleRenderer(N, R, BufferedImage.TYPE_INT_BGR); - private static final ParticleRenderer argbSurfaceBlitImageRenderer = new SurfaceBlitImageParticleRenderer(N, R, BufferedImage.TYPE_INT_ARGB); - private static final ParticleRenderer bgrSurfaceBlitImageRenderer = new SurfaceBlitImageParticleRenderer(N, R, BufferedImage.TYPE_INT_BGR); + final static class PerfMeterRobot extends PerfMeterExecutor { - private static final Configurable AA = (Graphics2D g2d) -> - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); + private int nRobotTimes = 0; + private long[] robotTime = (fh.calibrate) ? new long[COUNT] : null; - private static final Configurable TextLCD = (Graphics2D g2d) -> - g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, - RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB); + private int nDelayTimes = 0; + private long[] delayTime = (ROBOT_TIME_DELAY) ? null : new long[COUNT]; - private static final Configurable TextAA = (Graphics2D g2d) -> - g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, - RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + private long lastPaintTime = 0L; + private int renderedMarkerIdx = -1; - private static final Configurable XORMode = (Graphics2D g2d) -> - {g2d.setXORMode(Color.WHITE);}; + private Robot robot = null; - private static final Configurable XORModeLCDText = (Graphics2D g2d) -> - {g2d.setXORMode(Color.WHITE); - g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, - RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);}; + PerfMeterRobot(final FrameHandler fh) { + super(true, fh); + } + protected void beforeExec() { + try { + robot = new Robot(); + } catch (AWTException ae) { + throw new RuntimeException(ae); + } + } + + protected void reset() { + super.reset(); + nRobotTimes = 0; + nDelayTimes = 0; + lastPaintTime = 0L; + renderedMarkerIdx = -1; + } + + protected void paintPanel(final Renderable renderable, final Graphics g) { + final int idx = markerIdx.get(); + final long start = System.nanoTime(); + + final Graphics2D g2d = (Graphics2D) g.create(); + try { + paintTest(renderable, g2d, MARKER[idx], false); + } finally { + g2d.dispose(); + } + + // Update paintIdx: + paintIdx.incrementAndGet(); + + // publish start time: + if (idx != renderedMarkerIdx) { + renderedMarkerIdx = idx; + markerStartTime.set(start); + } + } + + protected long getElapsedTime(final long paintTime) { + final long now = System.nanoTime(); + long duration = (!ROBOT_TIME_DELAY) ? roundDuration(now - paintTime) : 0L; + if (lastPaintTime != 0L) { + final long delay = roundDuration(now - lastPaintTime); + if (ROBOT_TIME_DELAY) { + duration = delay; + } else if (nDelayTimes < COUNT) { + delayTime[nDelayTimes++] = delay; + } + } + lastPaintTime = now; + return duration; + } + + private static long roundDuration(final long durationNs) { + return (durationNs <= 0L) ? 0L : ( + (ROBOT_TIME_ROUND) ? + FRAME_PREC_IN_NANOS * (long) Math.rint(((double) durationNs) / FRAME_PREC_IN_NANOS) : durationNs + ); + } + + protected Color getMarkerColor() { + final Point frameOffset = fh.frame.getLocationOnScreen(); + final Insets insets = fh.frame.getInsets(); + final int px = frameOffset.x + insets.left + BW / 2; + final int py = frameOffset.y + insets.top + BH / 2; + + final long beforeRobot = (fh.calibrate) ? System.nanoTime() : 0L; + + final Color c = robot.getPixelColor(px, py); + + if ((fh.calibrate) && (nRobotTimes < COUNT)) { + robotTime[nRobotTimes++] = System.nanoTime() - beforeRobot; + } + return c; + } + + protected String getResults() { + if (fh.calibrate && (nRobotTimes != 0)) { + fh.calibrate = false; // only first time + + Arrays.sort(robotTime); + + final long[] pcts = getPercentiles(robotTime, 0, nRobotTimes - 1); + + // free testTime to avoid any future usage: + testTime = null; + + System.err.printf("%-25s : %.3f ms (p00: %.3f p10: %.3f p25: %.3f p50: %.3f p75: %.3f p90: %.3f p100: %.3f ms) (%d times)%n", + "Robot" + (isMultiThreads() ? ("-" + fh.threadId) : ""), millis(pcts[PCT_50]), + millis(pcts[PCT_00]), millis(pcts[PCT_10]), millis(pcts[PCT_25]), millis(pcts[PCT_50]), + millis(pcts[PCT_75]), millis(pcts[PCT_90]), millis(pcts[PCT_100]), nRobotTimes); + } + if (nDelayTimes != 0) { + Arrays.sort(delayTime); + + final long[] pcts = getPercentiles(robotTime, 0, nDelayTimes - 1); + + // free delayTime to avoid any future usage: + delayTime = null; + + System.err.printf("%-25s : %.3f ms [%.3f FPS] (p00: %.3f p10: %.3f p25: %.3f p50: %.3f p75: %.3f p90: %.3f p100: %.3f ms) (%d times)%n", + "DelayTime-" + name + (isMultiThreads() ? ("-" + fh.threadId) : ""), millis(pcts[PCT_50]), fps(pcts[PCT_50]), + millis(pcts[PCT_00]), millis(pcts[PCT_10]), millis(pcts[PCT_25]), millis(pcts[PCT_50]), + millis(pcts[PCT_75]), millis(pcts[PCT_90]), millis(pcts[PCT_100]), nDelayTimes); + } + return super.getResults(); + } + } + + final static class PerfMeterImageProvider extends PerfMeterExecutor { + private final ImageProvider imageProvider; + + PerfMeterImageProvider(final FrameHandler fh) { + super(false, fh); + this.imageProvider = fh.imageProvider; + } + + protected void beforeExec() { + imageProvider.create(fh.frame.getGraphicsConfiguration(), IMAGE_W, IMAGE_H); + } + + protected void afterExec() { + imageProvider.reset(); + } + + protected void paintPanel(final Renderable renderable, final Graphics g) { + // suppose image provider is ready yet + final int idx = markerIdx.get(); + long start = System.nanoTime(); + + // Get Graphics from image provider: + final Graphics2D g2d = imageProvider.createGraphics(); + try { + paintTest(renderable, g2d, MARKER[idx], true); + } finally { + g2d.dispose(); + } + + final long now = System.nanoTime(); + + // Update paintIdx: + paintIdx.incrementAndGet(); + + // publish start time: + markerStartTime.set(start); + // publish elapsed time: + markerPaintTime.set(now - start); + + // Draw image on screen: + g.drawImage(imageProvider.getImage(), 0, 0, null); + } + + protected long getElapsedTime(long paintTime) { + return paintTime; + } + + protected Color getMarkerColor() { + final int px = BW / 2; + final int py = BH / 2; + + return new Color(imageProvider.getSnapshot().getRGB(px, py)); + } + } + + private final static class ImageProvider { + private final static int TRANSPARENCY = Transparency.TRANSLUCENT; + + private final boolean useVolatile; + private Image image = null; + + private ImageProvider(boolean useVolatile) { + this.useVolatile = useVolatile; + } + + void create(GraphicsConfiguration gc, int width, int height) { + this.image = (useVolatile) ? gc.createCompatibleVolatileImage(width, height, TRANSPARENCY) + : gc.createCompatibleImage(width, height, TRANSPARENCY); + } + + public void reset() { + image = null; + } + + public Image getImage() { + return image; + } + + public Graphics2D createGraphics() { + return (useVolatile) ? ((VolatileImage) image).createGraphics() + : ((BufferedImage) image).createGraphics(); + } + + public BufferedImage getSnapshot() { + return (useVolatile) ? ((VolatileImage) image).getSnapshot() + : (BufferedImage) image; + } + } + + private final FrameHandler fh; + + private final Particles balls = new Particles(N, R, BW, BH, WIDTH, HEIGHT); + + private final ParticleRenderer calibRenderer = new CalibrationParticleRenderer(); + private final ParticleRenderer flatRenderer = new FlatParticleRenderer(N, R); + private final ParticleRenderer clipFlatRenderer = new ClipFlatParticleRenderer(N, R); + private final ParticleRenderer flatOvalRotRenderer = new FlatOvalRotParticleRenderer(N, R); + private final ParticleRenderer flatBoxRenderer = new FlatBoxParticleRenderer(N, R); + private final ParticleRenderer clipFlatBoxParticleRenderer = new ClipFlatBoxParticleRenderer(N, R); + private final ParticleRenderer flatBoxRotRenderer = new FlatBoxRotParticleRenderer(N, R); + private final ParticleRenderer linGradOvalRotRenderer = new LinGradOvalRotParticleRenderer(N, R); + private final ParticleRenderer linGrad3OvalRotRenderer = new LinGrad3OvalRotParticleRenderer(N, R); + private final ParticleRenderer radGrad3OvalRotRenderer = new RadGrad3OvalRotParticleRenderer(N, R); + private final ParticleRenderer wiredRenderer = new WiredParticleRenderer(N, R); + private final ParticleRenderer wiredBoxRenderer = new WiredBoxParticleRenderer(N, R); + private final ParticleRenderer segRenderer = new SegParticleRenderer(N, R); + private final ParticleRenderer flatQuadRenderer = new FlatQuadParticleRenderer(N, R); + private final ParticleRenderer wiredQuadRenderer = new WiredQuadParticleRenderer(N, R); + private final ParticleRenderer imgRenderer = new ImgParticleRenderer(N, R); + private final ParticleRenderer volImgRenderer = new VolImgParticleRenderer(N, R); + private final ParticleRenderer textRenderer = new TextParticleRenderer(N, R); + private final ParticleRenderer largeTextRenderer = new LargeTextParticleRenderer(N, R); + private final ParticleRenderer whiteTextRenderer = new WhiteTextParticleRenderer(R); + private final ParticleRenderer argbSwBlitImageRenderer = new SwBlitImageParticleRenderer(N, R, BufferedImage.TYPE_INT_ARGB); + private final ParticleRenderer bgrSwBlitImageRenderer = new SwBlitImageParticleRenderer(N, R, BufferedImage.TYPE_INT_BGR); + private final ParticleRenderer argbSurfaceBlitImageRenderer = new SurfaceBlitImageParticleRenderer(N, R, BufferedImage.TYPE_INT_ARGB); + private final ParticleRenderer bgrSurfaceBlitImageRenderer = new SurfaceBlitImageParticleRenderer(N, R, BufferedImage.TYPE_INT_BGR); + + private final ParticleRenderer textWiredQuadBatchedRenderer = new BatchedParticleRenderer(textRenderer, wiredQuadRenderer); + private final ParticleRenderer textWiredQuadMixedRenderer = new MixedParticleRenderer(textRenderer, wiredQuadRenderer); + + private final ParticleRenderer volImgFlatBoxBatchedRenderer = new BatchedParticleRenderer(volImgRenderer, flatBoxRenderer); + private final ParticleRenderer volImgFlatBoxMixedRenderer = new MixedParticleRenderer(volImgRenderer, flatBoxRenderer); + + private final ParticleRenderer volImgWiredQuadBatchedRenderer = new BatchedParticleRenderer(volImgRenderer, wiredQuadRenderer); + private final ParticleRenderer volImgWiredQuadMixedRenderer = new MixedParticleRenderer(volImgRenderer, wiredQuadRenderer); + + private final ParticleRenderer volImgTextBatchedRenderer = new BatchedParticleRenderer(volImgRenderer, textRenderer); + private final ParticleRenderer volImgTextMixedRenderer = new MixedParticleRenderer(volImgRenderer, textRenderer); + + private final static Configurable AA = new ConfigurableAA(); + private final static Configurable TextAA = new ConfigurableTextAA(); + private final static Configurable TextLCD = new ConfigurableTextLCD(); + private final static Configurable XORMode = new ConfigurableXORMode(); + private final static Configurable XORModeLCDText = new ConfigurableXORModeTextLCD(); + + RenderPerfTest(final GraphicsConfiguration gc) { + fh = new FrameHandler(gc); + } + + ParticleRenderable createPR(final ParticleRenderer renderer) { + return new ParticleRenderable(balls, renderer); + } + + PerfMeter createPerfMeter(final String name) { + return new PerfMeter(fh, name); + } + + public void testCalibration() throws Exception { + createPerfMeter(testName).exec(createPR(calibRenderer)); + } public void testFlatOval() throws Exception { - (new PerfMeter("FlatOval")).exec(createPR(flatRenderer)).report(); + createPerfMeter(testName).exec(createPR(flatRenderer)); } public void testFlatOvalAA() throws Exception { - (new PerfMeter("FlatOvalAA")).exec(createPR(flatRenderer).configure(AA)).report(); + createPerfMeter(testName).exec(createPR(flatRenderer).configure(AA)); } public void testClipFlatOval() throws Exception { - (new PerfMeter("ClipFlatOval")).exec(createPR(clipFlatRenderer)).report(); + createPerfMeter(testName).exec(createPR(clipFlatRenderer)); } public void testClipFlatOvalAA() throws Exception { - (new PerfMeter("ClipFlatOvalAA")).exec(createPR(clipFlatRenderer).configure(AA)).report(); + createPerfMeter(testName).exec(createPR(clipFlatRenderer).configure(AA)); } public void testFlatBox() throws Exception { - (new PerfMeter("FlatBox")).exec(createPR(flatBoxRenderer)).report(); + createPerfMeter(testName).exec(createPR(flatBoxRenderer)); } public void testFlatBoxAA() throws Exception { - (new PerfMeter("FlatBoxAA")).exec(createPR(flatBoxRenderer).configure(AA)).report(); + createPerfMeter(testName).exec(createPR(flatBoxRenderer).configure(AA)); } public void testClipFlatBox() throws Exception { - (new PerfMeter("ClipFlatBox")).exec(createPR(clipFlatBoxParticleRenderer)).report(); + createPerfMeter(testName).exec(createPR(clipFlatBoxParticleRenderer)); } public void testClipFlatBoxAA() throws Exception { - (new PerfMeter("ClipFlatBoxAA")).exec(createPR(clipFlatBoxParticleRenderer).configure(AA)).report(); + createPerfMeter(testName).exec(createPR(clipFlatBoxParticleRenderer).configure(AA)); } public void testImage() throws Exception { - (new PerfMeter("Image")).exec(createPR(imgRenderer)).report(); + createPerfMeter(testName).exec(createPR(imgRenderer)); } public void testImageAA() throws Exception { - (new PerfMeter("ImageAA")).exec(createPR(imgRenderer).configure(AA)).report(); + createPerfMeter(testName).exec(createPR(imgRenderer).configure(AA)); + } + + public void testVolImage() throws Exception { + createPerfMeter(testName).exec(createPR(volImgRenderer)); + } + + public void testVolImageAA() throws Exception { + createPerfMeter(testName).exec(createPR(volImgRenderer).configure(AA)); } public void testRotatedBox() throws Exception { - (new PerfMeter("RotatedBox")).exec(createPR(flatBoxRotRenderer)).report(); + createPerfMeter(testName).exec(createPR(flatBoxRotRenderer)); } public void testRotatedBoxAA() throws Exception { - (new PerfMeter("RotatedBoxAA")).exec(createPR(flatBoxRotRenderer).configure(AA)).report(); + createPerfMeter(testName).exec(createPR(flatBoxRotRenderer).configure(AA)); } public void testRotatedOval() throws Exception { - (new PerfMeter("RotatedOval")).exec(createPR(flatOvalRotRenderer)).report(); + createPerfMeter(testName).exec(createPR(flatOvalRotRenderer)); } public void testRotatedOvalAA() throws Exception { - (new PerfMeter("RotatedOvalAA")).exec(createPR(flatOvalRotRenderer).configure(AA)).report(); + createPerfMeter(testName).exec(createPR(flatOvalRotRenderer).configure(AA)); } public void testLinGrad3RotatedOval() throws Exception { - (new PerfMeter("LinGrad3RotatedOval")).exec(createPR(linGrad3OvalRotRenderer)).report(); + createPerfMeter(testName).exec(createPR(linGrad3OvalRotRenderer)); } public void testLinGrad3RotatedOvalAA() throws Exception { - (new PerfMeter("LinGrad3RotatedOvalAA")).exec(createPR(linGrad3OvalRotRenderer).configure(AA)).report(); + createPerfMeter(testName).exec(createPR(linGrad3OvalRotRenderer).configure(AA)); } public void testRadGrad3RotatedOval() throws Exception { - (new PerfMeter("RadGrad3RotatedOval")).exec(createPR(radGrad3OvalRotRenderer)).report(); + createPerfMeter(testName).exec(createPR(radGrad3OvalRotRenderer)); } public void testRadGrad3RotatedOvalAA() throws Exception { - (new PerfMeter("RadGrad3RotatedOvalAA")).exec(createPR(radGrad3OvalRotRenderer).configure(AA)).report(); + createPerfMeter(testName).exec(createPR(radGrad3OvalRotRenderer).configure(AA)); } public void testLinGradRotatedOval() throws Exception { - (new PerfMeter("LinGradRotatedOval")).exec(createPR(linGradOvalRotRenderer)).report(); + createPerfMeter(testName).exec(createPR(linGradOvalRotRenderer)); } public void testLinGradRotatedOvalAA() throws Exception { - (new PerfMeter("LinGradRotatedOvalAA")).exec(createPR(linGradOvalRotRenderer).configure(AA)).report(); + createPerfMeter(testName).exec(createPR(linGradOvalRotRenderer).configure(AA)); } public void testWiredBubbles() throws Exception { - (new PerfMeter("WiredBubbles")).exec(createPR(wiredRenderer)).report(); + createPerfMeter(testName).exec(createPR(wiredRenderer)); } public void testWiredBubblesAA() throws Exception { - (new PerfMeter("WiredBubblesAA")).exec(createPR(wiredRenderer).configure(AA)).report(); + createPerfMeter(testName).exec(createPR(wiredRenderer).configure(AA)); } public void testWiredBox() throws Exception { - (new PerfMeter("WiredBox")).exec(createPR(wiredBoxRenderer)).report(); + createPerfMeter(testName).exec(createPR(wiredBoxRenderer)); } public void testWiredBoxAA() throws Exception { - (new PerfMeter("WiredBoxAA")).exec(createPR(wiredBoxRenderer).configure(AA)).report(); + createPerfMeter(testName).exec(createPR(wiredBoxRenderer).configure(AA)); } public void testLines() throws Exception { - (new PerfMeter("Lines")).exec(createPR(segRenderer)).report(); + createPerfMeter(testName).exec(createPR(segRenderer)); } public void testLinesAA() throws Exception { - (new PerfMeter("LinesAA")).exec(createPR(segRenderer).configure(AA)).report(); + createPerfMeter(testName).exec(createPR(segRenderer).configure(AA)); } public void testFlatQuad() throws Exception { - (new PerfMeter("FlatQuad")).exec(createPR(flatQuadRenderer)).report(); + createPerfMeter(testName).exec(createPR(flatQuadRenderer)); } public void testFlatQuadAA() throws Exception { - (new PerfMeter("FlatQuadAA")).exec(createPR(flatQuadRenderer).configure(AA)).report(); + createPerfMeter(testName).exec(createPR(flatQuadRenderer).configure(AA)); } public void testWiredQuad() throws Exception { - (new PerfMeter("WiredQuad")).exec(createPR(wiredQuadRenderer)).report(); + createPerfMeter(testName).exec(createPR(wiredQuadRenderer)); } public void testWiredQuadAA() throws Exception { - (new PerfMeter("WiredQuadAA")).exec(createPR(wiredQuadRenderer).configure(AA)).report(); + createPerfMeter(testName).exec(createPR(wiredQuadRenderer).configure(AA)); } public void testTextNoAA() throws Exception { - (new PerfMeter("TextNoAA")).exec(createPR(textRenderer)).report(); + createPerfMeter(testName).exec(createPR(textRenderer)); } public void testTextLCD() throws Exception { - (new PerfMeter("TextLCD")).exec(createPR(textRenderer).configure(TextLCD)).report(); + createPerfMeter(testName).exec(createPR(textRenderer).configure(TextLCD)); } public void testTextGray() throws Exception { - (new PerfMeter("TextGray")).exec(createPR(textRenderer).configure(TextAA)).report(); + createPerfMeter(testName).exec(createPR(textRenderer).configure(TextAA)); } public void testLargeTextNoAA() throws Exception { - (new PerfMeter("LargeTextNoAA")).exec(createPR(largeTextRenderer)).report(); + createPerfMeter(testName).exec(createPR(largeTextRenderer)); } public void testLargeTextLCD() throws Exception { - (new PerfMeter("LargeTextLCD")).exec(createPR(largeTextRenderer).configure(TextLCD)).report(); + createPerfMeter(testName).exec(createPR(largeTextRenderer).configure(TextLCD)); } public void testLargeTextGray() throws Exception { - (new PerfMeter("LargeTextGray")).exec(createPR(largeTextRenderer).configure(TextAA)).report(); + createPerfMeter(testName).exec(createPR(largeTextRenderer).configure(TextAA)); } + public void testWhiteTextNoAA() throws Exception { - (new PerfMeter("WhiteTextNoAA")).exec(createPR(whiteTextRenderer)).report(); + createPerfMeter(testName).exec(createPR(whiteTextRenderer)); } public void testWhiteTextLCD() throws Exception { - (new PerfMeter("WhiteTextLCD")).exec(createPR(whiteTextRenderer).configure(TextLCD)).report(); + createPerfMeter(testName).exec(createPR(whiteTextRenderer).configure(TextLCD)); } public void testWhiteTextGray() throws Exception { - (new PerfMeter("WhiteTextGray")).exec(createPR(whiteTextRenderer).configure(TextAA)).report(); + createPerfMeter(testName).exec(createPR(whiteTextRenderer).configure(TextAA)); } public void testArgbSwBlitImage() throws Exception { - (new PerfMeter("ArgbSwBlitImage")).exec(createPR(argbSwBlitImageRenderer)).report(); + createPerfMeter(testName).exec(createPR(argbSwBlitImageRenderer)); } public void testBgrSwBlitImage() throws Exception { - (new PerfMeter("BgrSwBlitImage")).exec(createPR(bgrSwBlitImageRenderer)).report(); + createPerfMeter(testName).exec(createPR(bgrSwBlitImageRenderer)); } public void testArgbSurfaceBlitImage() throws Exception { - (new PerfMeter("ArgbSurfaceBlitImageRenderer")).exec(createPR(argbSurfaceBlitImageRenderer)).report(); + createPerfMeter(testName).exec(createPR(argbSurfaceBlitImageRenderer)); } public void testBgrSurfaceBlitImage() throws Exception { - (new PerfMeter("BgrSurfaceBlitImage")).exec(createPR(bgrSurfaceBlitImageRenderer)).report(); + createPerfMeter(testName).exec(createPR(bgrSurfaceBlitImageRenderer)); } + // XOR mode: public void testFlatOval_XOR() throws Exception { - (new PerfMeter("FlatOval_XOR")).exec(createPR(flatRenderer).configure(XORMode)).report(); + createPerfMeter(testName).exec(createPR(flatRenderer).configure(XORMode)); } public void testRotatedBox_XOR() throws Exception { - (new PerfMeter("RotatedBox_XOR")).exec(createPR(flatBoxRotRenderer).configure(XORMode)).report(); + createPerfMeter(testName).exec(createPR(flatBoxRotRenderer).configure(XORMode)); } public void testLines_XOR() throws Exception { - (new PerfMeter("Lines_XOR")).exec(createPR(segRenderer).configure(XORMode)).report(); + createPerfMeter(testName).exec(createPR(segRenderer).configure(XORMode)); } public void testImage_XOR() throws Exception { - (new PerfMeter("Image_XOR")).exec(createPR(imgRenderer).configure(XORMode)).report(); + createPerfMeter(testName).exec(createPR(imgRenderer).configure(XORMode)); } public void testTextNoAA_XOR() throws Exception { - (new PerfMeter("TextNoAA_XOR")).exec(createPR(textRenderer).configure(XORMode)).report(); + createPerfMeter(testName).exec(createPR(textRenderer).configure(XORMode)); } public void testTextLCD_XOR() throws Exception { - (new PerfMeter("TextLCD_XOR")).exec(createPR(textRenderer).configure(XORModeLCDText)).report(); + createPerfMeter(testName).exec(createPR(textRenderer).configure(XORModeLCDText)); + } + + // Mixed/Batched mode: + public void testTextWiredQuadBat() throws Exception { + createPerfMeter(testName).exec(createPR(textWiredQuadBatchedRenderer)); + } + + public void testTextWiredQuadMix() throws Exception { + createPerfMeter(testName).exec(createPR(textWiredQuadMixedRenderer)); + } + + public void testTextWiredQuadAABat() throws Exception { + createPerfMeter(testName).exec(createPR(textWiredQuadBatchedRenderer).configure(AA)); + } + + public void testTextWiredQuadAAMix() throws Exception { + createPerfMeter(testName).exec(createPR(textWiredQuadMixedRenderer).configure(AA)); + } + + public void testVolImageFlatBoxBat() throws Exception { + createPerfMeter(testName).exec(createPR(volImgFlatBoxBatchedRenderer)); + } + + public void testVolImageFlatBoxMix() throws Exception { + createPerfMeter(testName).exec(createPR(volImgFlatBoxMixedRenderer)); + } + + public void testVolImageFlatBoxAABat() throws Exception { + createPerfMeter(testName).exec(createPR(volImgFlatBoxBatchedRenderer).configure(AA)); + } + + public void testVolImageFlatBoxAAMix() throws Exception { + createPerfMeter(testName).exec(createPR(volImgFlatBoxMixedRenderer).configure(AA)); + } + + public void testVolImageWiredQuadBat() throws Exception { + createPerfMeter(testName).exec(createPR(volImgWiredQuadBatchedRenderer)); + } + + public void testVolImageWiredQuadMix() throws Exception { + createPerfMeter(testName).exec(createPR(volImgWiredQuadMixedRenderer)); + } + + public void testVolImageWiredQuadAABat() throws Exception { + createPerfMeter(testName).exec(createPR(volImgWiredQuadBatchedRenderer).configure(AA)); + } + + public void testVolImageWiredQuadAAMix() throws Exception { + createPerfMeter(testName).exec(createPR(volImgWiredQuadMixedRenderer).configure(AA)); + } + + public void testVolImageTextNoAABat() throws Exception { + createPerfMeter(testName).exec(createPR(volImgTextBatchedRenderer)); + } + + public void testVolImageTextNoAAMix() throws Exception { + createPerfMeter(testName).exec(createPR(volImgTextMixedRenderer)); + } + + private static void help() { + System.out.print("##############################################################\n"); + System.out.printf("# %s\n", VERSION); + System.out.print("##############################################################\n"); + System.out.println("# java ... RenderPerfTest "); + System.out.println("#"); + System.out.println("# Supported arguments :"); + System.out.println("# -h : display this help"); + System.out.println("# -v : set verbose output"); + System.out.println("#"); + System.out.println("# -e= : set the execution mode (default: " + EXEC_MODE_DEFAULT + + ") among " + EXEC_MODES); + System.out.println("#"); + System.out.println("# -f : use FPS unit (default)"); + System.out.println("# -t : use TIME(ms) unit"); + System.out.println("#"); + System.out.println("# -g=all|\"0-0,0-1...\" : use 'all' or specific graphics configurations"); + System.out.println("#"); + System.out.println("# -font=\"\" : set the font name used by all text renderers"); + System.out.println("# -fontSize= : set the font size used by Text renderers"); + System.out.println("# -fontSizeLarge= : set the font size used by LargeText renderers"); + System.out.println("# -text=\"\" : set the text drawn by all text renderers"); + System.out.println("#"); + System.out.println("# -lf : list available font names"); + System.out.println("# -lg : list available graphics configurations"); + System.out.println("#"); + System.out.println("# -n= : set number of primitives (default: " + N_DEFAULT + ")"); + System.out.println("# -r= : set number of test repeats (default: 1)"); + System.out.println("#"); + System.out.println("#"); + System.out.println("# -w= : use number of test frames (default: 1) per screen"); + System.out.println("#"); + System.out.println("# -u= : set number of warmup iterations (default: " + MIN_COUNT + ")"); + System.out.println("#"); + + System.out.print("# Supported test arguments :"); + + final ArrayList testCases = new ArrayList<>(); + for (Method m : RenderPerfTest.class.getDeclaredMethods()) { + if (m.getName().startsWith("test") && !ignoredTests.contains(m.getName())) { + testCases.add(m); + } + } + testCases.sort(Comparator.comparing(Method::getName)); + for (Method m : testCases) { + System.out.print(extractTestName(m)); + System.out.print(" "); + } + System.out.println(); + } + + private static String extractTestName(final Method m) { + return m.getName().substring("test".length()); } public static void main(String[] args) - throws InvocationTargetException, IllegalAccessException, NoSuchMethodException - { - RenderPerfTest test = new RenderPerfTest(); + throws NoSuchMethodException, NumberFormatException { + // Set the default locale to en-US locale (for Numerical Fields "." ",") + Locale.setDefault(Locale.US); - if (args.length > 0) { - for (String testCase : args) { - Method m = RenderPerfTest.class.getDeclaredMethod("test" + testCase); - m.invoke(test); - } - } else { - Method[] methods = RenderPerfTest.class.getDeclaredMethods(); - for (Method m : methods) { - if (m.getName().startsWith("test") && !ignoredTests.contains(m.getName())) { - m.invoke(test); + boolean help = false; + final ArrayList testCases = new ArrayList<>(); + + for (String arg : args) { + if (arg.length() >= 2) { + if (arg.startsWith("-")) { + switch (arg.substring(1, 2)) { + case "e": + if (arg.length() >= 3) { + EXEC_MODE = arg.substring(3).toLowerCase(); + } + break; + case "f": + if (arg.length() == 2) { + USE_FPS = true; + } else { + if ((arg.length() > 6) && "font=".equalsIgnoreCase(arg.substring(1, 6))) { + TEXT_FONT = arg.substring(6); + break; + } + if ((arg.length() > 10) && "fontSize=".equalsIgnoreCase(arg.substring(1, 10))) { + TEXT_SIZE_DEFAULT = Integer.parseInt(arg.substring(10)); + break; + } + if ((arg.length() > 15) && "fontSizeLarge=".equalsIgnoreCase(arg.substring(1, 15))) { + TEXT_SIZE_LARGE = Integer.parseInt(arg.substring(15)); + break; + } + } + break; + case "g": + if (arg.length() >= 3) { + GC_MODE = arg.substring(3).toLowerCase(); + } + break; + case "h": + help = true; + break; + case "l": + if (arg.length() == 3) { + if ("f".equalsIgnoreCase(arg.substring(2, 3))) { + VERBOSE_FONT_CONFIG = true; + } else if ("g".equalsIgnoreCase(arg.substring(2, 3))) { + VERBOSE_GRAPHICS_CONFIG = true; + } + } + break; + case "t": + if (arg.length() == 2) { + USE_FPS = false; + } else { + if ((arg.length() > 6) && "text=".equalsIgnoreCase(arg.substring(1, 6))) { + TEXT_STR = arg.substring(6); + } + } + break; + case "n": + if (arg.length() >= 3) { + N = Integer.parseInt(arg.substring(3)); + } + break; + case "r": + if (arg.length() >= 3) { + REPEATS = Integer.parseInt(arg.substring(3)); + } + break; + case "v": + VERBOSE = true; + break; + case "w": + if (arg.length() >= 3) { + NW = Integer.parseInt(arg.substring(3)); + } + break; + case "u": + if (arg.length() >= 3) { + WARMUP_COUNT = Integer.parseInt(arg.substring(3)); + } + break; + default: + System.err.println("Unsupported argument '" + arg + "' !"); + help = true; + } + } else { + Method m = RenderPerfTest.class.getDeclaredMethod("test" + arg); + testCases.add(m); } } } + if (testCases.isEmpty()) { + for (Method m : RenderPerfTest.class.getDeclaredMethods()) { + if (m.getName().startsWith("test") && !ignoredTests.contains(m.getName())) { + testCases.add(m); + } + } + testCases.sort(Comparator.comparing(Method::getName)); + } + + if (help) { + help(); + } + + if (CALIBRATION) { + Method m = RenderPerfTest.class.getDeclaredMethod("testCalibration"); + testCases.add(0, m); // first + } + + if (VERBOSE) { + System.out.print("##############################################################\n"); + System.out.printf("# %s\n", VERSION); + System.out.print("##############################################################\n"); + System.out.printf("# Java: %s\n", System.getProperty("java.runtime.version")); + System.out.printf("# VM: %s %s (%s)\n", System.getProperty("java.vm.name"), System.getProperty("java.vm.version"), System.getProperty("java.vm.info")); + System.out.printf("# OS: %s %s (%s)\n", System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch")); + System.out.printf("# CPUs: %d (virtual)\n", Runtime.getRuntime().availableProcessors()); + System.out.print("##############################################################\n"); + System.out.printf("# AWT Toolkit: %s \n", TOOLKIT.getClass().getSimpleName()); + System.out.printf("# Execution mode: %s\n", EXEC_MODE); + System.out.printf("# GraphicsConfiguration mode: %s\n", GC_MODE); + System.out.print("##############################################################\n"); + System.out.printf("# Repeats: %d\n", REPEATS); + System.out.printf("# NW: %d\n", NW); + System.out.printf("# N: %d\n", N); + System.out.printf("# WARMUP_COUNT: %d\n", WARMUP_COUNT); + System.out.printf("# Unit: %s\n", USE_FPS ? "FPS" : "TIME(ms)"); + System.out.print("##############################################################\n"); + System.out.printf("# Font: '%s'\n", TEXT_FONT); + System.out.printf("# Text: '%s'\n", TEXT_STR); + System.out.printf("# FontSize: %s\n", TEXT_SIZE_DEFAULT); + System.out.printf("# FontSizeLarge: %s\n", TEXT_SIZE_LARGE); + System.out.print("##############################################################\n"); + } + + // Graphics Configuration handling: + final Set fontNames = new LinkedHashSet<>(); + final Map gcByID = new LinkedHashMap<>(); + final Map idByGC = new HashMap<>(); + + final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + + for (String name : ge.getAvailableFontFamilyNames()) { + fontNames.add(name); + } + // Check font: + if (!fontNames.contains(TEXT_FONT)) { + System.err.println("Bad font name: [" + TEXT_FONT + "] !"); + VERBOSE_FONT_CONFIG = true; + } + + if (VERBOSE_FONT_CONFIG) { + System.out.print("# Available font names: "); + + for (String name : fontNames) { + System.out.print("'"); + System.out.print(name); + System.out.print("' "); + } + System.out.println(); + } + + final GraphicsDevice[] gds = ge.getScreenDevices(); + + if (VERBOSE_GRAPHICS_CONFIG) { + System.out.println("# Available GraphicsDevice(s) and their GraphicsConfiguration(s):"); + } + + for (int gdIdx = 0; gdIdx < gds.length; gdIdx++) { + final GraphicsDevice gd = gds[gdIdx]; + if (VERBOSE_GRAPHICS_CONFIG) { + System.out.println("# [" + gdIdx + "] = GraphicsDevice[" + gd.getIDstring() + "]"); + } + + final GraphicsConfiguration[] gcs = gd.getConfigurations(); + + for (int gcIdx = 0; gcIdx < gcs.length; gcIdx++) { + final GraphicsConfiguration gc = gcs[gcIdx]; + final String gcId = gdIdx + "-" + gcIdx; + gcByID.put(gcId, gc); + idByGC.put(gc, gcId); + if (VERBOSE_GRAPHICS_CONFIG) { + System.out.println("# - [" + gcId + "] = GraphicsConfiguration[" + gc + "] bounds:" + gc.getBounds()); + } + } + } + + final Set gcSet = new LinkedHashSet<>(); + + if (GC_MODE != null) { + if (!GC_MODE_DEF.equals(GC_MODE)) { + if (GC_MODE_ALL.equals(GC_MODE)) { + gcSet.addAll(gcByID.values()); + } else { + for (String gcKey : GC_MODE.split(",")) { + final GraphicsConfiguration gc = gcByID.get(gcKey); + if (gc != null) { + gcSet.add(gc); + } else { + System.err.println("Bad GraphicsConfiguration identifier 'x-y' where x is GraphicsDevice ID " + + "and y GraphicsConfiguration ID : [" + gcKey + "] ! (available values: " + gcByID.keySet() + ")"); + } + } + } + } + } + if (gcSet.isEmpty()) { + final GraphicsDevice gdDef = ge.getDefaultScreenDevice(); + final GraphicsConfiguration gcDef = gdDef.getDefaultConfiguration(); + final String gcId = idByGC.get(gcDef); + + if (VERBOSE_GRAPHICS_CONFIG) { + System.out.println("# Using default [" + gcId + "] = GraphicsConfiguration[" + gcDef + "] bounds:" + gcDef.getBounds()); + } + gcSet.add(gcDef); + } + + final List gcList = new ArrayList<>(gcSet); + final int NGC = gcList.size(); + + System.out.print("# Using GraphicsConfiguration(s): "); + for (GraphicsConfiguration gc : gcList) { + final String gcId = idByGC.get(gc); + System.out.print("[" + gcId + "][" + gc + "]"); + System.out.print(" "); + } + System.out.println(); + + final List instances = new ArrayList<>(); + int retCode = 0; + try { + if (!help) { + final List threads = new ArrayList<>(); + + for (int i = 0; i < NGC; i++) { + final GraphicsConfiguration gc = gcList.get(i); + + for (int j = 0; j < NW; j++) { + final RenderPerfTest rp = new RenderPerfTest(gc); + instances.add(rp); + threads.add(rp.createThreadTests(threads.size() + 1, j + 1, testCases)); + } + } + if (TRACE_SYNC) traceSync("testCount: " + testCount); + + initThreads(threads.size()); + initBarrierStart(); + + for (Thread thread : threads) { + if (TRACE_SYNC) traceSync(thread.getName() + " starting..."); + thread.start(); + } + + for (int n = 0; n < testCount; n++) { + if (VERBOSE) { + final int k = n / REPEATS; + final String methodName = extractTestName(testCases.get(k)); + System.out.println("# --- Test [" + (n + 1) + " / " + testCount + "] = " + methodName + " ---"); + } + + // reset stop barrier (to be ready): + initBarrierStop(); + + if (TRACE_SYNC) traceSync("Waiting " + threadCount + " threads to be ready..."); + readyCount.await(); + + if (TRACE_SYNC) traceSync("Threads are ready => starting benchmark on " + threadCount + " threads now"); + triggerStart.countDown(); + + // reset done barrier (to be ready): + initBarrierDone(); + + if (TRACE_SYNC) traceSync("Waiting " + threadCount + " threads to complete benchmark..."); + completedCount.await(); + + if (TRACE_SYNC) traceSync("Test completed on " + threadCount + " threads => stopping benchmark on all threads now"); + triggerStop.countDown(); + + // reset start barrier (to be ready): + initBarrierStart(); + + if (TRACE_SYNC) traceSync("Waiting " + threadCount + " threads to exit test..."); + doneCount.await(); + + if (TRACE_SYNC) traceSync("Test exited on " + threadCount + " threads => finalize benchmark on all threads now"); + triggerExit.countDown(); + } + + for (Thread thread : threads) { + thread.join(); + if (TRACE_SYNC) traceSync(thread.getName() + " terminated"); + } + } + } catch (Throwable th) { + System.err.println("Exception occurred during :"); + th.printStackTrace(System.err); + retCode = 1; + } finally { + for (RenderPerfTest rp : instances) { + try { + rp.fh.hideFrameAndWait(); + } catch (Throwable th) { + System.err.println("Exception occurred in hideFrameAndWait():"); + th.printStackTrace(System.err); + retCode = 1; + } + } + // ensure jvm immediate shutdown: + System.exit(retCode); + } + } + + // thread synchronization + + private static int threadCount = 0; + + private static int testCount = 0; + private static volatile String testName = null; + + private static volatile CountDownLatch readyCount = null; + private static volatile CountDownLatch triggerStart = null; + + private static volatile CountDownLatch completedCount = null; + private static volatile CountDownLatch triggerStop = null; + + private static volatile CountDownLatch doneCount = null; + private static volatile CountDownLatch triggerExit = null; + + static void traceSync(final String msg) { + System.out.println("[" + System.nanoTime() + "] " + msg); + } + + private static void initThreads(int count) { + threadCount = count; + if (TRACE_SYNC) traceSync("initThreads(): threadCount: " + threadCount); + } + + static boolean isMultiThreads() { + return threadCount > 1; + } + + private static void initBarrierStart() { + readyCount = new CountDownLatch(threadCount); + triggerStart = new CountDownLatch(1); + } + + private static void initBarrierStop() { + completedCount = new CountDownLatch(threadCount); + triggerStop = new CountDownLatch(1); + } + + private static void initBarrierDone() { + doneCount = new CountDownLatch(threadCount); + triggerExit = new CountDownLatch(1); + } + + public Thread createThreadTests(final int threadId, final int frameId, + final ArrayList testCases) throws Exception { + fh.setIds(threadId, frameId); + + SwingUtilities.invokeAndWait(new Runnable() { + @Override + public void run() { + fh.prepareFrameEDT(VERSION + " [" + fh.threadId + "]"); + + final JLabel label = new JLabel((DELAY_START) ? "Waiting 3s before starting benchmark..." : "Starting benchmark..."); + label.setForeground(Color.WHITE); + + final JPanel panel = new JPanel(); + panel.add(label); + + fh.showFrameEDT(panel); + } + }); + + // Wait frame to be shown: + fh.waitFrameShown(); + + // Set test count per thread: + testCount = testCases.size() * REPEATS; + + final RenderPerfTest rp = this; + return new Thread("RenderPerfThread[" + threadId + "]") { + @Override + public void run() { + if (DELAY_START) { + RenderPerfTest.sleep(3000); + } + try { + for (Method m : testCases) { + for (int i = 0; i < REPEATS; i++) { + testName = extractTestName(m); + m.invoke(rp); + } + } + } catch (Throwable th) { + System.err.println("Exception occurred in RenderPerfThread[" + threadId + "]:"); + th.printStackTrace(System.err); + } + } + }; + } + + private static void sleep(final long millis) { + if (millis > 0) { + try { + Thread.sleep(millis); + } catch (InterruptedException ie) { + ie.printStackTrace(System.err); + } + } + } + + /** regular expression used to match characters different than alpha/numeric/_/-/. (1..n) */ + private final static Pattern PATTERN_NON_FILE_NAME = Pattern.compile("[^a-zA-Z0-9\\-_\\.]"); + + private static String replaceNonFileNameChars(final String value) { + return PATTERN_NON_FILE_NAME.matcher(value).replaceAll("_"); } }