# HG changeset patch # User prr # Date 1380738127 25200 # Node ID be660188e0b450f06a1e587ccaf3c68ba2497b65 # Parent befaf108555190f93a38611339d1dbb1b56505a0 7179526: xrender : closed/sun/java2d/volatileImage/LineClipTest.java failed since jdk8b36 Reviewed-by: prr, jchen diff -r befaf1085551 -r be660188e0b4 jdk/src/solaris/classes/sun/java2d/xr/GrowableRectArray.java --- a/jdk/src/solaris/classes/sun/java2d/xr/GrowableRectArray.java Wed Oct 02 11:16:07 2013 -0700 +++ b/jdk/src/solaris/classes/sun/java2d/xr/GrowableRectArray.java Wed Oct 02 11:22:07 2013 -0700 @@ -38,6 +38,20 @@ super(RECT_SIZE, initialSize); } + public final void pushRectValues(int x, int y, int width, int height) { + int currSize = size; + size += RECT_SIZE; + + if (size >= array.length) { + growArray(); + } + + array[currSize] = x; + array[currSize + 1] = y; + array[currSize + 2] = width; + array[currSize + 3] = height; + } + public final void setX(int index, int x) { array[getCellIndex(index)] = x; } diff -r befaf1085551 -r be660188e0b4 jdk/src/solaris/classes/sun/java2d/xr/MaskTile.java --- a/jdk/src/solaris/classes/sun/java2d/xr/MaskTile.java Wed Oct 02 11:16:07 2013 -0700 +++ b/jdk/src/solaris/classes/sun/java2d/xr/MaskTile.java Wed Oct 02 11:22:07 2013 -0700 @@ -41,98 +41,6 @@ dirtyArea = new DirtyRegion(); } - public void addRect(int x, int y, int width, int height) { - int index = rects.getNextIndex(); - rects.setX(index, x); - rects.setY(index, y); - rects.setWidth(index, width); - rects.setHeight(index, height); - } - - public void addLine(int x1, int y1, int x2, int y2) { - /* - * EXA is not able to accalerate diagonal lines, we try to "guide" it a - * bit to avoid excessive migration See project documentation for an - * detailed explanation - */ - DirtyRegion region = new DirtyRegion(); - region.setDirtyLineRegion(x1, y1, x2, y2); - int xDiff = region.x2 - region.x; - int yDiff = region.y2 - region.y; - - if (xDiff == 0 || yDiff == 0) { - addRect(region.x, region.y, - region.x2 - region.x + 1, region.y2 - region.y + 1); - } else if (xDiff == 1 && yDiff == 1) { - addRect(x1, y1, 1, 1); - addRect(x2, y2, 1, 1); - } else { - lineToRects(x1, y1, x2, y2); - } - } - - private void lineToRects(int xstart, int ystart, int xend, int yend) { - int x, y, t, dx, dy, incx, incy, pdx, pdy, ddx, ddy, es, el, err; - - /* Entfernung in beiden Dimensionen berechnen */ - dx = xend - xstart; - dy = yend - ystart; - - /* Vorzeichen des Inkrements bestimmen */ - incx = dx > 0 ? 1 : (dx < 0) ? -1 : 0; - incy = dy > 0 ? 1 : (dy < 0) ? -1 : 0; - if (dx < 0) - dx = -dx; - if (dy < 0) - dy = -dy; - - /* feststellen, welche Entfernung groesser ist */ - if (dx > dy) { - /* x ist schnelle Richtung */ - pdx = incx; - pdy = 0; /* pd. ist Parallelschritt */ - ddx = incx; - ddy = incy; /* dd. ist Diagonalschritt */ - es = dy; - el = dx; /* Fehlerschritte schnell, langsam */ - } else { - /* y ist schnelle Richtung */ - pdx = 0; - pdy = incy; /* pd. ist Parallelschritt */ - ddx = incx; - ddy = incy; /* dd. ist Diagonalschritt */ - es = dx; - el = dy; /* Fehlerschritte schnell, langsam */ - } - - /* Initialisierungen vor Schleifenbeginn */ - x = xstart; - y = ystart; - err = el / 2; - addRect(x, y, 1, 1); - - /* Pixel berechnen */ - for (t = 0; t < el; ++t) /* t zaehlt die Pixel, el ist auch Anzahl */ - { - /* Aktualisierung Fehlerterm */ - err -= es; - if (err < 0) { - /* Fehlerterm wieder positiv (>=0) machen */ - err += el; - /* Schritt in langsame Richtung, Diagonalschritt */ - x += ddx; - y += ddy; - } else { - /* Schritt in schnelle Richtung, Parallelschritt */ - x += pdx; - y += pdy; - } - addRect(x, y, 1, 1); - // SetPixel(x,y); - // System.out.println(x+":"+y); - } - } - public void calculateDirtyAreas() { for (int i=0; i < rects.getSize(); i++) { diff -r befaf1085551 -r be660188e0b4 jdk/src/solaris/classes/sun/java2d/xr/MaskTileManager.java --- a/jdk/src/solaris/classes/sun/java2d/xr/MaskTileManager.java Wed Oct 02 11:16:07 2013 -0700 +++ b/jdk/src/solaris/classes/sun/java2d/xr/MaskTileManager.java Wed Oct 02 11:22:07 2013 -0700 @@ -54,10 +54,6 @@ int maskPixmap; int maskPicture; long maskGC; - int lineMaskPixmap; - int lineMaskPicture; - long drawLineGC; - long clearLineGC; public MaskTileManager(XRCompositeManager xrMgr, int parentXid) { tileList = new ArrayList(); @@ -71,34 +67,6 @@ 0, 0, MASK_SIZE, MASK_SIZE); maskGC = con.createGC(maskPixmap); con.setGCExposures(maskGC, false); - - lineMaskPixmap = con.createPixmap(parentXid, 8, MASK_SIZE, MASK_SIZE); - lineMaskPicture = - con.createPicture(lineMaskPixmap, XRUtils.PictStandardA8); - con.renderRectangle(lineMaskPicture, XRUtils.PictOpClear, - new XRColor(Color.black), 0, 0, MASK_SIZE, MASK_SIZE); - - drawLineGC = con.createGC(lineMaskPixmap); - con.setGCExposures(drawLineGC, false); - con.setGCForeground(drawLineGC, 255); - - clearLineGC = con.createGC(lineMaskPixmap); - con.setGCExposures(clearLineGC, false); - con.setGCForeground(clearLineGC, 0); - } - - /** - * Adds a rectangle to the mask. - */ - public void addRect(int x, int y, int width, int height) { - mainTile.addRect(x, y, width, height); - } - - /** - * Adds a line to the mask. - */ - public void addLine(int x1, int y1, int x2, int y2) { - mainTile.addLine(x1, y1, x2, y2); } /** @@ -324,4 +292,11 @@ rects.setY(index, 0); } } + + /** + * @return MainTile to which rectangles are added before composition. + */ + public MaskTile getMainTile() { + return mainTile; + } } diff -r befaf1085551 -r be660188e0b4 jdk/src/solaris/classes/sun/java2d/xr/XRDrawLine.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/solaris/classes/sun/java2d/xr/XRDrawLine.java Wed Oct 02 11:22:07 2013 -0700 @@ -0,0 +1,425 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + + + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * Bresenham line-drawing implementation decomposing line segments + * into a series of rectangles. + * This is required, because xrender doesn't support line primitives directly. + * The code here is an almost 1:1 port of the existing C-source contained in + * sun/java2d/loop/DrawLine.c and sun/java2d/loop/LoopMacros.h + */ +package sun.java2d.xr; + +public class XRDrawLine { + static final int BIG_MAX = ((1 << 29) - 1); + static final int BIG_MIN = (-(1 << 29)); + + static final int OUTCODE_TOP = 1; + static final int OUTCODE_BOTTOM = 2; + static final int OUTCODE_LEFT = 4; + static final int OUTCODE_RIGHT = 8; + + int x1, y1, x2, y2; + int ucX1, ucY1, ucX2, ucY2; + + DirtyRegion region = new DirtyRegion(); + + protected void rasterizeLine(GrowableRectArray rectBuffer, int _x1, + int _y1, int _x2, int _y2, int cxmin, int cymin, int cxmax, + int cymax, boolean clip, boolean overflowCheck) { + float diagF; + int error; + int steps; + int errminor, errmajor; + boolean xmajor; + int dx, dy, ax, ay; + + initCoordinates(_x1, _y1, _x2, _y2, overflowCheck); + + dx = x2 - x1; + dy = y2 - y1; + ax = Math.abs(dx); + ay = Math.abs(dy); + xmajor = (ax >= ay); + diagF = ((float) ax) / ay; + + if (clip + && !clipCoordinates(cxmin, cymin, cxmax, cymax, xmajor, dx, dy, + ax, ay)) { + // whole line was clipped away + return; + } + + region.setDirtyLineRegion(x1, y1, x2, y2); + int xDiff = region.x2 - region.x; + int yDiff = region.y2 - region.y; + + if (xDiff == 0 || yDiff == 0) { + // horizontal / diagonal lines can be represented by a single + // rectangle + rectBuffer.pushRectValues(region.x, region.y, region.x2 - region.x + + 1, region.y2 - region.y + 1); + return; + } + + // Setup bresenham + if (xmajor) { + errmajor = ay * 2; + errminor = ax * 2; + ax = -ax; /* For clipping adjustment below */ + steps = x2 - x1; + } else { + errmajor = ax * 2; + errminor = ay * 2; + ay = -ay; /* For clipping adjustment below */ + steps = y2 - y1; + } + + if ((steps = (Math.abs(steps) + 1)) == 0) { + return; + } + + error = -(errminor / 2); + + if (y1 != ucY1) { + int ysteps = y1 - ucY1; + if (ysteps < 0) { + ysteps = -ysteps; + } + error += ysteps * ax * 2; + } + + if (x1 != ucX1) { + int xsteps = x1 - ucX1; + if (xsteps < 0) { + xsteps = -xsteps; + } + error += xsteps * ay * 2; + } + error += errmajor; + errminor -= errmajor; + + int xStep = (dx > 0 ? 1 : -1); + int yStep = (dy > 0 ? 1 : -1); + int orthogonalXStep = xmajor ? xStep : 0; + int orthogonalYStep = !xmajor ? yStep : 0; + + /* + * For lines which proceed in one direction faster, we try to generate + * rectangles instead of points. Otherwise we try to avoid the extra + * work... + */ + if (diagF <= 0.9 || diagF >= 1.1) { + lineToRects(rectBuffer, steps, error, errmajor, errminor, xStep, + yStep, orthogonalXStep, orthogonalYStep); + } else { + lineToPoints(rectBuffer, steps, error, errmajor, errminor, xStep, + yStep, orthogonalXStep, orthogonalYStep); + } + } + + private void lineToPoints(GrowableRectArray rectBuffer, int steps, + int error, int errmajor, int errminor, int xStep, int yStep, + int orthogonalXStep, int orthogonalYStep) { + int x = x1, y = y1; + + do { + rectBuffer.pushRectValues(x, y, 1, 1); + + // "Traditional" Bresenham line drawing + if (error < 0) { + error += errmajor; + x += orthogonalXStep; + y += orthogonalYStep; + } else { + error -= errminor; + x += xStep; + y += yStep; + } + } while (--steps > 0); + } + + private void lineToRects(GrowableRectArray rectBuffer, int steps, + int error, int errmajor, int errminor, int xStep, int yStep, + int orthogonalXStep, int orthogonalYStep) { + int x = x1, y = y1; + int rectX = Integer.MIN_VALUE, rectY = 0; + int rectW = 0, rectH = 0; + + do { + // Combine the resulting rectangles + // for steps performed in a single direction. + if (y == rectY) { + if (x == (rectX + rectW)) { + rectW++; + } else if (x == (rectX - 1)) { + rectX--; + rectW++; + } + } else if (x == rectX) { + if (y == (rectY + rectH)) { + rectH++; + } else if (y == (rectY - 1)) { + rectY--; + rectH++; + } + } else { + // Diagonal step: add the previous rectangle to the list, + // iff it was "real" (= not initialized before the first + // iteration) + if (rectX != Integer.MIN_VALUE) { + rectBuffer.pushRectValues(rectX, rectY, rectW, rectH); + } + rectX = x; + rectY = y; + rectW = rectH = 1; + } + + // "Traditional" Bresenham line drawing + if (error < 0) { + error += errmajor; + x += orthogonalXStep; + y += orthogonalYStep; + } else { + error -= errminor; + x += xStep; + y += yStep; + } + } while (--steps > 0); + + // Add last rectangle which isn't handled by the combination-code + // anymore + rectBuffer.pushRectValues(rectX, rectY, rectW, rectH); + } + + private boolean clipCoordinates(int cxmin, int cymin, int cxmax, int cymax, + boolean xmajor, int dx, int dy, int ax, int ay) { + int outcode1, outcode2; + + outcode1 = outcode(x1, y1, cxmin, cymin, cxmax, cymax); + outcode2 = outcode(x2, y2, cxmin, cymin, cxmax, cymax); + + while ((outcode1 | outcode2) != 0) { + int xsteps = 0, ysteps = 0; + + if ((outcode1 & outcode2) != 0) { + return false; + } + + if (outcode1 != 0) { + if ((outcode1 & (OUTCODE_TOP | OUTCODE_BOTTOM)) != 0) { + if ((outcode1 & OUTCODE_TOP) != 0) { + y1 = cymin; + } else { + y1 = cymax; + } + ysteps = y1 - ucY1; + if (ysteps < 0) { + ysteps = -ysteps; + } + xsteps = 2 * ysteps * ax + ay; + if (xmajor) { + xsteps += ay - ax - 1; + } + xsteps = xsteps / (2 * ay); + if (dx < 0) { + xsteps = -xsteps; + } + x1 = ucX1 + (int) xsteps; + } else if ((outcode1 & (OUTCODE_LEFT | OUTCODE_RIGHT)) != 0) { + if ((outcode1 & OUTCODE_LEFT) != 0) { + x1 = cxmin; + } else { + x1 = cxmax; + } + xsteps = x1 - ucX1; + if (xsteps < 0) { + xsteps = -xsteps; + } + ysteps = 2 * xsteps * ay + ax; + if (!xmajor) { + ysteps += ax - ay - 1; + } + ysteps = ysteps / (2 * ax); + if (dy < 0) { + ysteps = -ysteps; + } + y1 = ucY1 + (int) ysteps; + } + outcode1 = outcode(x1, y1, cxmin, cymin, cxmax, cymax); + } else { + if ((outcode2 & (OUTCODE_TOP | OUTCODE_BOTTOM)) != 0) { + if ((outcode2 & OUTCODE_TOP) != 0) { + y2 = cymin; + } else { + y2 = cymax; + } + ysteps = y2 - ucY2; + if (ysteps < 0) { + ysteps = -ysteps; + } + xsteps = 2 * ysteps * ax + ay; + if (xmajor) { + xsteps += ay - ax; + } else { + xsteps -= 1; + } + xsteps = xsteps / (2 * ay); + if (dx > 0) { + xsteps = -xsteps; + } + x2 = ucX2 + (int) xsteps; + } else if ((outcode2 & (OUTCODE_LEFT | OUTCODE_RIGHT)) != 0) { + if ((outcode2 & OUTCODE_LEFT) != 0) { + x2 = cxmin; + } else { + x2 = cxmax; + } + xsteps = x2 - ucX2; + if (xsteps < 0) { + xsteps = -xsteps; + } + ysteps = 2 * xsteps * ay + ax; + if (xmajor) { + ysteps -= 1; + } else { + ysteps += ax - ay; + } + ysteps = ysteps / (2 * ax); + if (dy > 0) { + ysteps = -ysteps; + } + y2 = ucY2 + (int) ysteps; + } + outcode2 = outcode(x2, y2, cxmin, cymin, cxmax, cymax); + } + } + + return true; + } + + private void initCoordinates(int x1, int y1, int x2, int y2, + boolean checkOverflow) { + /* + * Part of calculating the Bresenham parameters for line stepping + * involves being able to store numbers that are twice the magnitude of + * the biggest absolute difference in coordinates. Since we want the + * stepping parameters to be stored in jints, we then need to avoid any + * absolute differences more than 30 bits. Thus, we need to preprocess + * the coordinates to reduce their range to 30 bits regardless of + * clipping. We need to cut their range back before we do the clipping + * because the Bresenham stepping values need to be calculated based on + * the "unclipped" coordinates. + * + * Thus, first we perform a "pre-clipping" stage to bring the + * coordinates within the 30-bit range and then we proceed to the + * regular clipping procedure, pretending that these were the original + * coordinates all along. Since this operation occurs based on a + * constant "pre-clip" rectangle of +/- 30 bits without any + * consideration for the final clip, the rounding errors that occur here + * will depend only on the line coordinates and be invariant with + * respect to the particular device/user clip rectangles in effect at + * the time. Thus, rendering a given large-range line will be consistent + * under a variety of clipping conditions. + */ + if (checkOverflow + && (OverflowsBig(x1) || OverflowsBig(y1) || OverflowsBig(x2) || OverflowsBig(y2))) { + /* + * Use doubles to get us into range for "Big" arithmetic. + * + * The math of adjusting an endpoint for clipping can involve an + * intermediate result with twice the number of bits as the original + * coordinate range. Since we want to maintain as much as 30 bits of + * precision in the resulting coordinates, we will get roundoff here + * even using IEEE double-precision arithmetic which cannot carry 60 + * bits of mantissa. Since the rounding errors will be consistent + * for a given set of input coordinates the potential roundoff error + * should not affect the consistency of our rendering. + */ + double x1d = x1; + double y1d = y1; + double x2d = x2; + double y2d = y2; + double dxd = x2d - x1d; + double dyd = y2d - y1d; + + if (x1 < BIG_MIN) { + y1d = y1 + (BIG_MIN - x1) * dyd / dxd; + x1d = BIG_MIN; + } else if (x1 > BIG_MAX) { + y1d = y1 - (x1 - BIG_MAX) * dyd / dxd; + x1d = BIG_MAX; + } + /* Use Y1d instead of _y1 for testing now as we may have modified it */ + if (y1d < BIG_MIN) { + x1d = x1 + (BIG_MIN - y1) * dxd / dyd; + y1d = BIG_MIN; + } else if (y1d > BIG_MAX) { + x1d = x1 - (y1 - BIG_MAX) * dxd / dyd; + y1d = BIG_MAX; + } + if (x2 < BIG_MIN) { + y2d = y2 + (BIG_MIN - x2) * dyd / dxd; + x2d = BIG_MIN; + } else if (x2 > BIG_MAX) { + y2d = y2 - (x2 - BIG_MAX) * dyd / dxd; + x2d = BIG_MAX; + } + /* Use Y2d instead of _y2 for testing now as we may have modified it */ + if (y2d < BIG_MIN) { + x2d = x2 + (BIG_MIN - y2) * dxd / dyd; + y2d = BIG_MIN; + } else if (y2d > BIG_MAX) { + x2d = x2 - (y2 - BIG_MAX) * dxd / dyd; + y2d = BIG_MAX; + } + + x1 = (int) x1d; + y1 = (int) y1d; + x2 = (int) x2d; + y2 = (int) y2d; + } + + this.x1 = ucX1 = x1; + this.y1 = ucY1 = y1; + this.x2 = ucX2 = x2; + this.y2 = ucY2 = y2; + } + + private boolean OverflowsBig(int v) { + return ((v) != (((v) << 2) >> 2)); + } + + private int out(int v, int vmin, int vmax, int cmin, int cmax) { + return ((v < vmin) ? cmin : ((v > vmax) ? cmax : 0)); + } + + private int outcode(int x, int y, int xmin, int ymin, int xmax, int ymax) { + return out(y, ymin, ymax, OUTCODE_TOP, OUTCODE_BOTTOM) + | out(x, xmin, xmax, OUTCODE_LEFT, OUTCODE_RIGHT); + } +} diff -r befaf1085551 -r be660188e0b4 jdk/src/solaris/classes/sun/java2d/xr/XRRenderer.java --- a/jdk/src/solaris/classes/sun/java2d/xr/XRRenderer.java Wed Oct 02 11:16:07 2013 -0700 +++ b/jdk/src/solaris/classes/sun/java2d/xr/XRRenderer.java Wed Oct 02 11:22:07 2013 -0700 @@ -53,10 +53,15 @@ public class XRRenderer implements PixelDrawPipe, PixelFillPipe, ShapeDrawPipe { XRDrawHandler drawHandler; MaskTileManager tileManager; + XRDrawLine lineGen; + GrowableRectArray rectBuffer; public XRRenderer(MaskTileManager tileManager) { this.tileManager = tileManager; + this.rectBuffer = tileManager.getMainTile().getRects(); + this.drawHandler = new XRDrawHandler(); + this.lineGen = new XRDrawLine(); } /** @@ -77,19 +82,15 @@ int transX2 = Region.clipAdd(x2, sg2d.transX); int transY2 = Region.clipAdd(y2, sg2d.transY); - // Non clipped fast path - if (compClip.contains(transX1, transY1) - && compClip.contains(transX2, transY2)) { - SunToolkit.awtLock(); - try { - validateSurface(sg2d); - tileManager.addLine(transX1, transY1, transX2, transY2); - tileManager.fillMask((XRSurfaceData) sg2d.surfaceData); - } finally { - SunToolkit.awtUnlock(); - } - } else { - draw(sg2d, new Line2D.Float(x1, y1, x2, y2)); + SunToolkit.awtLock(); + try { + validateSurface(sg2d); + lineGen.rasterizeLine(rectBuffer, transX1, transY1, + transX2, transY2, compClip.getLoX(), compClip.getLoY(), + compClip.getHiX(), compClip.getHiY(), true, true); + tileManager.fillMask((XRSurfaceData) sg2d.surfaceData); + } finally { + SunToolkit.awtUnlock(); } } @@ -148,7 +149,7 @@ SunToolkit.awtLock(); try { validateSurface(sg2d); - tileManager.addRect(x, y, width, height); + rectBuffer.pushRectValues(x, y, width, height); tileManager.fillMask((XRSurfaceData) sg2d.surfaceData); } finally { SunToolkit.awtUnlock(); @@ -199,11 +200,13 @@ } private class XRDrawHandler extends ProcessPath.DrawHandler { + DirtyRegion region; XRDrawHandler() { // these are bogus values; the caller will use validate() // to ensure that they are set properly prior to each usage super(0, 0, 0, 0); + this.region = new DirtyRegion(); } /** @@ -218,15 +221,32 @@ } public void drawLine(int x1, int y1, int x2, int y2) { - tileManager.addLine(x1, y1, x2, y2); + region.setDirtyLineRegion(x1, y1, x2, y2); + int xDiff = region.x2 - region.x; + int yDiff = region.y2 - region.y; + + if (xDiff == 0 || yDiff == 0) { + // horizontal / diagonal lines can be represented by a single + // rectangle + rectBuffer.pushRectValues(region.x, region.y, region.x2 - region.x + + 1, region.y2 - region.y + 1); + } else if (xDiff == 1 && yDiff == 1) { + // fast path for pattern commonly generated by + // ProcessPath.DrawHandler + rectBuffer.pushRectValues(x1, y1, 1, 1); + rectBuffer.pushRectValues(x2, y2, 1, 1); + } else { + lineGen.rasterizeLine(rectBuffer, x1, y1, x2, y2, 0, 0, + 0, 0, false, false); + } } public void drawPixel(int x, int y) { - tileManager.addRect(x, y, 1, 1); + rectBuffer.pushRectValues(x, y, 1, 1); } public void drawScanline(int x1, int x2, int y) { - tileManager.addRect(x1, y, x2 - x1 + 1, 1); + rectBuffer.pushRectValues(x1, y, x2 - x1 + 1, 1); } } @@ -263,7 +283,7 @@ validateSurface(sg2d); int[] spanBox = new int[4]; while (si.nextSpan(spanBox)) { - tileManager.addRect(spanBox[0] + transx, + rectBuffer.pushRectValues(spanBox[0] + transx, spanBox[1] + transy, spanBox[2] - spanBox[0], spanBox[3] - spanBox[1]); diff -r befaf1085551 -r be660188e0b4 jdk/test/java/awt/Graphics/LineClipTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/java/awt/Graphics/LineClipTest.java Wed Oct 02 11:22:07 2013 -0700 @@ -0,0 +1,504 @@ +/* + * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 4780022 4862193 7179526 + * @summary Tests that clipped lines are drawn over the same pixels + * as unclipped lines (within the clip bounds) + * @run main/timeout=600/othervm -Dsun.java2d.ddforcevram=true LineClipTest + * @run main/timeout=600/othervm LineClipTest + */ + + +/** + * This app tests whether we are drawing clipped lines the same + * as unclipped lines. The problem occurred when we started + * clipping d3d lines using simple integer clipping, which did not + * account for sub-pixel precision and ended up drawing very different + * pixels than the same line drawn unclipped. A supposed fix + * to that problem used floating-point clipping instead, but there + * was some problem with very limited precision inside of d3d + * (presumably in hardware) that caused some variation in pixels. + * We decided that whatever the fix was, we needed a serious + * line check test to make sure that all kinds of different + * lines would be drawn exactly the same inside the clip area, + * regardless of whether clipping was enabled. This test should + * check all kinds of different cases, such as lines that fall + * completely outside, completely inside, start outside and + * end inside, etc., and lines should end and originate in + * all quadrants of the space divided up by the clip box. + * + * The test works as follows: + * We create nine quadrants using the spaces bisected by the + * edges of the clip bounds (note that only one of these + * quadrants is actually visible when clipping is enabled). + * We create several points in each of these quadrants + * (three in each of the invisible quadrants, nine in the + * center/visible quadrant). Our resulting grid looks like + * this: + * + * x x|x x x|x x + * | | + * | | + * | | + * | | + * | | + * x | | x + * ----------------------------------- + * x |x x x| x + * | | + * | | + * x |x x x| x + * | | + * | | + * x |x x x| x + * ----------------------------------- + * x | | x + * | | + * | | + * | | + * | | + * | | + * x x|x x x|x x + * + * The test then draws lines from every point to every other + * point. First, we draw unclipped lines in blue and + * then we draw clipped lines in red. + * At certain times (after every point during the default + * test, after every quadrant of lines if you run with the -quick + * option), we check for errors and draw the current image + * to the screen. Error checking consists of copying the + * VolatileImage to a BufferedImage (because we need access + * to the pixels directly) and checking every pixel in the + * image. The check is simple: everything outside the + * clip bounds should be blue (or the background color) and + * everything inside the clip bounds should be red (or the + * background color). So any blue pixel inside or red + * pixel outside means that there was a drawing error and + * the test fails. + * There are 4 modes that the test can run in (dynamic mode is + * exclusive to the other modes, but the other modes are combinable): + * + * (default): the clip is set + * to a default size (100x100) and the test is run. + * + * -quick: The error + * check is run only after every quadrant of lines is + * drawn. This speeds up the test considerably with + * some less accuracy in error checking (because pixels + * from some lines may overdrawn pixels from other lines + * before we have verified the correctness of those + * pixels). + * + * -dynamic: There is no error checking, but this version + * of the test automatically resizes the clip bounds and + * reruns the test over and over. Nothing besides the + * visual check verifies that the test is running correctly. + * + * -rect: Instead of drawing lines, the test draws rectangles + * to/from all points in all quadrants. This tests similar + * clipping functionality for drawRect(). + * + * n (where "n" is a number): sets the clip size to the + * given value. Just like the default test except that + * the clip size is as specified. + * + * Note: this test must be run with the -Dsun.java2d.ddforcevram=true + * option to force the test image to stay in VRAM. We currently + * punt VRAM images to system memory when we detect lots of + * reads. Since we read the whole buffer on every error check + * to copy it to the BufferedImage), this causes us to punt the + * buffer. A system memory surface will have no d3d capabilities, + * thus we are not testing the d3d line quality when this happens. + * By using the ddforcevram flag, we make sure the buffer + * stays put in VRAM and d3d is used to draw the lines. + */ + +import javax.swing.*; +import java.awt.*; +import java.awt.image.*; + + +public class LineClipTest extends Component implements Runnable { + + int clipBumpVal = 5; + static int clipSize = 100; + int clipX1; + int clipY1; + static final int NUM_QUADS = 9; + Point quadrants[][] = new Point[NUM_QUADS][]; + static boolean dynamic = false; + BufferedImage imageChecker = null; + Color unclippedColor = Color.blue; + Color clippedColor = Color.red; + int testW = -1, testH = -1; + VolatileImage testImage = null; + static boolean keepRunning = false; + static boolean quickTest = false; + static boolean rectTest = false; + static boolean runTestDone = false; + static Frame f = null; + + /** + * Check for errors in the grid. This error check consists of + * copying the buffer into a BufferedImage and reading all pixels + * in that image. No pixel outside the clip bounds should be + * of the color clippedColor and no pixel inside should be + * of the color unclippedColor. Any wrong color returns an error. + */ + boolean gridError(Graphics g) { + boolean error = false; + if (imageChecker == null || (imageChecker.getWidth() != testW) || + (imageChecker.getHeight() != testH)) + { + // Recreate BufferedImage as necessary + GraphicsConfiguration gc = getGraphicsConfiguration(); + ColorModel cm = gc.getColorModel(); + WritableRaster wr = + cm.createCompatibleWritableRaster(getWidth(), getHeight()); + imageChecker = + new BufferedImage(cm, wr, + cm.isAlphaPremultiplied(), null); + } + // Copy buffer to BufferedImage + Graphics gChecker = imageChecker.getGraphics(); + gChecker.drawImage(testImage, 0, 0, this); + + // Set up pixel colors to check against + int clippedPixelColor = clippedColor.getRGB(); + int unclippedPixelColor = unclippedColor.getRGB(); + int wrongPixelColor = clippedPixelColor; + boolean insideClip = false; + for (int row = 0; row < getHeight(); ++row) { + for (int col = 0; col < getWidth(); ++col) { + if (row >= clipY1 && row < (clipY1 + clipSize) && + col >= clipX1 && col < (clipX1 + clipSize)) + { + // Inside clip bounds - should not see unclipped color + wrongPixelColor = unclippedPixelColor; + } else { + // Outside clip - should not see clipped color + wrongPixelColor = clippedPixelColor; + } + int pixel = imageChecker.getRGB(col, row); + if (pixel == wrongPixelColor) { + System.out.println("FAILED: pixel = " + + Integer.toHexString(pixel) + + " at (x, y) = " + col + ", " + row); + // Draw magenta rectangle around problem pixel in buffer + // for visual feedback to user + g.setColor(Color.magenta); + g.drawRect(col - 1, row - 1, 2, 2); + error = true; + } + } + } + return error; + } + + /** + * Draw all test lines and check for errors (unless running + * with -dynamic option) + */ + void drawLineGrid(Graphics screenGraphics, Graphics g) { + // Fill buffer with background color + g.setColor(Color.white); + g.fillRect(0, 0, getWidth(), getHeight()); + + // Now, iterate through all quadrants + for (int srcQuad = 0; srcQuad < NUM_QUADS; ++srcQuad) { + // Draw lines to all other quadrants + for (int dstQuad = 0; dstQuad < NUM_QUADS; ++dstQuad) { + for (int srcPoint = 0; + srcPoint < quadrants[srcQuad].length; + ++srcPoint) + { + // For every point in the source quadrant + int sx = quadrants[srcQuad][srcPoint].x; + int sy = quadrants[srcQuad][srcPoint].y; + for (int dstPoint = 0; + dstPoint < quadrants[dstQuad].length; + ++dstPoint) + { + int dx = quadrants[dstQuad][dstPoint].x; + int dy = quadrants[dstQuad][dstPoint].y; + if (!rectTest) { + // Draw unclipped/clipped lines to every + // point in the dst quadrant + g.setColor(unclippedColor); + g.drawLine(sx, sy, dx, dy); + g.setClip(clipX1, clipY1, clipSize, clipSize); + g.setColor(clippedColor); + g.drawLine(sx,sy, dx, dy); + } else { + // Draw unclipped/clipped rectangles to every + // point in the dst quadrant + g.setColor(unclippedColor); + int w = dx - sx; + int h = dy - sy; + g.drawRect(sx, sy, w, h); + g.setClip(clipX1, clipY1, clipSize, clipSize); + g.setColor(clippedColor); + g.drawRect(sx, sy, w, h); + } + g.setClip(null); + } + if (!dynamic) { + // Draw screen update for visual feedback + screenGraphics.drawImage(testImage, 0, 0, this); + // On default test, check for errors after every + // src point + if (!quickTest && gridError(g)) { + throw new java.lang.RuntimeException("Failed"); + } + } + } + } + if (!dynamic && quickTest && gridError(g)) { + // On quick test, check for errors only after every + // src quadrant + throw new java.lang.RuntimeException("Failed"); + //return; + } + } + if (!dynamic) { + System.out.println("PASSED"); + if (!keepRunning) { + f.dispose(); + } + } + } + + /** + * If we have not yet run the test, or if the window size has + * changed, or if we are running the test in -dynamic mode, + * run the test. Then draw the test buffer to the screen + */ + public void paint(Graphics g) { + if (dynamic || testImage == null || + getWidth() != testW || getHeight() != testH) + { + runTest(g); + } + if (testImage != null) { + g.drawImage(testImage, 0, 0, this); + } + } + + /* + * Create the quadrant of points and run the test to draw all the lines + */ + public void runTest(Graphics screenGraphics) { + if (getWidth() == 0 || getHeight() == 0) { + // May get here before window is really ready + return; + } + clipX1 = (getWidth() - clipSize) / 2; + clipY1 = (getHeight() - clipSize) / 2; + int clipX2 = clipX1 + clipSize; + int clipY2 = clipY1 + clipSize; + int centerX = getWidth()/2; + int centerY = getHeight()/2; + int leftX = 0; + int topY = 0; + int rightX = getWidth() - 1; + int bottomY = getHeight() - 1; + int quadIndex = 0; + // Offsets are used to force diagonal (versus hor/vert) lines + int xOffset = 0; + int yOffset = 0; + + if (quadrants[0] == null) { + for (int i = 0; i < 9; ++i) { + int numPoints = (i == 4) ? 9 : 3; + quadrants[i] = new Point[numPoints]; + } + } + // Upper-left + quadrants[quadIndex] = new Point[] { + new Point(leftX + xOffset, clipY1 - 1 - yOffset), + new Point(leftX + xOffset, topY + yOffset), + new Point(clipX1 - 1 - xOffset, topY + yOffset), + }; + + quadIndex++; + yOffset++; + // Upper-middle + quadrants[quadIndex] = new Point[] { + new Point(clipX1 + 1 + xOffset, topY + yOffset), + new Point(centerX + xOffset, topY + yOffset), + new Point(clipX2 - 1 - xOffset, topY + yOffset), + }; + + quadIndex++; + ++yOffset; + // Upper-right + quadrants[quadIndex] = new Point[] { + new Point(clipX2 + 1 + xOffset, topY + yOffset), + new Point(rightX - xOffset, topY + yOffset), + new Point(rightX - xOffset, clipY1 - 1 - yOffset), + }; + + quadIndex++; + yOffset = 0; + ++xOffset; + // Middle-left + quadrants[quadIndex] = new Point[] { + new Point(leftX + xOffset, clipY1 + 1 + yOffset), + new Point(leftX + xOffset, centerY + yOffset), + new Point(leftX + xOffset, clipY2 - 1 - yOffset), + }; + + quadIndex++; + ++yOffset; + // Middle-middle + quadrants[quadIndex] = new Point[] { + new Point(clipX1 + 1 + xOffset, clipY1 + 1 + yOffset), + new Point(centerX + xOffset, clipY1 + 1 + yOffset), + new Point(clipX2 - 1 - xOffset, clipY1 + 1 + yOffset), + new Point(clipX1 + 1 + xOffset, centerY + yOffset), + new Point(centerX + xOffset, centerY + yOffset), + new Point(clipX2 - 1 - xOffset, centerY + yOffset), + new Point(clipX1 + 1 + xOffset, clipY2 - 1 - yOffset), + new Point(centerX + xOffset, clipY2 - 1 - yOffset), + new Point(clipX2 - 1 - xOffset, clipY2 - 1 - yOffset), + }; + + quadIndex++; + ++yOffset; + // Middle-right + quadrants[quadIndex] = new Point[] { + new Point(rightX - xOffset, clipY1 + 1 + yOffset), + new Point(rightX - xOffset, centerY + yOffset), + new Point(rightX - xOffset, clipY2 - 1 - yOffset), + }; + + quadIndex++; + yOffset = 0; + ++xOffset; + // Lower-left + quadrants[quadIndex] = new Point[] { + new Point(leftX + xOffset, clipY2 + 1 + yOffset), + new Point(leftX + xOffset, bottomY - yOffset), + new Point(clipX1 - 1 - xOffset, bottomY - yOffset), + }; + + quadIndex++; + ++yOffset; + // Lower-middle + quadrants[quadIndex] = new Point[] { + new Point(clipX1 + 1 + xOffset, bottomY - yOffset), + new Point(centerX + xOffset, bottomY - yOffset), + new Point(clipX2 - 1 - xOffset, bottomY - yOffset), + }; + + quadIndex++; + ++yOffset; + // Lower-right + quadrants[quadIndex] = new Point[] { + new Point(clipX2 + 1 + xOffset, bottomY - yOffset), + new Point(rightX - xOffset, bottomY - yOffset), + new Point(rightX - xOffset, clipY2 + 1 + yOffset), + }; + + + if (testImage != null) { + testImage.flush(); + } + testW = getWidth(); + testH = getHeight(); + testImage = createVolatileImage(testW, testH); + Graphics g = testImage.getGraphics(); + do { + int valCode = testImage.validate(getGraphicsConfiguration()); + if (valCode == VolatileImage.IMAGE_INCOMPATIBLE) { + testImage.flush(); + testImage = createVolatileImage(testW, testH); + g = testImage.getGraphics(); + } + drawLineGrid(screenGraphics, g); + } while (testImage.contentsLost()); + if (dynamic) { + // Draw clip box if dynamic + g.setClip(null); + g.setColor(Color.black); + g.drawRect(clipX1, clipY1, clipSize, clipSize); + screenGraphics.drawImage(testImage, 0, 0, this); + } + runTestDone = true; + } + + /** + * When running -dynamic, resize the clip bounds and run the test + * over and over + */ + public void run() { + while (true) { + clipSize += clipBumpVal; + if (clipSize > getWidth() || clipSize < 0) { + clipBumpVal = -clipBumpVal; + clipSize += clipBumpVal; + } + update(getGraphics()); + try { + Thread.sleep(50); + } catch (Exception e) {} + } + } + + public static void main(String args[]) { + for (int i = 0; i < args.length; ++i) { + if (args[i].equals("-dynamic")) { + dynamic = true; + } else if (args[i].equals("-rect")) { + rectTest = true; + } else if (args[i].equals("-quick")) { + quickTest = true; + } else if (args[i].equals("-keep")) { + keepRunning = true; + } else { + // could be clipSize + try { + clipSize = Integer.parseInt(args[i]); + } catch (Exception e) {} + } + } + f = new Frame(); + f.setSize(500, 500); + LineClipTest test = new LineClipTest(); + f.add(test); + if (dynamic) { + Thread t = new Thread(test); + t.start(); + } + f.setVisible(true); + while (!runTestDone) { + // need to make sure jtreg doesn't exit before the + // test is done... + try { + Thread.sleep(50); + } catch (Exception e) {} + } + } +}