// from https://github.com/ideastorm/Benchmark /* My results on ThinkPad, JDK 17: 3 Buffers Direct Buffer Average 324.43 Buffered Image Average 137.89 Reuse Buffered Average 183.25 ARGB Image Average 133.57 Reuse ARGB Image Average 173.53 Volatile Image Average 43159.51 Reuse Volatile Average 521.36 2 Buffers Direct Buffer Average 326.72 Buffered Image Average 124.76 Reuse Buffered Average 170.78 ARGB Image Average 123.15 Reuse ARGB Image Average 157.24 Volatile Image Average 34659.11 Reuse Volatile Average 731.17 1 Buffers Direct Buffer Average 396.43 Buffered Image Average 155.31 Reuse Buffered Average 208.33 ARGB Image Average 162.80 Reuse ARGB Image Average 191.03 Volatile Image Average 39582.93 Reuse Volatile Average 752.04 */ p { Benchmark.main(args); } sclass Benchmark implements Callable { private static final int INNER_LOOP_COUNT = 250; private static final int OUTER_LOOP_COUNT = 5; private static final int NORMALIZE_LOOP_COUNT = 3; // Window Objects static javax.swing.JFrame frame; static javax.swing.JPanel panel; private final Consumer> painter; private Benchmark(Consumer> painter) { this.painter = painter; } @Override public Long call() { Font font = panel.getFont().deriveFont(30f); long start = System.nanoTime(); for (int i = 0; i < INNER_LOOP_COUNT; i++) { final String frameCount = "" + i; painter.accept((Graphics2D g) -> { g.setColor(Color.BLACK); g.setFont(font); g.drawString(frameCount, 30, 100); }); } long end = System.nanoTime(); return end - start; } public static void main(String[] args) { // Create JFrame frame = new javax.swing.JFrame("Benchmark"); frame.setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE); // Create JPanel panel = new javax.swing.JPanel(false); panel.setPreferredSize(new java.awt.Dimension(1280, 720)); frame.setContentPane(panel); ClassLoader classloader = ClassLoader.getSystemClassLoader(); // Load Test Image try { testimage = loadImage2(#1103053); blackimage = loadImage2(#1103052); // Set Window Visible and get Panel Graphics frame.pack(); frame.setVisible(true); panelGraphics = panel.getGraphics(); for (int normalizeIndex = 0; normalizeIndex < NORMALIZE_LOOP_COUNT; normalizeIndex++) { printVars(+normalizeIndex); // Start Tests and Display Results for (int bufferCount = 3; bufferCount > 0; bufferCount--) { printVars(+bufferCount); frame.createBufferStrategy(bufferCount); List directBufferTimes = new ArrayList<>(); List bufferedImageTimes = new ArrayList<>(); List volatileImageTimes = new ArrayList<>(); List bufferedArgbTimes = new ArrayList<>(); List reuseBufferedTimes = new ArrayList<>(); List reuseVolatileTimes = new ArrayList<>(); List reuseArgbBufrTimes = new ArrayList<>(); for (int i = 0; i < OUTER_LOOP_COUNT; i++) { printVars(+normalizeIndex, +bufferCount, +i); frame.setTitle("Benchmark - Direct Buffer"); directBufferTimes.add(new Benchmark(Benchmark::drawDoubleBuffer).call()); frame.setTitle("Benchmark - Buffered Image"); bufferedImageTimes.add(new Benchmark(Benchmark::drawBufferedImage).call()); frame.setTitle("Benchmark - Reuse Buffered Image"); reuseBufferedTimes.add(new Benchmark(Benchmark::reuseBufferedImage).call()); frame.setTitle("Benchmark - ARGB Buffered Image"); bufferedArgbTimes.add(new Benchmark(Benchmark::drawArgbBufferedImage).call()); frame.setTitle("Benchmark - Reuse ARGB Buffered Image"); reuseArgbBufrTimes.add(new Benchmark(Benchmark::reuseArgbBufferedImage).call()); frame.setTitle("Benchmark - Volatile Image"); volatileImageTimes.add(new Benchmark(Benchmark::drawVolatileImage).call()); frame.setTitle("Benchmark - Reuse Volatile Image"); reuseVolatileTimes.add(new Benchmark(Benchmark::reuseVolatileImage).call()); } /* * Java benchmarks usually have to "warm up". By running it multiple times and ignoring the first runs, we * allow the JVM to normalize its heap allocation, so garbage collection doesn't affect the results as much. */ if (normalizeIndex == NORMALIZE_LOOP_COUNT - 1) { // bufferedImageTimes.forEach(t -> System.out.printf("Buffered Image %01.2f\n", calcFps(t))); // volatileImageTimes.forEach(t -> System.out.printf("Volatile Image %01.2f\n", calcFps(t))); // directBufferTimes.forEach(t -> System.out.printf("Direct Buffer %01.2f\n", calcFps(t))); System.out.printf("%d Buffers\n", bufferCount); System.out.printf(" Direct Buffer Average %01.2f\n", directBufferTimes.stream().mapToDouble(Benchmark::calcFps).average().getAsDouble()); System.out.printf(" Buffered Image Average %01.2f\n", bufferedImageTimes.stream().mapToDouble(Benchmark::calcFps).average().getAsDouble()); System.out.printf(" Reuse Buffered Average %01.2f\n", reuseBufferedTimes.stream().mapToDouble(Benchmark::calcFps).average().getAsDouble()); System.out.printf(" ARGB Image Average %01.2f\n", bufferedArgbTimes.stream().mapToDouble(Benchmark::calcFps).average().getAsDouble()); System.out.printf("Reuse ARGB Image Average %01.2f\n", reuseArgbBufrTimes.stream().mapToDouble(Benchmark::calcFps).average().getAsDouble()); System.out.printf(" Volatile Image Average %01.2f\n", volatileImageTimes.stream().mapToDouble(Benchmark::calcFps).average().getAsDouble()); System.out.printf(" Reuse Volatile Average %01.2f\n", reuseVolatileTimes.stream().mapToDouble(Benchmark::calcFps).average().getAsDouble()); } } } frame.setVisible(false); } catch (Throwable e) { e.printStackTrace(); } System.exit(0); //force shutdown } static double calcFps(Long nanoTime) { return INNER_LOOP_COUNT * 1_000_000_000.0 / nanoTime; } // Test Image static java.awt.Image testimage; static java.awt.Image blackimage; private static BufferedImage staticBuffered; private static BufferedImage staticArgbBuffered; private static VolatileImage staticVolatile; // Draw Graphics static Graphics panelGraphics; static void drawUsingImageBuffer(Consumer frameMarker, Image buffer) { Graphics2D imageGraphics = (Graphics2D) buffer.getGraphics(); imageGraphics.drawImage(testimage, 0, 0, 1280, 720, null); frameMarker.accept(imageGraphics); panelGraphics.drawImage(buffer, 0, 0, 1280, 720, null); } static void drawBufferedImage(Consumer frameMarker) { BufferedImage buffer = new BufferedImage(1280, 720, BufferedImage.TYPE_INT_RGB); drawUsingImageBuffer(frameMarker, buffer); } static void reuseBufferedImage(Consumer frameMarker) { if (staticBuffered == null) { staticBuffered = new BufferedImage(1280, 720, BufferedImage.TYPE_INT_RGB); } drawUsingImageBuffer(frameMarker, staticBuffered); } static void drawArgbBufferedImage(Consumer frameMarker) { BufferedImage buffer = new BufferedImage(1280, 720, BufferedImage.TYPE_INT_ARGB); drawUsingImageBuffer(frameMarker, buffer); } static void reuseArgbBufferedImage(Consumer frameMarker) { if (staticArgbBuffered == null) { staticArgbBuffered = new BufferedImage(1280, 720, BufferedImage.TYPE_INT_ARGB); } drawUsingImageBuffer(frameMarker, staticArgbBuffered); } static void drawVolatileImage(Consumer frameMarker) { VolatileImage volatileImage = panel.createVolatileImage(1280, 720); drawUsingImageBuffer(frameMarker, volatileImage); } static void reuseVolatileImage(Consumer frameMarker) { if (staticVolatile == null || staticVolatile.contentsLost()) { staticVolatile = panel.createVolatileImage(1280, 720); } drawUsingImageBuffer(frameMarker, staticVolatile); } static void drawDoubleBuffer(Consumer frameMarker) { Insets insets = frame.getInsets(); Toolkit.getDefaultToolkit().sync(); BufferStrategy strategy = frame.getBufferStrategy(); if (strategy != null) { do { do { Graphics2D graphics = (Graphics2D) strategy.getDrawGraphics(); graphics.translate(insets.left, insets.top); graphics.drawImage(testimage, 0, 0, 1280, 720, null); frameMarker.accept(graphics); graphics.dispose(); } while (strategy.contentsRestored()); strategy.show(); } while (strategy.contentsLost()); } else { Graphics2D graphics = (Graphics2D) panelGraphics; graphics.drawImage(testimage, insets.left, insets.top, 1280, 720, null); frameMarker.accept(graphics); } } }