| /* | |
| * 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 | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code 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 | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| 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; | |
| import java.awt.geom.QuadCurve2D; | |
| import java.awt.image.BufferedImage; | |
| 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.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 final class RenderPerfTest { | |
| private final static String VERSION = "RenderPerfTest 2024.02"; | |
| private static final HashSet<String> ignoredTests = new HashSet<>(); | |
| static { | |
| // add ignored tests here | |
| // ignoredTests.add("testMyIgnoredTest"); | |
| ignoredTests.add("testCalibration"); // not from command line | |
| } | |
| 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<String> 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 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 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, boolean enabled); | |
| } | |
| 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); | |
| } | |
| } | |
| 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); | |
| } | |
| } | |
| 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]; | |
| by = new float[n]; | |
| vx = new float[n]; | |
| vy = new float[n]; | |
| this.n = n; | |
| this.r = r; | |
| this.x0 = x0; | |
| this.y0 = y0; | |
| this.width = width; | |
| this.height = height; | |
| for (int i = 0; i < n; i++) { | |
| bx[i] = (float) (x0 + r + 0.1 + Math.random() * (width - 2 * r - 0.2 - x0)); | |
| by[i] = (float) (y0 + r + 0.1 + Math.random() * (height - 2 * r - 0.2 - y0)); | |
| vx[i] = 0.1f * (float) (Math.random() * 2 * r - r); | |
| vy[i] = 0.1f * (float) (Math.random() * 2 * r - r); | |
| } | |
| } | |
| void render(Graphics2D g2d, ParticleRenderer renderer) { | |
| for (int i = 0; i < n; i++) { | |
| renderer.render(g2d, i, bx, by, vx, vy); | |
| } | |
| } | |
| void update() { | |
| for (int i = 0; i < n; i++) { | |
| bx[i] += vx[i]; | |
| if (bx[i] + r > width || bx[i] - r < x0) vx[i] = -vx[i]; | |
| by[i] += vy[i]; | |
| if (by[i] + r > height || by[i] - r < y0) vy[i] = -vy[i]; | |
| } | |
| } | |
| } | |
| interface Renderable { | |
| void setup(Graphics2D g2d, boolean enabled); | |
| void render(Graphics2D g2d); | |
| void update(); | |
| } | |
| final static class ParticleRenderable implements Renderable { | |
| final Particles balls; | |
| final ParticleRenderer renderer; | |
| Configurable configure = null; | |
| ParticleRenderable(final Particles balls, final ParticleRenderer renderer) { | |
| this.balls = balls; | |
| this.renderer = renderer; | |
| } | |
| @Override | |
| 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 | |
| public void render(Graphics2D g2d) { | |
| balls.render(g2d, renderer); | |
| } | |
| @Override | |
| public void update() { | |
| balls.update(); | |
| } | |
| public ParticleRenderable configure(final Configurable configure) { | |
| this.configure = configure; | |
| return this; | |
| } | |
| } | |
| 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 { | |
| Color[] colors; | |
| float r; | |
| FlatParticleRenderer(int n, float r) { | |
| colors = new Color[n]; | |
| this.r = r; | |
| for (int i = 0; i < n; i++) { | |
| colors[i] = new Color((float) Math.random(), | |
| (float) Math.random(), (float) Math.random()); | |
| } | |
| } | |
| @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)); | |
| } | |
| } | |
| static class ClipFlatParticleRenderer extends FlatParticleRenderer { | |
| ClipFlatParticleRenderer(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) { | |
| g2d.setColor(colors[id % colors.length]); | |
| g2d.setClip(new Ellipse2D.Double((int) (x[id] - r), (int) (y[id] - r), (int) (2 * r), (int) (2 * r))); | |
| g2d.fillRect((int) (x[id] - 2 * r), (int) (y[id] - 2 * r), (int) (4 * r), (int) (4 * r)); | |
| } | |
| } | |
| } | |
| static class WhiteTextParticleRenderer implements ParticleRenderer { | |
| 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) { | |
| g2d.setColor(Color.WHITE); | |
| } | |
| @Override | |
| public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { | |
| setPaint(g2d, id); | |
| 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)); | |
| } | |
| } | |
| static class TextParticleRenderer extends WhiteTextParticleRenderer { | |
| Color[] colors; | |
| float r; | |
| TextParticleRenderer(int n, float 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++) { | |
| colors[i] = new Color((float) Math.random(), | |
| (float) Math.random(), (float) Math.random()); | |
| } | |
| } | |
| void setPaint(Graphics2D g2d, int id) { | |
| g2d.setColor(colors[id % colors.length]); | |
| } | |
| } | |
| static class LargeTextParticleRenderer extends TextParticleRenderer { | |
| LargeTextParticleRenderer(int n, float r) { | |
| super(n, r, TEXT_SIZE_LARGE); | |
| } | |
| @Override | |
| public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { | |
| if (id % 100 != 0) return; | |
| super.render(g2d, id, x, y, vx, vy); | |
| } | |
| } | |
| static class FlatOvalRotParticleRenderer extends FlatParticleRenderer { | |
| FlatOvalRotParticleRenderer(int n, float r) { | |
| super(n, r); | |
| } | |
| void setPaint(Graphics2D g2d, int id) { | |
| g2d.setColor(colors[id % colors.length]); | |
| } | |
| @Override | |
| public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { | |
| setPaint(g2d, id); | |
| if (Math.abs(vx[id] + vy[id]) > 0.001) { | |
| AffineTransform t = (AffineTransform) g2d.getTransform().clone(); | |
| double l = vx[id] / Math.sqrt(vx[id] * vx[id] + vy[id] * vy[id]); | |
| if (vy[id] < 0) { | |
| l = -l; | |
| } | |
| 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.setTransform(t); | |
| } else { | |
| g2d.fillOval((int) (x[id] - r), (int) (y[id] - 0.5 * r), | |
| (int) (2 * r), (int) r); | |
| } | |
| } | |
| } | |
| 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); | |
| 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); | |
| 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); | |
| 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); | |
| g2d.setPaint(p); | |
| } | |
| } | |
| static class RadGrad3OvalRotParticleRenderer extends FlatOvalRotParticleRenderer { | |
| RadGrad3OvalRotParticleRenderer(int n, float r) { | |
| super(n, r); | |
| } | |
| @Override | |
| void setPaint(Graphics2D g2d, int id) { | |
| 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); | |
| 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)); | |
| } | |
| } | |
| 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) { | |
| g2d.setColor(colors[id % colors.length]); | |
| g2d.setClip((int) (x[id] - r), (int) (y[id] - r), (int) (2 * r), (int) (2 * r)); | |
| g2d.fillRect((int) (x[id] - 2 * r), (int) (y[id] - 2 * r), (int) (4 * r), (int) (4 * r)); | |
| } | |
| } | |
| } | |
| static class ImgParticleRenderer extends FlatParticleRenderer { | |
| BufferedImage dukeImg; | |
| ImgParticleRenderer(int n, float r) { | |
| super(n, r); | |
| try { | |
| dukeImg = ImageIO.read( | |
| Objects.requireNonNull( | |
| RenderPerfTest.class.getClassLoader().getResourceAsStream( | |
| "renderperf/images/duke.png"))); | |
| } catch (IOException e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| @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); | |
| } | |
| } | |
| 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]); | |
| if (Math.abs(vx[id] + vy[id]) > 0.001) { | |
| AffineTransform t = (AffineTransform) g2d.getTransform().clone(); | |
| double l = vx[id] / Math.sqrt(vx[id] * vx[id] + vy[id] * vy[id]); | |
| if (vy[id] < 0) { | |
| l = -l; | |
| } | |
| g2d.translate(x[id], y[id]); | |
| g2d.rotate(Math.acos(l)); | |
| 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), | |
| (int) (2 * r), (int) (2 * r)); | |
| } | |
| } | |
| } | |
| 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)); | |
| } | |
| } | |
| static class WiredBoxParticleRenderer extends FlatParticleRenderer { | |
| WiredBoxParticleRenderer(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.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) { | |
| super(n, r); | |
| } | |
| @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); | |
| 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)); | |
| } | |
| } | |
| static class WiredQuadParticleRenderer extends FlatParticleRenderer { | |
| WiredQuadParticleRenderer(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 > 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])); | |
| } | |
| } | |
| } | |
| static class FlatQuadParticleRenderer extends FlatParticleRenderer { | |
| FlatQuadParticleRenderer(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 > 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])); | |
| } | |
| } | |
| } | |
| static class BlitImageParticleRenderer extends FlatParticleRenderer { | |
| BufferedImage image; | |
| BlitImageParticleRenderer(int n, float r, BufferedImage img) { | |
| super(n, r); | |
| image = img; | |
| fill(image); | |
| } | |
| @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); | |
| } | |
| private static void fill(final Image image) { | |
| final Graphics2D graphics = (Graphics2D) image.getGraphics(); | |
| graphics.setComposite(AlphaComposite.Src); | |
| for (int i = 0; i < image.getHeight(null); ++i) { | |
| graphics.setColor(new Color(i, 0, 0)); | |
| graphics.fillRect(0, i, image.getWidth(null), 1); | |
| } | |
| graphics.dispose(); | |
| } | |
| } | |
| static class SwBlitImageParticleRenderer extends BlitImageParticleRenderer { | |
| SwBlitImageParticleRenderer(int n, float r, final int type) { | |
| super(n, r, makeUnmanagedBI(type)); | |
| } | |
| private static BufferedImage makeUnmanagedBI(final int type) { | |
| final BufferedImage bi = new BufferedImage(17, 33, type); | |
| final DataBuffer db = bi.getRaster().getDataBuffer(); | |
| if (db instanceof DataBufferInt) { | |
| ((DataBufferInt) db).getData(); | |
| } else if (db instanceof DataBufferShort) { | |
| ((DataBufferShort) db).getData(); | |
| } else if (db instanceof DataBufferByte) { | |
| ((DataBufferByte) db).getData(); | |
| } | |
| bi.setAccelerationPriority(0.0f); | |
| return bi; | |
| } | |
| } | |
| static class SurfaceBlitImageParticleRenderer extends BlitImageParticleRenderer { | |
| SurfaceBlitImageParticleRenderer(int n, float r, final int type) { | |
| super(n, r, makeManagedBI(type)); | |
| } | |
| private static BufferedImage makeManagedBI(final int type) { | |
| final BufferedImage bi = new BufferedImage(17, 33, type); | |
| bi.setAccelerationPriority(1.0f); | |
| return bi; | |
| } | |
| } | |
| final static class PerfMeter { | |
| private final FrameHandler fh; | |
| private final String name; | |
| private final PerfMeterExecutor executor; | |
| PerfMeter(final FrameHandler fh, String name) { | |
| this.fh = fh; | |
| this.name = name; | |
| executor = getExecutor(); | |
| } | |
| void exec(final Renderable renderable) throws Exception { | |
| executor.exec(name, renderable); | |
| } | |
| 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(); | |
| final JPanel panel = new JPanel() { | |
| @Override | |
| protected void paintComponent(Graphics g) { | |
| if (TRACE) System.out.print("P"); | |
| paintPanel(renderable, g); | |
| if (TRACE) System.out.print("Q"); | |
| } | |
| }; | |
| fh.showFrameEDT(panel); | |
| if (TRACE) System.out.print(">>"); | |
| } | |
| }); | |
| // 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 = markerStartTime.getAndSet(0L)) > 0L) { | |
| paintStartTime = t; | |
| if (TRACE) System.out.print("|"); | |
| } | |
| boolean wait = true; | |
| 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(); | |
| 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("-"); | |
| } | |
| } | |
| final long currentTime = System.nanoTime(); | |
| if ((frames >= MIN_COUNT) && (currentTime >= endTime)) { | |
| break; | |
| } | |
| if ((frames >= COUNT) && (currentTime >= minTime)) { | |
| break; | |
| } | |
| if (wait) { | |
| sleep(CYCLE_DELAY); | |
| } | |
| cycles++; | |
| } // end measurements | |
| // 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"); | |
| } | |
| 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"); | |
| } | |
| 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); | |
| } | |
| 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); | |
| } | |
| } | |
| final static class PerfMeterRobot extends PerfMeterExecutor { | |
| private int nRobotTimes = 0; | |
| private long[] robotTime = (fh.calibrate) ? new long[COUNT] : null; | |
| private int nDelayTimes = 0; | |
| private long[] delayTime = (ROBOT_TIME_DELAY) ? null : new long[COUNT]; | |
| private long lastPaintTime = 0L; | |
| private int renderedMarkerIdx = -1; | |
| private Robot robot = null; | |
| 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 { | |
| createPerfMeter(testName).exec(createPR(flatRenderer)); | |
| } | |
| public void testFlatOvalAA() throws Exception { | |
| createPerfMeter(testName).exec(createPR(flatRenderer).configure(AA)); | |
| } | |
| public void testClipFlatOval() throws Exception { | |
| createPerfMeter(testName).exec(createPR(clipFlatRenderer)); | |
| } | |
| public void testClipFlatOvalAA() throws Exception { | |
| createPerfMeter(testName).exec(createPR(clipFlatRenderer).configure(AA)); | |
| } | |
| public void testFlatBox() throws Exception { | |
| createPerfMeter(testName).exec(createPR(flatBoxRenderer)); | |
| } | |
| public void testFlatBoxAA() throws Exception { | |
| createPerfMeter(testName).exec(createPR(flatBoxRenderer).configure(AA)); | |
| } | |
| public void testClipFlatBox() throws Exception { | |
| createPerfMeter(testName).exec(createPR(clipFlatBoxParticleRenderer)); | |
| } | |
| public void testClipFlatBoxAA() throws Exception { | |
| createPerfMeter(testName).exec(createPR(clipFlatBoxParticleRenderer).configure(AA)); | |
| } | |
| public void testImage() throws Exception { | |
| createPerfMeter(testName).exec(createPR(imgRenderer)); | |
| } | |
| public void testImageAA() throws Exception { | |
| 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 { | |
| createPerfMeter(testName).exec(createPR(flatBoxRotRenderer)); | |
| } | |
| public void testRotatedBoxAA() throws Exception { | |
| createPerfMeter(testName).exec(createPR(flatBoxRotRenderer).configure(AA)); | |
| } | |
| public void testRotatedOval() throws Exception { | |
| createPerfMeter(testName).exec(createPR(flatOvalRotRenderer)); | |
| } | |
| public void testRotatedOvalAA() throws Exception { | |
| createPerfMeter(testName).exec(createPR(flatOvalRotRenderer).configure(AA)); | |
| } | |
| public void testLinGrad3RotatedOval() throws Exception { | |
| createPerfMeter(testName).exec(createPR(linGrad3OvalRotRenderer)); | |
| } | |
| public void testLinGrad3RotatedOvalAA() throws Exception { | |
| createPerfMeter(testName).exec(createPR(linGrad3OvalRotRenderer).configure(AA)); | |
| } | |
| public void testRadGrad3RotatedOval() throws Exception { | |
| createPerfMeter(testName).exec(createPR(radGrad3OvalRotRenderer)); | |
| } | |
| public void testRadGrad3RotatedOvalAA() throws Exception { | |
| createPerfMeter(testName).exec(createPR(radGrad3OvalRotRenderer).configure(AA)); | |
| } | |
| public void testLinGradRotatedOval() throws Exception { | |
| createPerfMeter(testName).exec(createPR(linGradOvalRotRenderer)); | |
| } | |
| public void testLinGradRotatedOvalAA() throws Exception { | |
| createPerfMeter(testName).exec(createPR(linGradOvalRotRenderer).configure(AA)); | |
| } | |
| public void testWiredBubbles() throws Exception { | |
| createPerfMeter(testName).exec(createPR(wiredRenderer)); | |
| } | |
| public void testWiredBubblesAA() throws Exception { | |
| createPerfMeter(testName).exec(createPR(wiredRenderer).configure(AA)); | |
| } | |
| public void testWiredBox() throws Exception { | |
| createPerfMeter(testName).exec(createPR(wiredBoxRenderer)); | |
| } | |
| public void testWiredBoxAA() throws Exception { | |
| createPerfMeter(testName).exec(createPR(wiredBoxRenderer).configure(AA)); | |
| } | |
| public void testLines() throws Exception { | |
| createPerfMeter(testName).exec(createPR(segRenderer)); | |
| } | |
| public void testLinesAA() throws Exception { | |
| createPerfMeter(testName).exec(createPR(segRenderer).configure(AA)); | |
| } | |
| public void testFlatQuad() throws Exception { | |
| createPerfMeter(testName).exec(createPR(flatQuadRenderer)); | |
| } | |
| public void testFlatQuadAA() throws Exception { | |
| createPerfMeter(testName).exec(createPR(flatQuadRenderer).configure(AA)); | |
| } | |
| public void testWiredQuad() throws Exception { | |
| createPerfMeter(testName).exec(createPR(wiredQuadRenderer)); | |
| } | |
| public void testWiredQuadAA() throws Exception { | |
| createPerfMeter(testName).exec(createPR(wiredQuadRenderer).configure(AA)); | |
| } | |
| public void testTextNoAA() throws Exception { | |
| createPerfMeter(testName).exec(createPR(textRenderer)); | |
| } | |
| public void testTextLCD() throws Exception { | |
| createPerfMeter(testName).exec(createPR(textRenderer).configure(TextLCD)); | |
| } | |
| public void testTextGray() throws Exception { | |
| createPerfMeter(testName).exec(createPR(textRenderer).configure(TextAA)); | |
| } | |
| public void testLargeTextNoAA() throws Exception { | |
| createPerfMeter(testName).exec(createPR(largeTextRenderer)); | |
| } | |
| public void testLargeTextLCD() throws Exception { | |
| createPerfMeter(testName).exec(createPR(largeTextRenderer).configure(TextLCD)); | |
| } | |
| public void testLargeTextGray() throws Exception { | |
| createPerfMeter(testName).exec(createPR(largeTextRenderer).configure(TextAA)); | |
| } | |
| public void testWhiteTextNoAA() throws Exception { | |
| createPerfMeter(testName).exec(createPR(whiteTextRenderer)); | |
| } | |
| public void testWhiteTextLCD() throws Exception { | |
| createPerfMeter(testName).exec(createPR(whiteTextRenderer).configure(TextLCD)); | |
| } | |
| public void testWhiteTextGray() throws Exception { | |
| createPerfMeter(testName).exec(createPR(whiteTextRenderer).configure(TextAA)); | |
| } | |
| public void testArgbSwBlitImage() throws Exception { | |
| createPerfMeter(testName).exec(createPR(argbSwBlitImageRenderer)); | |
| } | |
| public void testBgrSwBlitImage() throws Exception { | |
| createPerfMeter(testName).exec(createPR(bgrSwBlitImageRenderer)); | |
| } | |
| public void testArgbSurfaceBlitImage() throws Exception { | |
| createPerfMeter(testName).exec(createPR(argbSurfaceBlitImageRenderer)); | |
| } | |
| public void testBgrSurfaceBlitImage() throws Exception { | |
| createPerfMeter(testName).exec(createPR(bgrSurfaceBlitImageRenderer)); | |
| } | |
| // XOR mode: | |
| public void testFlatOval_XOR() throws Exception { | |
| createPerfMeter(testName).exec(createPR(flatRenderer).configure(XORMode)); | |
| } | |
| public void testRotatedBox_XOR() throws Exception { | |
| createPerfMeter(testName).exec(createPR(flatBoxRotRenderer).configure(XORMode)); | |
| } | |
| public void testLines_XOR() throws Exception { | |
| createPerfMeter(testName).exec(createPR(segRenderer).configure(XORMode)); | |
| } | |
| public void testImage_XOR() throws Exception { | |
| createPerfMeter(testName).exec(createPR(imgRenderer).configure(XORMode)); | |
| } | |
| public void testTextNoAA_XOR() throws Exception { | |
| createPerfMeter(testName).exec(createPR(textRenderer).configure(XORMode)); | |
| } | |
| public void testTextLCD_XOR() throws Exception { | |
| 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 <args> <tests>"); | |
| System.out.println("#"); | |
| System.out.println("# Supported arguments <args>:"); | |
| System.out.println("# -h : display this help"); | |
| System.out.println("# -v : set verbose output"); | |
| System.out.println("#"); | |
| System.out.println("# -e=<mode> : 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=\"<name>\" : set the font name used by all text renderers"); | |
| System.out.println("# -fontSize=<number> : set the font size used by Text renderers"); | |
| System.out.println("# -fontSizeLarge=<number> : set the font size used by LargeText renderers"); | |
| System.out.println("# -text=\"<string>\" : 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=<number> : set number of primitives (default: " + N_DEFAULT + ")"); | |
| System.out.println("# -r=<number> : set number of test repeats (default: 1)"); | |
| System.out.println("#"); | |
| System.out.println("#"); | |
| System.out.println("# -w=<number> : use number of test frames (default: 1) per screen"); | |
| System.out.println("#"); | |
| System.out.println("# -u=<number> : set number of warmup iterations (default: " + MIN_COUNT + ")"); | |
| System.out.println("#"); | |
| System.out.print("# Supported test arguments <tests>:"); | |
| final ArrayList<Method> 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 NoSuchMethodException, NumberFormatException { | |
| // Set the default locale to en-US locale (for Numerical Fields "." ",") | |
| Locale.setDefault(Locale.US); | |
| boolean help = false; | |
| final ArrayList<Method> 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<String> fontNames = new LinkedHashSet<>(); | |
| final Map<String, GraphicsConfiguration> gcByID = new LinkedHashMap<>(); | |
| final Map<GraphicsConfiguration, String> 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<GraphicsConfiguration> 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<GraphicsConfiguration> 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<RenderPerfTest> instances = new ArrayList<>(); | |
| int retCode = 0; | |
| try { | |
| if (!help) { | |
| final List<Thread> 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<Method> 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("_"); | |
| } | |
| } |