LineClipTest.java revision 9330:8b1f1c2a400f
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
138import javax.swing.*;
139import java.awt.*;
140import java.awt.image.*;
141
142
143public 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}
505