1 /*
   2  * Copyright (c) 2002, 2016, Oracle and/or its affiliates. All rights reserved.
   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 /**
  25  * @test
  26  * @key headful
  27  * @bug   4780022 4862193 7179526
  28  * @summary  Tests that clipped lines are drawn over the same pixels
  29  * as unclipped lines (within the clip bounds)
  30  * @run main/timeout=600/othervm -Dsun.java2d.ddforcevram=true LineClipTest
  31  * @run main/timeout=600/othervm LineClipTest
  32  */
  33 
  34 
  35 /**
  36  * This app tests whether we are drawing clipped lines the same
  37  * as unclipped lines.  The problem occurred when we started
  38  * clipping d3d lines using simple integer clipping, which did not
  39  * account for sub-pixel precision and ended up drawing very different
  40  * pixels than the same line drawn unclipped.  A supposed fix
  41  * to that problem used floating-point clipping instead, but there
  42  * was some problem with very limited precision inside of d3d
  43  * (presumably in hardware) that caused some variation in pixels.
  44  * We decided that whatever the fix was, we needed a serious
  45  * line check test to make sure that all kinds of different
  46  * lines would be drawn exactly the same inside the clip area,
  47  * regardless of whether clipping was enabled.  This test should
  48  * check all kinds of different cases, such as lines that fall
  49  * completely outside, completely inside, start outside and
  50  * end inside, etc., and lines should end and originate in
  51  * all quadrants of the space divided up by the clip box.
  52  *
  53  * The test works as follows:
  54  * We create nine quadrants using the spaces bisected by the
  55  * edges of the clip bounds (note that only one of these
  56  * quadrants is actually visible when clipping is enabled).
  57  * We create several points in each of these quadrants
  58  * (three in each of the invisible quadrants, nine in the
  59  * center/visible quadrant).  Our resulting grid looks like
  60  * this:
  61  *
  62  *  x         x|x    x    x|x         x
  63  *             |           |
  64  *             |           |
  65  *             |           |
  66  *             |           |
  67  *             |           |
  68  *  x          |           |          x
  69  *  -----------------------------------
  70  *  x          |x    x    x|          x
  71  *             |           |
  72  *             |           |
  73  *  x          |x    x    x|          x
  74  *             |           |
  75  *             |           |
  76  *  x          |x    x    x|          x
  77  *  -----------------------------------
  78  *  x          |           |          x
  79  *             |           |
  80  *             |           |
  81  *             |           |
  82  *             |           |
  83  *             |           |
  84  *  x         x|x    x    x|x         x
  85  *
  86  * The test then draws lines from every point to every other
  87  * point.  First, we draw unclipped lines in blue and
  88  * then we draw clipped lines in red.
  89  * At certain times (after every point during the default
  90  * test, after every quadrant of lines if you run with the -quick
  91  * option), we check for errors and draw the current image
  92  * to the screen.  Error checking consists of copying the
  93  * VolatileImage to a BufferedImage (because we need access
  94  * to the pixels directly) and checking every pixel in the
  95  * image.  The check is simple: everything outside the
  96  * clip bounds should be blue (or the background color) and
  97  * everything inside the clip bounds should be red (or the
  98  * background color).  So any blue pixel inside or red
  99  * pixel outside means that there was a drawing error and
 100  * the test fails.
 101  * There are 4 modes that the test can run in (dynamic mode is
 102  * exclusive to the other modes, but the other modes are combinable):
 103  *
 104  *      (default): the clip is set
 105  *      to a default size (100x100) and the test is run.
 106  *
 107  *      -quick: The error
 108  *      check is run only after every quadrant of lines is
 109  *      drawn.  This speeds up the test considerably with
 110  *      some less accuracy in error checking (because pixels
 111  *      from some lines may overdrawn pixels from other lines
 112  *      before we have verified the correctness of those
 113  *      pixels).
 114  *
 115  *      -dynamic: There is no error checking, but this version
 116  *      of the test automatically resizes the clip bounds and
 117  *      reruns the test over and over.  Nothing besides the
 118  *      visual check verifies that the test is running correctly.
 119  *
 120  *      -rect: Instead of drawing lines, the test draws rectangles
 121  *      to/from all points in all quadrants.  This tests similar
 122  *      clipping functionality for drawRect().
 123  *
 124  *      n (where "n" is a number): sets the clip size to the
 125  *      given value.  Just like the default test except that
 126  *      the clip size is as specified.
 127  *
 128  * Note: this test must be run with the -Dsun.java2d.ddforcevram=true
 129  * option to force the test image to stay in VRAM.  We currently
 130  * punt VRAM images to system memory when we detect lots of
 131  * reads.  Since we read the whole buffer on every error check
 132  * to copy it to the BufferedImage), this causes us to punt the
 133  * buffer.  A system memory surface will have no d3d capabilities,
 134  * thus we are not testing the d3d line quality when this happens.
 135  * By using the ddforcevram flag, we make sure the buffer
 136  * stays put in VRAM and d3d is used to draw the lines.
 137  */
 138 
 139 import javax.swing.*;
 140 import java.awt.*;
 141 import java.awt.image.*;
 142 
 143 
 144 public class LineClipTest extends Component implements Runnable {
 145 
 146     int clipBumpVal = 5;
 147     static int clipSize = 100;
 148     int clipX1;
 149     int clipY1;
 150     static final int NUM_QUADS = 9;
 151     Point quadrants[][] = new Point[NUM_QUADS][];
 152     static boolean dynamic = false;
 153     BufferedImage imageChecker = null;
 154     Color unclippedColor = Color.blue;
 155     Color clippedColor = Color.red;
 156     int testW = -1, testH = -1;
 157     VolatileImage testImage = null;
 158     static boolean keepRunning = false;
 159     static boolean quickTest = false;
 160     static boolean rectTest = false;
 161     static boolean runTestDone = false;
 162     static Frame f = null;
 163 
 164     /**
 165      * Check for errors in the grid.  This error check consists of
 166      * copying the buffer into a BufferedImage and reading all pixels
 167      * in that image.  No pixel outside the clip bounds should be
 168      * of the color clippedColor and no pixel inside should be
 169      * of the color unclippedColor.  Any wrong color returns an error.
 170      */
 171     boolean gridError(Graphics g) {
 172         boolean error = false;
 173         if (imageChecker == null || (imageChecker.getWidth() != testW) ||
 174             (imageChecker.getHeight() != testH))
 175         {
 176             // Recreate BufferedImage as necessary
 177             GraphicsConfiguration gc = getGraphicsConfiguration();
 178             ColorModel cm = gc.getColorModel();
 179             WritableRaster wr =
 180                 cm.createCompatibleWritableRaster(getWidth(), getHeight());
 181             imageChecker =
 182                 new BufferedImage(cm, wr,
 183                                   cm.isAlphaPremultiplied(), null);
 184         }
 185         // Copy buffer to BufferedImage
 186         Graphics gChecker = imageChecker.getGraphics();
 187         gChecker.drawImage(testImage, 0, 0, this);
 188 
 189         // Set up pixel colors to check against
 190         int clippedPixelColor = clippedColor.getRGB();
 191         int unclippedPixelColor = unclippedColor.getRGB();
 192         int wrongPixelColor = clippedPixelColor;
 193         boolean insideClip = false;
 194         for (int row = 0; row < getHeight(); ++row) {
 195             for (int col = 0; col < getWidth(); ++col) {
 196                 if (row >= clipY1 && row < (clipY1 + clipSize) &&
 197                     col >= clipX1 && col < (clipX1 + clipSize))
 198                 {
 199                     // Inside clip bounds - should not see unclipped color
 200                     wrongPixelColor = unclippedPixelColor;
 201                 } else {
 202                     // Outside clip - should not see clipped color
 203                     wrongPixelColor = clippedPixelColor;
 204                 }
 205                 int pixel = imageChecker.getRGB(col, row);
 206                 if (pixel == wrongPixelColor) {
 207                     System.out.println("FAILED: pixel = " +
 208                                        Integer.toHexString(pixel) +
 209                                        " at (x, y) = " + col + ", " + row);
 210                     // Draw magenta rectangle around problem pixel in buffer
 211                     // for visual feedback to user
 212                     g.setColor(Color.magenta);
 213                     g.drawRect(col - 1, row - 1, 2, 2);
 214                     error = true;
 215                 }
 216             }
 217         }
 218         return error;
 219     }
 220 
 221     /**
 222      * Draw all test lines and check for errors (unless running
 223      * with -dynamic option)
 224      */
 225     void drawLineGrid(Graphics screenGraphics, Graphics g) {
 226         // Fill buffer with background color
 227         g.setColor(Color.white);
 228         g.fillRect(0, 0, getWidth(), getHeight());
 229 
 230         // Now, iterate through all quadrants
 231         for (int srcQuad = 0; srcQuad < NUM_QUADS; ++srcQuad) {
 232             // Draw lines to all other quadrants
 233             for (int dstQuad = 0; dstQuad < NUM_QUADS; ++dstQuad) {
 234                 for (int srcPoint = 0;
 235                      srcPoint < quadrants[srcQuad].length;
 236                      ++srcPoint)
 237                 {
 238                     // For every point in the source quadrant
 239                     int sx = quadrants[srcQuad][srcPoint].x;
 240                     int sy = quadrants[srcQuad][srcPoint].y;
 241                     for (int dstPoint = 0;
 242                          dstPoint < quadrants[dstQuad].length;
 243                          ++dstPoint)
 244                     {
 245                         int dx = quadrants[dstQuad][dstPoint].x;
 246                         int dy = quadrants[dstQuad][dstPoint].y;
 247                         if (!rectTest) {
 248                             // Draw unclipped/clipped lines to every
 249                             // point in the dst quadrant
 250                             g.setColor(unclippedColor);
 251                             g.drawLine(sx, sy, dx, dy);
 252                             g.setClip(clipX1, clipY1, clipSize, clipSize);
 253                             g.setColor(clippedColor);
 254                             g.drawLine(sx,sy, dx, dy);
 255                         } else {
 256                             // Draw unclipped/clipped rectangles to every
 257                             // point in the dst quadrant
 258                             g.setColor(unclippedColor);
 259                             int w = dx - sx;
 260                             int h = dy - sy;
 261                             g.drawRect(sx, sy, w, h);
 262                             g.setClip(clipX1, clipY1, clipSize, clipSize);
 263                             g.setColor(clippedColor);
 264                             g.drawRect(sx, sy, w, h);
 265                         }
 266                         g.setClip(null);
 267                     }
 268                     if (!dynamic) {
 269                         // Draw screen update for visual feedback
 270                         screenGraphics.drawImage(testImage, 0, 0, this);
 271                         // On default test, check for errors after every
 272                         // src point
 273                         if (!quickTest && gridError(g)) {
 274                             throw new java.lang.RuntimeException("Failed");
 275                         }
 276                     }
 277                 }
 278             }
 279             if (!dynamic && quickTest && gridError(g)) {
 280                 // On quick test, check for errors only after every
 281                 // src quadrant
 282                 throw new java.lang.RuntimeException("Failed");
 283                 //return;
 284             }
 285         }
 286         if (!dynamic) {
 287             System.out.println("PASSED");
 288             if (!keepRunning) {
 289                 f.dispose();
 290             }
 291         }
 292     }
 293 
 294     /**
 295      * If we have not yet run the test, or if the window size has
 296      * changed, or if we are running the test in -dynamic mode,
 297      * run the test.  Then draw the test buffer to the screen
 298      */
 299     public void paint(Graphics g) {
 300         if (dynamic || testImage == null ||
 301             getWidth() != testW || getHeight() != testH)
 302         {
 303             runTest(g);
 304         }
 305         if (testImage != null) {
 306             g.drawImage(testImage, 0, 0, this);
 307         }
 308     }
 309 
 310     /*
 311      * Create the quadrant of points and run the test to draw all the lines
 312      */
 313     public void runTest(Graphics screenGraphics) {
 314         if (getWidth() == 0 || getHeight() == 0) {
 315             // May get here before window is really ready
 316             return;
 317         }
 318         clipX1 = (getWidth() - clipSize) / 2;
 319         clipY1 = (getHeight() - clipSize) / 2;
 320         int clipX2 = clipX1 + clipSize;
 321         int clipY2 = clipY1 + clipSize;
 322         int centerX = getWidth()/2;
 323         int centerY = getHeight()/2;
 324         int leftX = 0;
 325         int topY = 0;
 326         int rightX = getWidth() - 1;
 327         int bottomY = getHeight() - 1;
 328         int quadIndex = 0;
 329         // Offsets are used to force diagonal (versus hor/vert) lines
 330         int xOffset = 0;
 331         int yOffset = 0;
 332 
 333         if (quadrants[0] == null) {
 334             for (int i = 0; i < 9; ++i) {
 335                 int numPoints = (i == 4) ? 9 : 3;
 336                 quadrants[i] = new Point[numPoints];
 337             }
 338         }
 339         // Upper-left
 340         quadrants[quadIndex] = new Point[] {
 341             new Point(leftX + xOffset,          clipY1 - 1 - yOffset),
 342             new Point(leftX + xOffset,          topY + yOffset),
 343             new Point(clipX1 - 1 - xOffset,     topY + yOffset),
 344         };
 345 
 346         quadIndex++;
 347         yOffset++;
 348         // Upper-middle
 349         quadrants[quadIndex] = new Point[] {
 350             new Point(clipX1 + 1 + xOffset,     topY + yOffset),
 351             new Point(centerX + xOffset,        topY + yOffset),
 352             new Point(clipX2 - 1 - xOffset,     topY + yOffset),
 353         };
 354 
 355         quadIndex++;
 356         ++yOffset;
 357         // Upper-right
 358         quadrants[quadIndex] = new Point[] {
 359             new Point(clipX2 + 1 + xOffset,     topY + yOffset),
 360             new Point(rightX - xOffset,         topY + yOffset),
 361             new Point(rightX - xOffset,         clipY1 - 1 - yOffset),
 362         };
 363 
 364         quadIndex++;
 365         yOffset = 0;
 366         ++xOffset;
 367         // Middle-left
 368         quadrants[quadIndex] = new Point[] {
 369             new Point(leftX + xOffset,          clipY1 + 1 + yOffset),
 370             new Point(leftX + xOffset,          centerY + yOffset),
 371             new Point(leftX + xOffset,          clipY2 - 1 - yOffset),
 372         };
 373 
 374         quadIndex++;
 375         ++yOffset;
 376         // Middle-middle
 377         quadrants[quadIndex] = new Point[] {
 378             new Point(clipX1 + 1 + xOffset,     clipY1 + 1 + yOffset),
 379             new Point(centerX + xOffset,        clipY1 + 1 + yOffset),
 380             new Point(clipX2 - 1 - xOffset,     clipY1 + 1 + yOffset),
 381             new Point(clipX1 + 1 + xOffset,     centerY + yOffset),
 382             new Point(centerX + xOffset,        centerY + yOffset),
 383             new Point(clipX2 - 1 - xOffset,     centerY + yOffset),
 384             new Point(clipX1 + 1 + xOffset,     clipY2 - 1 - yOffset),
 385             new Point(centerX + xOffset,        clipY2 - 1 - yOffset),
 386             new Point(clipX2 - 1 - xOffset,     clipY2 - 1 - yOffset),
 387         };
 388 
 389         quadIndex++;
 390         ++yOffset;
 391         // Middle-right
 392         quadrants[quadIndex] = new Point[] {
 393             new Point(rightX - xOffset,         clipY1 + 1 + yOffset),
 394             new Point(rightX - xOffset,         centerY + yOffset),
 395             new Point(rightX - xOffset,         clipY2 - 1 - yOffset),
 396         };
 397 
 398         quadIndex++;
 399         yOffset = 0;
 400         ++xOffset;
 401         // Lower-left
 402         quadrants[quadIndex] = new Point[] {
 403             new Point(leftX + xOffset,          clipY2 + 1 + yOffset),
 404             new Point(leftX + xOffset,          bottomY - yOffset),
 405             new Point(clipX1 - 1 - xOffset,     bottomY - yOffset),
 406         };
 407 
 408         quadIndex++;
 409         ++yOffset;
 410         // Lower-middle
 411         quadrants[quadIndex] = new Point[] {
 412             new Point(clipX1 + 1 + xOffset,     bottomY - yOffset),
 413             new Point(centerX + xOffset,        bottomY - yOffset),
 414             new Point(clipX2 - 1 - xOffset,     bottomY - yOffset),
 415         };
 416 
 417         quadIndex++;
 418         ++yOffset;
 419         // Lower-right
 420         quadrants[quadIndex] = new Point[] {
 421             new Point(clipX2 + 1 + xOffset,     bottomY - yOffset),
 422             new Point(rightX - xOffset,         bottomY - yOffset),
 423             new Point(rightX - xOffset,         clipY2 + 1 + yOffset),
 424         };
 425 
 426 
 427         if (testImage != null) {
 428             testImage.flush();
 429         }
 430         testW = getWidth();
 431         testH = getHeight();
 432         testImage = createVolatileImage(testW, testH);
 433         Graphics g = testImage.getGraphics();
 434         do {
 435             int valCode = testImage.validate(getGraphicsConfiguration());
 436             if (valCode == VolatileImage.IMAGE_INCOMPATIBLE) {
 437                 testImage.flush();
 438                 testImage = createVolatileImage(testW, testH);
 439                 g = testImage.getGraphics();
 440             }
 441             drawLineGrid(screenGraphics, g);
 442         } while (testImage.contentsLost());
 443         if (dynamic) {
 444             // Draw clip box if dynamic
 445             g.setClip(null);
 446             g.setColor(Color.black);
 447             g.drawRect(clipX1, clipY1, clipSize, clipSize);
 448             screenGraphics.drawImage(testImage, 0, 0, this);
 449         }
 450         runTestDone = true;
 451     }
 452 
 453     /**
 454      * When running -dynamic, resize the clip bounds and run the test
 455      * over and over
 456      */
 457     public void run() {
 458         while (true) {
 459             clipSize += clipBumpVal;
 460             if (clipSize > getWidth() || clipSize < 0) {
 461                 clipBumpVal = -clipBumpVal;
 462                 clipSize += clipBumpVal;
 463             }
 464             update(getGraphics());
 465             try {
 466                 Thread.sleep(50);
 467             } catch (Exception e) {}
 468         }
 469     }
 470 
 471     public static void main(String args[]) {
 472         for (int i = 0; i < args.length; ++i) {
 473             if (args[i].equals("-dynamic")) {
 474                 dynamic = true;
 475             } else if (args[i].equals("-rect")) {
 476                 rectTest = true;
 477             } else if (args[i].equals("-quick")) {
 478                 quickTest = true;
 479             } else if (args[i].equals("-keep")) {
 480                 keepRunning = true;
 481             } else {
 482                 // could be clipSize
 483                 try {
 484                     clipSize = Integer.parseInt(args[i]);
 485                 } catch (Exception e) {}
 486             }
 487         }
 488         f = new Frame();
 489         f.setSize(500, 500);
 490         LineClipTest test = new LineClipTest();
 491         f.add(test);
 492         if (dynamic) {
 493             Thread t = new Thread(test);
 494             t.start();
 495         }
 496         f.setVisible(true);
 497         while (!runTestDone) {
 498             // need to make sure jtreg doesn't exit before the
 499             // test is done...
 500             try {
 501                 Thread.sleep(50);
 502             } catch (Exception e) {}
 503         }
 504     }
 505 }