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 }