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