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.imageio.ImageIO;
  29 import javax.swing.*;
  30 import java.awt.*;
  31 import java.awt.event.WindowAdapter;
  32 import java.awt.event.WindowEvent;
  33 import java.awt.geom.AffineTransform;
  34 import java.awt.geom.Point2D;
  35 import java.awt.geom.QuadCurve2D;
  36 import java.awt.image.BufferedImage;
  37 import java.io.File;
  38 import java.io.IOException;
  39 import java.util.concurrent.CountDownLatch;
  40 import java.util.concurrent.atomic.AtomicBoolean;
  41 
  42 import static org.junit.Assert.assertNotNull;
  43 import static org.junit.Assert.assertTrue;
  44 
  45 public class MetalPerfTest {
  46     private final static int N = 500;
  47     private final static float WIDTH = 800;
  48     private final static float HEIGHT = 800;
  49     private final static float R = 25;
  50     private final static int BW = 50;
  51     private final static int BH = 50;
  52     private final static int COUNT = 300;
  53     private final static int DELAY = 10;
  54     private final static int RESOLUTION = 5;
  55     private final static int COLOR_TOLERANCE = 10;
  56 
  57 
  58     interface Renderable {
  59         void render(Graphics2D g2d);
  60         void update();
  61     }
  62 
  63     static class Particles {
  64         private float[] bx;
  65         private float[] by;
  66         private float[] vx;
  67         private float[] vy;
  68         private float r;
  69         private int n;
  70 
  71         private float x0;
  72         private float y0;
  73         private float width;
  74         private float height;
  75 
  76         Particles(int n, float r, float x0, float y0, float width, float height) {
  77             bx = new float[n];
  78             by = new float[n];
  79             vx = new float[n];
  80             vy = new float[n];
  81             this.n = n;
  82             this.r = r;
  83             this.x0 = x0;
  84             this.y0 = y0;
  85             this.width = width;
  86             this.height = height;
  87             for (int i = 0; i < n; i++) {
  88                 bx[i] = (float) (x0 + r + 0.1 + Math.random() * (width - 2 * r - 0.2 - x0));
  89                 by[i] = (float) (y0 + r + 0.1 + Math.random() * (height - 2 * r - 0.2 - y0));
  90                 vx[i] = 0.1f * (float) (Math.random() * 2 * r - r);
  91                 vy[i] = 0.1f * (float) (Math.random() * 2 * r - r);
  92             }
  93 
  94         }
  95 
  96         void render(Graphics2D g2d, ParticleRenderer renderer) {
  97             for (int i = 0; i < n; i++) {
  98                 renderer.render(g2d, i, bx, by, vx, vy);
  99             }
 100         }
 101 
 102         void update() {
 103             for (int i = 0; i < n; i++) {
 104                 bx[i] += vx[i];
 105                 if (bx[i] + r > width || bx[i] - r < x0) vx[i] = -vx[i];
 106                 by[i] += vy[i];
 107                 if (by[i] + r > height || by[i] - r < y0) vy[i] = -vy[i];
 108             }
 109 
 110         }
 111     }
 112 
 113     interface ParticleRenderer {
 114         void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy);
 115 
 116     }
 117 
 118     static class FlatParticleRenderer implements ParticleRenderer {
 119         Color[] colors;
 120         float r;
 121 
 122         FlatParticleRenderer(int n, float r) {
 123             colors = new Color[n];
 124             this.r = r;
 125             for (int i = 0; i < n; i++) {
 126                 colors[i] = new Color((float) Math.random(),
 127                         (float) Math.random(), (float) Math.random());
 128             }
 129         }
 130 
 131         @Override
 132         public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
 133             g2d.setColor(colors[id % colors.length]);
 134             g2d.fillOval((int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r));
 135         }
 136 
 137     }
 138 
 139     static class FlatOvalRotParticleRenderer extends FlatParticleRenderer {
 140 
 141 
 142         FlatOvalRotParticleRenderer(int n, float r) {
 143             super(n, r);
 144         }
 145 
 146         void setPaint(Graphics2D g2d, int id) {
 147             g2d.setColor(colors[id % colors.length]);
 148         }
 149 
 150         @Override
 151         public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
 152             setPaint(g2d, id);
 153             if (Math.abs(vx[id] + vy[id]) > 0.001) {
 154                 AffineTransform t = (AffineTransform) g2d.getTransform().clone();
 155                 double l = vx[id] / Math.sqrt(vx[id] * vx[id] + vy[id] * vy[id]);
 156                 if (vy[id] < 0) {
 157                     l = -l;
 158                 }
 159                 g2d.translate(x[id], y[id]);
 160                 g2d.rotate(Math.acos(l));
 161                 g2d.fillOval(-(int)r, (int)(-0.5*r), (int) (2 * r), (int)r);
 162                 g2d.setTransform(t);
 163             } else {
 164                 g2d.fillOval((int)(x[id] - r), (int)(y[id] - 0.5*r),
 165                         (int) (2 * r), (int) r);
 166             }
 167         }
 168     }
 169 
 170     static class LinGradOvalRotParticleRenderer extends FlatOvalRotParticleRenderer {
 171 
 172 
 173         LinGradOvalRotParticleRenderer(int n, float r) {
 174             super(n, r);
 175         }
 176 
 177         @Override
 178         void setPaint(Graphics2D g2d, int id) {
 179             Point2D start = new Point2D.Double(- r,  - 0.5*r);
 180             Point2D end = new Point2D.Double( 2 * r, r);
 181             float[] dist = {0.0f, 1.0f};
 182             Color[] cls = {colors[id %colors.length], colors[(colors.length - id) %colors.length]};
 183             LinearGradientPaint p =
 184                     new LinearGradientPaint(start, end, dist, cls);
 185             g2d.setPaint(p);
 186         }
 187     }
 188 
 189     static class FlatBoxParticleRenderer extends FlatParticleRenderer {
 190 
 191 
 192         FlatBoxParticleRenderer(int n, float r) {
 193             super(n, r);
 194         }
 195         @Override
 196         public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
 197             g2d.setColor(colors[id % colors.length]);
 198             g2d.fillRect((int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r));
 199 
 200         }
 201 
 202     }
 203 
 204     static class ImgParticleRenderer extends FlatParticleRenderer {
 205         BufferedImage dukeImg;
 206 
 207         ImgParticleRenderer(int n, float r) {
 208             super(n, r);
 209             String testDataStr = System.getProperty("testdata");
 210             assertNotNull("testdata property is not set", testDataStr);
 211 
 212             File testData = new File(testDataStr, "perf" + File.separator + "metal");
 213             assertTrue("Test data dir does not exist", testData.exists());
 214             File dukeFile = new File(testData, "duke.png");
 215 
 216             if (!dukeFile.exists()) throw new RuntimeException(dukeFile.toString() + " not found");
 217 
 218             try {
 219                 dukeImg = ImageIO.read(dukeFile);
 220             } catch (IOException e) {
 221                 throw new RuntimeException(e);
 222             }
 223         }
 224 
 225         @Override
 226         public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
 227             g2d.setColor(colors[id % colors.length]);
 228             g2d.drawImage(dukeImg, (int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r), null);
 229         }
 230 
 231     }
 232 
 233     static class FlatBoxRotParticleRenderer extends FlatParticleRenderer {
 234 
 235 
 236         FlatBoxRotParticleRenderer(int n, float r) {
 237             super(n, r);
 238         }
 239         @Override
 240         public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
 241             g2d.setColor(colors[id % colors.length]);
 242             if (Math.abs(vx[id] + vy[id]) > 0.001) {
 243                 AffineTransform t = (AffineTransform) g2d.getTransform().clone();
 244                 double l = vx[id] / Math.sqrt(vx[id] * vx[id] + vy[id] * vy[id]);
 245                 if (vy[id] < 0) {
 246                     l = -l;
 247                 }
 248                 g2d.translate(x[id], y[id]);
 249                 g2d.rotate(Math.acos(l));
 250                 g2d.fillRect(-(int)r, -(int)r, (int) (2 * r), (int) (2 * r));
 251                 g2d.setTransform(t);
 252             } else {
 253                 g2d.fillRect((int)(x[id] - r), (int)(y[id] - r),
 254                         (int) (2 * r), (int) (2 * r));
 255             }
 256         }
 257     }
 258 
 259     static class WiredParticleRenderer extends FlatParticleRenderer {
 260 
 261 
 262         WiredParticleRenderer(int n, float r) {
 263             super(n, r);
 264         }
 265         @Override
 266         public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
 267             g2d.setColor(colors[id % colors.length]);
 268             g2d.drawOval((int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r));
 269         }
 270 
 271     }
 272     static class WiredBoxParticleRenderer extends FlatParticleRenderer {
 273 
 274         WiredBoxParticleRenderer(int n, float r) {
 275             super(n, r);
 276         }
 277 
 278         @Override
 279         public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
 280             g2d.setColor(colors[id % colors.length]);
 281             g2d.drawRect((int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r));
 282         }
 283 
 284     }
 285     static class SegParticleRenderer extends FlatParticleRenderer {
 286 
 287         SegParticleRenderer(int n, float r) {
 288             super(n, r);
 289         }
 290 
 291         @Override
 292         public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
 293             double v = Math.sqrt(vx[id]*vx[id]+vy[id]*vy[id]);
 294             float nvx = (float) (vx[id]/v);
 295             float nvy = (float) (vy[id]/v);
 296             g2d.setColor(colors[id % colors.length]);
 297             g2d.drawLine((int)(x[id] - r*nvx), (int)(y[id] - r*nvy),
 298                     (int)(x[id] + 2*r*nvx), (int)(y[id] + 2*r*nvy));
 299         }
 300 
 301     }
 302 
 303 
 304     static class WiredQuadParticleRenderer extends FlatParticleRenderer {
 305 
 306         WiredQuadParticleRenderer(int n, float r) {
 307             super(n, r);
 308         }
 309 
 310         @Override
 311         public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
 312             if (id > 2 && (id % 3) == 0) {
 313                 g2d.setColor(colors[id % colors.length]);
 314                 g2d.draw(new QuadCurve2D.Float(x[id-3], y[id-3], x[id-2], y[id-2], x[id-1], y[id-1]));
 315             }
 316 
 317         }
 318     }
 319 
 320     static class FlatQuadParticleRenderer extends FlatParticleRenderer {
 321 
 322         FlatQuadParticleRenderer(int n, float r) {
 323             super(n, r);
 324         }
 325 
 326         @Override
 327         public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) {
 328             if (id > 2 && (id % 3) == 0) {
 329                 g2d.setColor(colors[id % colors.length]);
 330                 g2d.fill(new QuadCurve2D.Float(x[id-3], y[id-3], x[id-2], y[id-2], x[id-1], y[id-1]));
 331             }
 332 
 333         }
 334     }
 335 
 336     class PerfMeter {
 337 
 338         private int frame = 0;
 339 
 340         private JPanel panel;
 341 
 342         private long time;
 343         private double execTime = 0;
 344         private Color expColor = Color.RED;
 345         AtomicBoolean waiting = new AtomicBoolean(false);
 346 
 347         double exec(final Renderable renderable) throws Exception{
 348             final CountDownLatch latch = new CountDownLatch(COUNT);
 349             final CountDownLatch latchFrame = new CountDownLatch(1);
 350 
 351             final JFrame f = new JFrame();
 352             f.addWindowListener(new WindowAdapter() {
 353                 @Override
 354                 public void windowClosed(WindowEvent e) {
 355                     latchFrame.countDown();
 356                 }
 357             });
 358 
 359             SwingUtilities.invokeAndWait(new Runnable() {
 360                 @Override
 361                 public void run() {
 362 
 363                     panel = new JPanel()
 364                     {
 365                         @Override
 366                         protected void paintComponent(Graphics g) {
 367 
 368                             super.paintComponent(g);
 369                             time = System.nanoTime();
 370                             Graphics2D g2d = (Graphics2D) g;
 371                             renderable.render(g2d);
 372                             g2d.setColor(expColor);
 373                             g.fillRect(0, 0, BW, BH);
 374                         }
 375                     };
 376 
 377                     panel.setPreferredSize(new Dimension((int)(WIDTH + BW), (int)(HEIGHT + BH)));
 378                     panel.setBackground(Color.BLACK);
 379                     f.add(panel);
 380                     f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
 381                     f.pack();
 382                     f.setVisible(true);
 383                 }
 384             });
 385             Robot robot = new Robot();
 386 
 387             Timer timer = new Timer(DELAY, e -> {
 388 
 389                 if (waiting.compareAndSet(false, true)) {
 390                     Color c = robot.getPixelColor(panel.getTopLevelAncestor().getX() + 25,
 391                             panel.getTopLevelAncestor().getY() + 25);
 392                     if (isAlmostEqual(c, Color.BLUE)) {
 393                         expColor = Color.RED;
 394                     } else {
 395                         expColor = Color.BLUE;
 396                     }
 397                     renderable.update();
 398                     panel.getParent().repaint();
 399 
 400                 } else {
 401                     while (!isAlmostEqual(
 402                             robot.getPixelColor(
 403                                     panel.getTopLevelAncestor().getX() + BW / 2,
 404                                     panel.getTopLevelAncestor().getY() + BH / 2),
 405                             expColor))
 406                     {
 407                         try {
 408                             Thread.sleep(RESOLUTION);
 409                         } catch (InterruptedException ex) {
 410                             ex.printStackTrace();
 411                         }
 412                     }
 413                     time = System.nanoTime() - time;
 414                     execTime += time;
 415                     frame++;
 416                     waiting.set(false);
 417                 }
 418 
 419                 latch.countDown();
 420             });
 421             timer.start();
 422             latch.await();
 423             SwingUtilities.invokeAndWait(() -> {
 424                 timer.stop();
 425                 f.setVisible(false);
 426                 f.dispose();
 427             });
 428 
 429             latchFrame.await();
 430             return 1e9/(execTime / frame);
 431         }
 432 
 433         private boolean isAlmostEqual(Color c1, Color c2) {
 434             return Math.abs(c1.getRed() - c2.getRed()) < COLOR_TOLERANCE ||
 435                     Math.abs(c1.getGreen() - c2.getGreen()) < COLOR_TOLERANCE ||
 436                     Math.abs(c1.getBlue() - c2.getBlue()) < COLOR_TOLERANCE;
 437 
 438         }
 439     }
 440 
 441     private static final Particles balls = new Particles(N, R, BW, BH, WIDTH, HEIGHT);
 442     private static final ParticleRenderer flatRenderer = new FlatParticleRenderer(N, R);
 443     private static final ParticleRenderer flatOvalRotRenderer = new FlatOvalRotParticleRenderer(N, R);
 444     private static final ParticleRenderer flatBoxRenderer = new FlatBoxParticleRenderer(N, R);
 445     private static final ParticleRenderer flatBoxRotRenderer = new FlatBoxRotParticleRenderer(N, R);
 446     private static final ParticleRenderer linGradOvalRotRenderer = new LinGradOvalRotParticleRenderer(N, R);
 447     private static final ParticleRenderer wiredRenderer = new WiredParticleRenderer(N, R);
 448     private static final ParticleRenderer wiredBoxRenderer = new WiredBoxParticleRenderer(N, R);
 449     private static final ParticleRenderer segRenderer = new SegParticleRenderer(N, R);
 450     private static final ParticleRenderer flatQuadRenderer = new FlatQuadParticleRenderer(N, R);
 451     private static final ParticleRenderer wiredQuadRenderer = new WiredQuadParticleRenderer(N, R);
 452     private static final ParticleRenderer imgRenderer = new ImgParticleRenderer(N, R);
 453 
 454 
 455     @Test
 456     public void testFlatBubbles() throws Exception {
 457 
 458         double fps = (new PerfMeter()).exec(new Renderable() {
 459             @Override
 460             public void render(Graphics2D g2d) {
 461                 balls.render(g2d, flatRenderer);
 462             }
 463 
 464             @Override
 465             public void update() {
 466                 balls.update();
 467             }
 468         });
 469 
 470         System.out.println(fps);
 471     }
 472 
 473     @Test
 474     public void testFlatBoxBubbles() throws Exception {
 475 
 476         double fps = (new PerfMeter()).exec(new Renderable() {
 477             @Override
 478             public void render(Graphics2D g2d) {
 479                 balls.render(g2d, flatBoxRenderer);
 480             }
 481 
 482             @Override
 483             public void update() {
 484                 balls.update();
 485             }
 486         });
 487 
 488         System.out.println(fps);
 489     }
 490 
 491     @Test
 492     public void testImgBubbles() throws Exception {
 493 
 494         double fps = (new PerfMeter()).exec(new Renderable() {
 495             @Override
 496             public void render(Graphics2D g2d) {
 497                 balls.render(g2d, imgRenderer);
 498             }
 499 
 500             @Override
 501             public void update() {
 502                 balls.update();
 503             }
 504         });
 505 
 506         System.out.println(fps);
 507     }
 508 
 509     @Test
 510     public void testFlatBoxRotBubbles() throws Exception {
 511 
 512         double fps = (new PerfMeter()).exec(new Renderable() {
 513             @Override
 514             public void render(Graphics2D g2d) {
 515                 balls.render(g2d, flatBoxRotRenderer);
 516             }
 517 
 518             @Override
 519             public void update() {
 520                 balls.update();
 521             }
 522         });
 523 
 524         System.out.println(fps);
 525     }
 526 
 527     @Test
 528     public void testFlatOvalRotBubbles() throws Exception {
 529 
 530         double fps = (new PerfMeter()).exec(new Renderable() {
 531             @Override
 532             public void render(Graphics2D g2d) {
 533                 balls.render(g2d, flatOvalRotRenderer);
 534             }
 535 
 536             @Override
 537             public void update() {
 538                 balls.update();
 539             }
 540         });
 541 
 542         System.out.println(fps);
 543     }
 544 
 545     @Test
 546     public void testLinGradOvalRotBubbles() throws Exception {
 547 
 548         double fps = (new PerfMeter()).exec(new Renderable() {
 549             @Override
 550             public void render(Graphics2D g2d) {
 551                 balls.render(g2d, linGradOvalRotRenderer);
 552             }
 553 
 554             @Override
 555             public void update() {
 556                 balls.update();
 557             }
 558         });
 559 
 560         System.out.println(fps);
 561     }
 562 
 563 
 564     @Test
 565     public void testWiredBubbles() throws Exception {
 566 
 567         double fps = (new PerfMeter()).exec(new Renderable() {
 568             @Override
 569             public void render(Graphics2D g2d) {
 570                 balls.render(g2d, wiredRenderer);
 571             }
 572 
 573             @Override
 574             public void update() {
 575                 balls.update();
 576             }
 577         });
 578 
 579         System.out.println(fps);
 580     }
 581 
 582     @Test
 583     public void testWiredBoxBubbles() throws Exception {
 584 
 585         double fps = (new PerfMeter()).exec(new Renderable() {
 586             @Override
 587             public void render(Graphics2D g2d) {
 588                 balls.render(g2d, wiredBoxRenderer);
 589             }
 590 
 591             @Override
 592             public void update() {
 593                 balls.update();
 594             }
 595         });
 596 
 597         System.out.println(fps);
 598     }
 599 
 600     @Test
 601     public void testLines() throws Exception {
 602 
 603         double fps = (new PerfMeter()).exec(new Renderable() {
 604             @Override
 605             public void render(Graphics2D g2d) {
 606                 balls.render(g2d, segRenderer);
 607             }
 608 
 609             @Override
 610             public void update() {
 611                 balls.update();
 612             }
 613         });
 614 
 615         System.out.println(fps);
 616     }
 617 
 618     @Test
 619     public void testFlatQuad() throws Exception {
 620 
 621         double fps = (new PerfMeter()).exec(new Renderable() {
 622             @Override
 623             public void render(Graphics2D g2d) {
 624                 balls.render(g2d, flatQuadRenderer);
 625             }
 626 
 627             @Override
 628             public void update() {
 629                 balls.update();
 630             }
 631         });
 632 
 633         System.out.println(fps);
 634     }
 635 
 636     @Test
 637     public void testWiredQuad() throws Exception {
 638 
 639         double fps = (new PerfMeter()).exec(new Renderable() {
 640             @Override
 641             public void render(Graphics2D g2d) {
 642                 balls.render(g2d, wiredQuadRenderer);
 643             }
 644 
 645             @Override
 646             public void update() {
 647                 balls.update();
 648             }
 649         });
 650 
 651         System.out.println(fps);
 652     }
 653 
 654 }