1 /*
   2  * Copyright 2019 JetBrains s.r.o.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 package perf.metal;
  25 
  26 import org.junit.Test;
  27 
  28 import javax.swing.*;
  29 import java.awt.*;
  30 import java.awt.event.WindowAdapter;
  31 import java.awt.event.WindowEvent;
  32 import java.awt.geom.QuadCurve2D;
  33 import java.util.concurrent.CountDownLatch;
  34 import java.util.concurrent.atomic.AtomicBoolean;
  35 
  36 public class MetalPerfTest {
  37     private final static int N = 500;
  38     private final static float WIDTH = 800;
  39     private final static float HEIGHT = 800;
  40     private final static float R = 25;
  41     private final static int BW = 50;
  42     private final static int BH = 50;
  43     private final static int COUNT = 300;
  44     private final static int DELAY = 10;
  45     private final static int RESOLUTION = 5;
  46     private final static int COLOR_TOLERANCE = 10;
  47 
  48 
  49     interface Renderable {
  50         void render(Graphics2D g2d);
  51         void update();
  52     }
  53 
  54     static class Particles {
  55         private float[] bx;
  56         private float[] by;
  57         private float[] vx;
  58         private float[] vy;
  59         private float r;
  60         private int n;
  61 
  62         private float x0;
  63         private float y0;
  64         private float width;
  65         private float height;
  66 
  67         Particles(int n, float r, float x0, float y0, float width, float height) {
  68             bx = new float[n];
  69             by = new float[n];
  70             vx = new float[n];
  71             vy = new float[n];
  72             this.n = n;
  73             this.r = r;
  74             this.x0 = x0;
  75             this.y0 = y0;
  76             this.width = width;
  77             this.height = height;
  78             for (int i = 0; i < n; i++) {
  79                 bx[i] = (float) (x0 + r + 0.1 + Math.random() * (width - 2 * r - 0.2 - x0));
  80                 by[i] = (float) (y0 + r + 0.1 + Math.random() * (height - 2 * r - 0.2 - y0));
  81                 vx[i] = 0.1f * (float) (Math.random() * 2 * r - r);
  82                 vy[i] = 0.1f * (float) (Math.random() * 2 * r - r);
  83             }
  84 
  85         }
  86 
  87         void render(Graphics2D g2d, ParticleRenderer renderer) {
  88             for (int i = 0; i < n; i++) {
  89                 renderer.render(g2d, i, bx, by, vx, vy);
  90             }
  91         }
  92 
  93         void update() {
  94             for (int i = 0; i < n; i++) {
  95                 bx[i] += vx[i];
  96                 if (bx[i] + r > width || bx[i] - r < x0) vx[i] = -vx[i];
  97                 by[i] += vy[i];
  98                 if (by[i] + r > height || by[i] - r < y0) vy[i] = -vy[i];
  99             }
 100 
 101         }
 102     }
 103 
 104     interface ParticleRenderer {
 105         void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy);
 106 
 107     }
 108 
 109     static class FlatParticleRenderer implements ParticleRenderer {
 110         Color[] colors;
 111         float r;
 112 
 113         FlatParticleRenderer(int n, float r) {
 114             colors = new Color[n];
 115             this.r = r;
 116             for (int i = 0; i < n; i++) {
 117                 colors[i] = new Color((float) Math.random(),
 118                         (float) Math.random(), (float) Math.random());
 119             }
 120         }
 121 
 122         @Override
 123         public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
 124             g2d.setColor(colors[id % colors.length]);
 125             g2d.fillOval((int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r));
 126         }
 127 
 128     }
 129     static class FlatBoxParticleRenderer extends FlatParticleRenderer {
 130 
 131 
 132         FlatBoxParticleRenderer(int n, float r) {
 133             super(n, r);
 134         }
 135         @Override
 136         public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
 137             g2d.setColor(colors[id % colors.length]);
 138             g2d.fillRect((int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r));
 139 
 140         }
 141 
 142     }
 143     static class WiredParticleRenderer extends FlatParticleRenderer {
 144 
 145 
 146         WiredParticleRenderer(int n, float r) {
 147             super(n, r);
 148         }
 149         @Override
 150         public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
 151             g2d.setColor(colors[id % colors.length]);
 152             g2d.drawOval((int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r));
 153         }
 154 
 155     }
 156     static class WiredBoxParticleRenderer extends FlatParticleRenderer {
 157 
 158         WiredBoxParticleRenderer(int n, float r) {
 159             super(n, r);
 160         }
 161 
 162         @Override
 163         public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
 164             g2d.setColor(colors[id % colors.length]);
 165             g2d.drawRect((int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r));
 166         }
 167 
 168     }
 169     static class SegParticleRenderer extends FlatParticleRenderer {
 170 
 171         SegParticleRenderer(int n, float r) {
 172             super(n, r);
 173         }
 174 
 175         @Override
 176         public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
 177             double v = Math.sqrt(vx[id]*vx[id]+vy[id]*vy[id]);
 178             float nvx = (float) (vx[id]/v);
 179             float nvy = (float) (vy[id]/v);
 180             g2d.setColor(colors[id % colors.length]);
 181             g2d.drawLine((int)(x[id] - r*nvx), (int)(y[id] - r*nvy),
 182                     (int)(x[id] + 2*r*nvx), (int)(y[id] + 2*r*nvy));
 183         }
 184 
 185     }
 186 
 187 
 188     static class WiredQuadParticleRenderer extends FlatParticleRenderer {
 189 
 190         WiredQuadParticleRenderer(int n, float r) {
 191             super(n, r);
 192         }
 193 
 194         @Override
 195         public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
 196             if (id > 2 && (id % 3) == 0) {
 197                 g2d.setColor(colors[id % colors.length]);
 198                 g2d.draw(new QuadCurve2D.Float(x[id-3], y[id-3], x[id-2], y[id-2], x[id-1], y[id-1]));
 199             }
 200 
 201         }
 202     }
 203 
 204     static class FlatQuadParticleRenderer extends FlatParticleRenderer {
 205 
 206         FlatQuadParticleRenderer(int n, float r) {
 207             super(n, r);
 208         }
 209 
 210         @Override
 211         public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
 212             if (id > 2 && (id % 3) == 0) {
 213                 g2d.setColor(colors[id % colors.length]);
 214                 g2d.fill(new QuadCurve2D.Float(x[id-3], y[id-3], x[id-2], y[id-2], x[id-1], y[id-1]));
 215             }
 216 
 217         }
 218     }
 219 
 220     class PerfMeter {
 221 
 222         private int frame = 0;
 223 
 224         private JPanel panel;
 225 
 226         private long time;
 227         private double execTime = 0;
 228         private Color expColor = Color.RED;
 229         AtomicBoolean waiting = new AtomicBoolean(false);
 230 
 231         double exec(final Renderable renderable) throws Exception{
 232             final CountDownLatch latch = new CountDownLatch(COUNT);
 233             final CountDownLatch latchFrame = new CountDownLatch(1);
 234 
 235             final JFrame f = new JFrame();
 236             f.addWindowListener(new WindowAdapter() {
 237                 @Override
 238                 public void windowClosed(WindowEvent e) {
 239                     latchFrame.countDown();
 240                 }
 241             });
 242 
 243             SwingUtilities.invokeAndWait(new Runnable() {
 244                 @Override
 245                 public void run() {
 246 
 247                     panel = new JPanel()
 248                     {
 249                         @Override
 250                         protected void paintComponent(Graphics g) {
 251 
 252                             super.paintComponent(g);
 253                             time = System.nanoTime();
 254                             Graphics2D g2d = (Graphics2D) g;
 255                             renderable.render(g2d);
 256                             g2d.setColor(expColor);
 257                             g.fillRect(0, 0, BW, BH);
 258                         }
 259                     };
 260 
 261                     panel.setPreferredSize(new Dimension((int)(WIDTH + BW), (int)(HEIGHT + BH)));
 262                     panel.setBackground(Color.BLACK);
 263                     f.add(panel);
 264                     f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
 265                     f.pack();
 266                     f.setVisible(true);
 267                 }
 268             });
 269             Robot robot = new Robot();
 270 
 271             Timer timer = new Timer(DELAY, e -> {
 272 
 273                 if (waiting.compareAndSet(false, true)) {
 274                     Color c = robot.getPixelColor(panel.getTopLevelAncestor().getX() + 25,
 275                             panel.getTopLevelAncestor().getY() + 25);
 276                     if (isAlmostEqual(c, Color.BLUE)) {
 277                         expColor = Color.RED;
 278                     } else {
 279                         expColor = Color.BLUE;
 280                     }
 281                     renderable.update();
 282                     panel.getParent().repaint();
 283 
 284                 } else {
 285                     while (!isAlmostEqual(
 286                             robot.getPixelColor(
 287                                     panel.getTopLevelAncestor().getX() + BW / 2,
 288                                     panel.getTopLevelAncestor().getY() + BH / 2),
 289                             expColor))
 290                     {
 291                         try {
 292                             Thread.sleep(RESOLUTION);
 293                         } catch (InterruptedException ex) {
 294                             ex.printStackTrace();
 295                         }
 296                     }
 297                     time = System.nanoTime() - time;
 298                     execTime += time;
 299                     frame++;
 300                     waiting.set(false);
 301                 }
 302 
 303                 latch.countDown();
 304             });
 305             timer.start();
 306             latch.await();
 307             SwingUtilities.invokeAndWait(() -> {
 308                 timer.stop();
 309                 f.setVisible(false);
 310                 f.dispose();
 311             });
 312 
 313             latchFrame.await();
 314             return 1e9/(execTime / frame);
 315         }
 316 
 317         private boolean isAlmostEqual(Color c1, Color c2) {
 318             return Math.abs(c1.getRed() - c2.getRed()) < COLOR_TOLERANCE ||
 319                     Math.abs(c1.getGreen() - c2.getGreen()) < COLOR_TOLERANCE ||
 320                     Math.abs(c1.getBlue() - c2.getBlue()) < COLOR_TOLERANCE;
 321 
 322         }
 323     }
 324 
 325     private static final Particles balls = new Particles(N, R, BW, BH, WIDTH, HEIGHT);
 326     private static final ParticleRenderer flatRenderer = new FlatParticleRenderer(N, R);
 327     private static final ParticleRenderer flatBoxRenderer = new FlatBoxParticleRenderer(N, R);
 328     private static final ParticleRenderer wiredRenderer = new WiredParticleRenderer(N, R);
 329     private static final ParticleRenderer wiredBoxRenderer = new WiredBoxParticleRenderer(N, R);
 330     private static final ParticleRenderer segRenderer = new SegParticleRenderer(N, R);
 331     private static final ParticleRenderer flatQuadRenderer = new FlatQuadParticleRenderer(N, R);
 332     private static final ParticleRenderer wiredQuadRenderer = new WiredQuadParticleRenderer(N, R);
 333 
 334 
 335     @Test
 336     public void testFlatBubbles() throws Exception {
 337 
 338         double fps = (new PerfMeter()).exec(new Renderable() {
 339             @Override
 340             public void render(Graphics2D g2d) {
 341                 balls.render(g2d, flatRenderer);
 342             }
 343 
 344             @Override
 345             public void update() {
 346                 balls.update();
 347             }
 348         });
 349 
 350         System.out.println(fps);
 351     }
 352 
 353     @Test
 354     public void testFlatBoxBubbles() throws Exception {
 355 
 356         double fps = (new PerfMeter()).exec(new Renderable() {
 357             @Override
 358             public void render(Graphics2D g2d) {
 359                 balls.render(g2d, flatBoxRenderer);
 360             }
 361 
 362             @Override
 363             public void update() {
 364                 balls.update();
 365             }
 366         });
 367 
 368         System.out.println(fps);
 369     }
 370 
 371     @Test
 372     public void testWiredBubbles() throws Exception {
 373 
 374         double fps = (new PerfMeter()).exec(new Renderable() {
 375             @Override
 376             public void render(Graphics2D g2d) {
 377                 balls.render(g2d, wiredRenderer);
 378             }
 379 
 380             @Override
 381             public void update() {
 382                 balls.update();
 383             }
 384         });
 385 
 386         System.out.println(fps);
 387     }
 388 
 389     @Test
 390     public void testWiredBoxBubbles() throws Exception {
 391 
 392         double fps = (new PerfMeter()).exec(new Renderable() {
 393             @Override
 394             public void render(Graphics2D g2d) {
 395                 balls.render(g2d, wiredBoxRenderer);
 396             }
 397 
 398             @Override
 399             public void update() {
 400                 balls.update();
 401             }
 402         });
 403 
 404         System.out.println(fps);
 405     }
 406 
 407     @Test
 408     public void testLines() throws Exception {
 409 
 410         double fps = (new PerfMeter()).exec(new Renderable() {
 411             @Override
 412             public void render(Graphics2D g2d) {
 413                 balls.render(g2d, segRenderer);
 414             }
 415 
 416             @Override
 417             public void update() {
 418                 balls.update();
 419             }
 420         });
 421 
 422         System.out.println(fps);
 423     }
 424 
 425     @Test
 426     public void testFlatQuad() throws Exception {
 427 
 428         double fps = (new PerfMeter()).exec(new Renderable() {
 429             @Override
 430             public void render(Graphics2D g2d) {
 431                 balls.render(g2d, flatQuadRenderer);
 432             }
 433 
 434             @Override
 435             public void update() {
 436                 balls.update();
 437             }
 438         });
 439 
 440         System.out.println(fps);
 441     }
 442 
 443     @Test
 444     public void testWiredQuad() throws Exception {
 445 
 446         double fps = (new PerfMeter()).exec(new Renderable() {
 447             @Override
 448             public void render(Graphics2D g2d) {
 449                 balls.render(g2d, wiredQuadRenderer);
 450             }
 451 
 452             @Override
 453             public void update() {
 454                 balls.update();
 455             }
 456         });
 457 
 458         System.out.println(fps);
 459     }
 460 
 461 }