--- old/src/java.desktop/share/classes/sun/java2d/marlin/Curve.java 2017-08-28 16:48:24.941663252 +0200 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/Curve.java 2017-08-28 16:48:24.797663254 +0200 @@ -56,12 +56,16 @@ float x3, float y3, float x4, float y4) { - ax = 3.0f * (x2 - x3) + x4 - x1; - ay = 3.0f * (y2 - y3) + y4 - y1; - bx = 3.0f * (x1 - 2.0f * x2 + x3); - by = 3.0f * (y1 - 2.0f * y2 + y3); - cx = 3.0f * (x2 - x1); - cy = 3.0f * (y2 - y1); + final float dx32 = 3.0f * (x3 - x2); + final float dy32 = 3.0f * (y3 - y2); + final float dx21 = 3.0f * (x2 - x1); + final float dy21 = 3.0f * (y2 - y1); + ax = (x4 - x1) - dx32; + ay = (y4 - y1) - dy32; + bx = (dx32 - dx21); + by = (dy32 - dy21); + cx = dx21; + cy = dy21; dx = x1; dy = y1; dax = 3.0f * ax; day = 3.0f * ay; @@ -72,11 +76,13 @@ float x2, float y2, float x3, float y3) { + final float dx21 = (x2 - x1); + final float dy21 = (y2 - y1); ax = 0.0f; ay = 0.0f; - bx = x1 - 2.0f * x2 + x3; - by = y1 - 2.0f * y2 + y3; - cx = 2.0f * (x2 - x1); - cy = 2.0f * (y2 - y1); + bx = (x3 - x2) - dx21; + by = (y3 - y2) - dy21; + cx = 2.0f * dx21; + cy = 2.0f * dy21; dx = x1; dy = y1; dax = 0.0f; day = 0.0f; --- old/src/java.desktop/share/classes/sun/java2d/marlin/DCurve.java 2017-08-28 16:48:25.365663246 +0200 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DCurve.java 2017-08-28 16:48:25.205663248 +0200 @@ -56,12 +56,16 @@ double x3, double y3, double x4, double y4) { - ax = 3.0d * (x2 - x3) + x4 - x1; - ay = 3.0d * (y2 - y3) + y4 - y1; - bx = 3.0d * (x1 - 2.0d * x2 + x3); - by = 3.0d * (y1 - 2.0d * y2 + y3); - cx = 3.0d * (x2 - x1); - cy = 3.0d * (y2 - y1); + final double dx32 = 3.0d * (x3 - x2); + final double dy32 = 3.0d * (y3 - y2); + final double dx21 = 3.0d * (x2 - x1); + final double dy21 = 3.0d * (y2 - y1); + ax = (x4 - x1) - dx32; + ay = (y4 - y1) - dy32; + bx = (dx32 - dx21); + by = (dy32 - dy21); + cx = dx21; + cy = dy21; dx = x1; dy = y1; dax = 3.0d * ax; day = 3.0d * ay; @@ -72,11 +76,13 @@ double x2, double y2, double x3, double y3) { + final double dx21 = (x2 - x1); + final double dy21 = (y2 - y1); ax = 0.0d; ay = 0.0d; - bx = x1 - 2.0d * x2 + x3; - by = y1 - 2.0d * y2 + y3; - cx = 2.0d * (x2 - x1); - cy = 2.0d * (y2 - y1); + bx = (x3 - x2) - dx21; + by = (y3 - y2) - dy21; + cx = 2.0d * dx21; + cy = 2.0d * dy21; dx = x1; dy = y1; dax = 0.0d; day = 0.0d; --- old/src/java.desktop/share/classes/sun/java2d/marlin/DDasher.java 2017-08-28 16:48:25.845663239 +0200 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DDasher.java 2017-08-28 16:48:25.701663241 +0200 @@ -137,7 +137,7 @@ dashOn = !dashOn; } } - } else if (phase > 0) { + } else if (phase > 0.0d) { if (cycles >= MAX_CYCLES) { phase = 0.0d; } else { @@ -157,12 +157,13 @@ this.dash = dash; this.dashLen = dashLen; - this.startPhase = this.phase = phase; + this.phase = phase; + this.startPhase = phase; this.startDashOn = dashOn; this.startIdx = sidx; this.starting = true; - needsMoveTo = false; - firstSegidx = 0; + this.needsMoveTo = false; + this.firstSegidx = 0; this.recycleDashes = recycleDashes; @@ -201,8 +202,8 @@ } @Override - public void moveTo(double x0, double y0) { - if (firstSegidx > 0) { + public void moveTo(final double x0, final double y0) { + if (firstSegidx != 0) { out.moveTo(sx, sy); emitFirstSegments(); } @@ -210,8 +211,10 @@ this.idx = startIdx; this.dashOn = this.startDashOn; this.phase = this.startPhase; - this.sx = this.x0 = x0; - this.sy = this.y0 = y0; + this.sx = x0; + this.sy = y0; + this.x0 = x0; + this.y0 = y0; this.starting = true; } @@ -236,7 +239,7 @@ private void emitFirstSegments() { final double[] fSegBuf = firstSegmentsBuffer; - for (int i = 0; i < firstSegidx; ) { + for (int i = 0, len = firstSegidx; i < len; ) { int type = (int)fSegBuf[i]; emitSeg(fSegBuf, i + 1, type); i += (type - 1); @@ -251,48 +254,59 @@ private int firstSegidx; // precondition: pts must be in relative coordinates (relative to x0,y0) - private void goTo(double[] pts, int off, final int type) { - double x = pts[off + type - 4]; - double y = pts[off + type - 3]; - if (dashOn) { + private void goTo(final double[] pts, final int off, final int type, + final boolean on) + { + final int index = off + type; + final double x = pts[index - 4]; + final double y = pts[index - 3]; + + if (on) { if (starting) { - int len = type - 1; // - 2 + 1 - int segIdx = firstSegidx; - double[] buf = firstSegmentsBuffer; - if (segIdx + len > buf.length) { - if (DO_STATS) { - rdrCtx.stats.stat_array_dasher_firstSegmentsBuffer - .add(segIdx + len); - } - firstSegmentsBuffer = buf - = firstSegmentsBuffer_ref.widenArray(buf, segIdx, - segIdx + len); - } - buf[segIdx++] = type; - len--; - // small arraycopy (2, 4 or 6) but with offset: - System.arraycopy(pts, off, buf, segIdx, len); - segIdx += len; - firstSegidx = segIdx; + goTo_starting(pts, off, type); } else { if (needsMoveTo) { - out.moveTo(x0, y0); needsMoveTo = false; + out.moveTo(x0, y0); } emitSeg(pts, off, type); } } else { - starting = false; + if (starting) { + // low probability test (hotspot) + starting = false; + } needsMoveTo = true; } this.x0 = x; this.y0 = y; } + private void goTo_starting(final double[] pts, final int off, final int type) { + int len = type - 1; // - 2 + 1 + int segIdx = firstSegidx; + double[] buf = firstSegmentsBuffer; + + if (segIdx + len > buf.length) { + if (DO_STATS) { + rdrCtx.stats.stat_array_dasher_firstSegmentsBuffer + .add(segIdx + len); + } + firstSegmentsBuffer = buf + = firstSegmentsBuffer_ref.widenArray(buf, segIdx, + segIdx + len); + } + buf[segIdx++] = type; + len--; + // small arraycopy (2, 4 or 6) but with offset: + System.arraycopy(pts, off, buf, segIdx, len); + firstSegidx = segIdx + len; + } + @Override - public void lineTo(double x1, double y1) { - double dx = x1 - x0; - double dy = y1 - y0; + public void lineTo(final double x1, final double y1) { + final double dx = x1 - x0; + final double dy = y1 - y0; double len = dx*dx + dy*dy; if (len == 0.0d) { @@ -307,48 +321,61 @@ final double[] _curCurvepts = curCurvepts; final double[] _dash = dash; + final int _dashLen = this.dashLen; + + int _idx = idx; + boolean _dashOn = dashOn; + double _phase = phase; double leftInThisDashSegment; - double dashdx, dashdy, p; + double d, dashdx, dashdy, p; while (true) { - leftInThisDashSegment = _dash[idx] - phase; + d = _dash[_idx]; + leftInThisDashSegment = d - _phase; if (len <= leftInThisDashSegment) { _curCurvepts[0] = x1; _curCurvepts[1] = y1; - goTo(_curCurvepts, 0, 4); + + goTo(_curCurvepts, 0, 4, _dashOn); // Advance phase within current dash segment - phase += len; + _phase += len; + // TODO: compare double values using epsilon: if (len == leftInThisDashSegment) { - phase = 0.0d; - idx = (idx + 1) % dashLen; - dashOn = !dashOn; + _phase = 0.0d; + _idx = (_idx + 1) % _dashLen; + _dashOn = !_dashOn; } + + // Save local state: + idx = _idx; + dashOn = _dashOn; + phase = _phase; return; } - dashdx = _dash[idx] * cx; - dashdy = _dash[idx] * cy; + dashdx = d * cx; + dashdy = d * cy; - if (phase == 0.0d) { + if (_phase == 0.0d) { _curCurvepts[0] = x0 + dashdx; _curCurvepts[1] = y0 + dashdy; } else { - p = leftInThisDashSegment / _dash[idx]; + p = leftInThisDashSegment / d; _curCurvepts[0] = x0 + p * dashdx; _curCurvepts[1] = y0 + p * dashdy; } - goTo(_curCurvepts, 0, 4); + goTo(_curCurvepts, 0, 4, _dashOn); len -= leftInThisDashSegment; // Advance to next dash segment - idx = (idx + 1) % dashLen; - dashOn = !dashOn; - phase = 0.0d; + _idx = (_idx + 1) % _dashLen; + _dashOn = !_dashOn; + _phase = 0.0d; } } @@ -361,39 +388,55 @@ if (pointCurve(curCurvepts, type)) { return; } - li.initializeIterationOnCurve(curCurvepts, type); + final LengthIterator _li = li; + final double[] _curCurvepts = curCurvepts; + final double[] _dash = dash; + final int _dashLen = this.dashLen; + + _li.initializeIterationOnCurve(_curCurvepts, type); + + int _idx = idx; + boolean _dashOn = dashOn; + double _phase = phase; // initially the current curve is at curCurvepts[0...type] int curCurveoff = 0; double lastSplitT = 0.0d; double t; - double leftInThisDashSegment = dash[idx] - phase; + double leftInThisDashSegment = _dash[_idx] - _phase; - while ((t = li.next(leftInThisDashSegment)) < 1.0d) { + while ((t = _li.next(leftInThisDashSegment)) < 1.0d) { if (t != 0.0d) { DHelpers.subdivideAt((t - lastSplitT) / (1.0d - lastSplitT), - curCurvepts, curCurveoff, - curCurvepts, 0, - curCurvepts, type, type); + _curCurvepts, curCurveoff, + _curCurvepts, 0, + _curCurvepts, type, type); lastSplitT = t; - goTo(curCurvepts, 2, type); + goTo(_curCurvepts, 2, type, _dashOn); curCurveoff = type; } // Advance to next dash segment - idx = (idx + 1) % dashLen; - dashOn = !dashOn; - phase = 0.0d; - leftInThisDashSegment = dash[idx]; - } - goTo(curCurvepts, curCurveoff+2, type); - phase += li.lastSegLen(); - if (phase >= dash[idx]) { - phase = 0.0d; - idx = (idx + 1) % dashLen; - dashOn = !dashOn; - } + _idx = (_idx + 1) % _dashLen; + _dashOn = !_dashOn; + _phase = 0.0d; + leftInThisDashSegment = _dash[_idx]; + } + + goTo(_curCurvepts, curCurveoff + 2, type, _dashOn); + + _phase += _li.lastSegLen(); + if (_phase >= _dash[_idx]) { + _phase = 0.0d; + _idx = (_idx + 1) % _dashLen; + _dashOn = !_dashOn; + } + // Save local state: + idx = _idx; + dashOn = _dashOn; + phase = _phase; + // reset LengthIterator: - li.reset(); + _li.reset(); } private static boolean pointCurve(double[] curve, int type) { @@ -419,7 +462,7 @@ // tree; however, the trees we are interested in have the property that // every non leaf node has exactly 2 children static final class LengthIterator { - private enum Side {LEFT, RIGHT}; + private enum Side {LEFT, RIGHT} // Holds the curves at various levels of the recursion. The root // (i.e. the original curve) is at recCurveStack[0] (but then it // gets subdivided, the left half is put at 1, so most of the time @@ -669,11 +712,12 @@ // this is a bit of a hack. It returns -1 if we're not on a leaf, and // the length of the leaf if we are on a leaf. private double onLeaf() { - double[] curve = recCurveStack[recLevel]; + final double[] curve = recCurveStack[recLevel]; + final int _curveType = curveType; double polyLen = 0.0d; double x0 = curve[0], y0 = curve[1]; - for (int i = 2; i < curveType; i += 2) { + for (int i = 2; i < _curveType; i += 2) { final double x1 = curve[i], y1 = curve[i+1]; final double len = DHelpers.linelen(x0, y0, x1, y1); polyLen += len; @@ -683,8 +727,8 @@ } final double lineLen = DHelpers.linelen(curve[0], curve[1], - curve[curveType-2], - curve[curveType-1]); + curve[_curveType-2], + curve[_curveType-1]); if ((polyLen - lineLen) < ERR || recLevel == REC_LIMIT) { return (polyLen + lineLen) / 2.0d; } @@ -693,9 +737,9 @@ } @Override - public void curveTo(double x1, double y1, - double x2, double y2, - double x3, double y3) + public void curveTo(final double x1, final double y1, + final double x2, final double y2, + final double x3, final double y3) { final double[] _curCurvepts = curCurvepts; _curCurvepts[0] = x0; _curCurvepts[1] = y0; @@ -706,7 +750,9 @@ } @Override - public void quadTo(double x1, double y1, double x2, double y2) { + public void quadTo(final double x1, final double y1, + final double x2, final double y2) + { final double[] _curCurvepts = curCurvepts; _curCurvepts[0] = x0; _curCurvepts[1] = y0; _curCurvepts[2] = x1; _curCurvepts[3] = y1; @@ -717,7 +763,7 @@ @Override public void closePath() { lineTo(sx, sy); - if (firstSegidx > 0) { + if (firstSegidx != 0) { if (!dashOn || needsMoveTo) { out.moveTo(sx, sy); } @@ -728,7 +774,7 @@ @Override public void pathDone() { - if (firstSegidx > 0) { + if (firstSegidx != 0) { out.moveTo(sx, sy); emitFirstSegments(); } --- old/src/java.desktop/share/classes/sun/java2d/marlin/DHelpers.java 2017-08-28 16:48:26.297663233 +0200 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DHelpers.java 2017-08-28 16:48:26.137663235 +0200 @@ -30,6 +30,10 @@ import static java.lang.Math.sqrt; import static java.lang.Math.cbrt; import static java.lang.Math.acos; +import java.util.Arrays; +import static sun.java2d.marlin.MarlinConst.INITIAL_EDGES_COUNT; +import sun.java2d.marlin.stats.Histogram; +import sun.java2d.marlin.stats.StatLong; final class DHelpers implements MarlinConst { @@ -171,15 +175,6 @@ return ret; } - static double polyLineLength(double[] poly, final int off, final int nCoords) { - assert nCoords % 2 == 0 && poly.length >= off + nCoords : ""; - double acc = 0.0d; - for (int i = off + 2; i < off + nCoords; i += 2) { - acc += linelen(poly[i], poly[i+1], poly[i-2], poly[i-1]); - } - return acc; - } - static double linelen(double x1, double y1, double x2, double y2) { final double dx = x2 - x1; final double dy = y2 - y1; @@ -433,4 +428,283 @@ return; } } + + // From sun.java2d.loops.GeneralRenderer: + + static final int OUTCODE_TOP = 1; + static final int OUTCODE_BOTTOM = 2; + static final int OUTCODE_LEFT = 4; + static final int OUTCODE_RIGHT = 8; + + static int outcode(final double x, final double y, + final double[] clipRect) + { + int code; + if (y < clipRect[0]) { + code = OUTCODE_TOP; + } else if (y >= clipRect[1]) { + code = OUTCODE_BOTTOM; + } else { + code = 0; + } + if (x < clipRect[2]) { + code |= OUTCODE_LEFT; + } else if (x >= clipRect[3]) { + code |= OUTCODE_RIGHT; + } + return code; + } + + // a stack of polynomial curves where each curve shares endpoints with + // adjacent ones. + static final class PolyStack { + private static final byte TYPE_LINETO = (byte) 0; + private static final byte TYPE_QUADTO = (byte) 1; + private static final byte TYPE_CUBICTO = (byte) 2; + + // curves capacity = edges count (8192) = edges x 2 (coords) + private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1; + + // types capacity = edges count (4096) + private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT; + + double[] curves; + int end; + byte[] curveTypes; + int numCurves; + + // curves ref (dirty) + final DoubleArrayCache.Reference curves_ref; + // curveTypes ref (dirty) + final ByteArrayCache.Reference curveTypes_ref; + + // used marks (stats only) + int curveTypesUseMark; + int curvesUseMark; + + private final StatLong stat_polystack_types; + private final StatLong stat_polystack_curves; + private final Histogram hist_polystack_curves; + private final StatLong stat_array_polystack_curves; + private final StatLong stat_array_polystack_curveTypes; + + PolyStack(final DRendererContext rdrCtx) { + this(rdrCtx, null, null, null, null, null); + } + + PolyStack(final DRendererContext rdrCtx, + final StatLong stat_polystack_types, + final StatLong stat_polystack_curves, + final Histogram hist_polystack_curves, + final StatLong stat_array_polystack_curves, + final StatLong stat_array_polystack_curveTypes) + { + curves_ref = rdrCtx.newDirtyDoubleArrayRef(INITIAL_CURVES_COUNT); // 32K + curves = curves_ref.initial; + + curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K + curveTypes = curveTypes_ref.initial; + numCurves = 0; + end = 0; + + if (DO_STATS) { + curveTypesUseMark = 0; + curvesUseMark = 0; + } + this.stat_polystack_types = stat_polystack_types; + this.stat_polystack_curves = stat_polystack_curves; + this.hist_polystack_curves = hist_polystack_curves; + this.stat_array_polystack_curves = stat_array_polystack_curves; + this.stat_array_polystack_curveTypes = stat_array_polystack_curveTypes; + } + + /** + * Disposes this PolyStack: + * clean up before reusing this instance + */ + void dispose() { + end = 0; + numCurves = 0; + + if (DO_STATS) { + stat_polystack_types.add(curveTypesUseMark); + stat_polystack_curves.add(curvesUseMark); + hist_polystack_curves.add(curvesUseMark); + + // reset marks + curveTypesUseMark = 0; + curvesUseMark = 0; + } + + // Return arrays: + // curves and curveTypes are kept dirty + curves = curves_ref.putArray(curves); + curveTypes = curveTypes_ref.putArray(curveTypes); + } + + private void ensureSpace(final int n) { + // use substraction to avoid integer overflow: + if (curves.length - end < n) { + if (DO_STATS) { + stat_array_polystack_curves.add(end + n); + } + curves = curves_ref.widenArray(curves, end, end + n); + } + if (curveTypes.length <= numCurves) { + if (DO_STATS) { + stat_array_polystack_curveTypes.add(numCurves + 1); + } + curveTypes = curveTypes_ref.widenArray(curveTypes, + numCurves, + numCurves + 1); + } + } + + void pushCubic(double x0, double y0, + double x1, double y1, + double x2, double y2) + { + ensureSpace(6); + curveTypes[numCurves++] = TYPE_CUBICTO; + // we reverse the coordinate order to make popping easier + final double[] _curves = curves; + int e = end; + _curves[e++] = x2; _curves[e++] = y2; + _curves[e++] = x1; _curves[e++] = y1; + _curves[e++] = x0; _curves[e++] = y0; + end = e; + } + + void pushQuad(double x0, double y0, + double x1, double y1) + { + ensureSpace(4); + curveTypes[numCurves++] = TYPE_QUADTO; + final double[] _curves = curves; + int e = end; + _curves[e++] = x1; _curves[e++] = y1; + _curves[e++] = x0; _curves[e++] = y0; + end = e; + } + + void pushLine(double x, double y) { + ensureSpace(2); + curveTypes[numCurves++] = TYPE_LINETO; + curves[end++] = x; curves[end++] = y; + } + + void pullAll(final DPathConsumer2D io) { + final int nc = numCurves; + if (nc == 0) { + return; + } + if (DO_STATS) { + // update used marks: + if (numCurves > curveTypesUseMark) { + curveTypesUseMark = numCurves; + } + if (end > curvesUseMark) { + curvesUseMark = end; + } + } + final byte[] _curveTypes = curveTypes; + final double[] _curves = curves; + int e = 0; + + for (int i = 0; i < nc; i++) { + switch(_curveTypes[i]) { + case TYPE_LINETO: + io.lineTo(_curves[e], _curves[e+1]); + e += 2; + continue; + case TYPE_QUADTO: + io.quadTo(_curves[e+0], _curves[e+1], + _curves[e+2], _curves[e+3]); + e += 4; + continue; + case TYPE_CUBICTO: + io.curveTo(_curves[e+0], _curves[e+1], + _curves[e+2], _curves[e+3], + _curves[e+4], _curves[e+5]); + e += 6; + continue; + default: + } + } + numCurves = 0; + end = 0; + } + + void popAll(final DPathConsumer2D io) { + int nc = numCurves; + if (nc == 0) { + return; + } + if (DO_STATS) { + // update used marks: + if (numCurves > curveTypesUseMark) { + curveTypesUseMark = numCurves; + } + if (end > curvesUseMark) { + curvesUseMark = end; + } + } + final byte[] _curveTypes = curveTypes; + final double[] _curves = curves; + int e = end; + + while (nc != 0) { + switch(_curveTypes[--nc]) { + case TYPE_LINETO: + e -= 2; + io.lineTo(_curves[e], _curves[e+1]); + continue; + case TYPE_QUADTO: + e -= 4; + io.quadTo(_curves[e+0], _curves[e+1], + _curves[e+2], _curves[e+3]); + continue; + case TYPE_CUBICTO: + e -= 6; + io.curveTo(_curves[e+0], _curves[e+1], + _curves[e+2], _curves[e+3], + _curves[e+4], _curves[e+5]); + continue; + default: + } + } + numCurves = 0; + end = 0; + } + + @Override + public String toString() { + String ret = ""; + int nc = numCurves; + int last = end; + int len; + while (nc != 0) { + switch(curveTypes[--nc]) { + case TYPE_LINETO: + len = 2; + ret += "line: "; + break; + case TYPE_QUADTO: + len = 4; + ret += "quad: "; + break; + case TYPE_CUBICTO: + len = 6; + ret += "cubic: "; + break; + default: + len = 0; + } + last -= len; + ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len)) + + "\n"; + } + return ret; + } + } } --- old/src/java.desktop/share/classes/sun/java2d/marlin/DMarlinRenderingEngine.java 2017-08-28 16:48:26.669663228 +0200 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DMarlinRenderingEngine.java 2017-08-28 16:48:26.501663230 +0200 @@ -84,6 +84,9 @@ static final double UPPER_BND = Float.MAX_VALUE / 2.0d; static final double LOWER_BND = -UPPER_BND; + static final boolean DO_CLIP = MarlinProperties.isDoClip(); + static final boolean DO_TRACE = false; + /** * Public constructor */ @@ -324,6 +327,7 @@ int dashLen = -1; boolean recycleDashes = false; + double scale = 1.0d; double[] dashesD = null; // Ensure converting dashes to double precision: @@ -364,7 +368,7 @@ // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we // leave a bit of room for error. if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) { - final double scale = Math.sqrt(a*a + c*c); + scale = Math.sqrt(a*a + c*c); if (dashesD != null) { for (int i = 0; i < dashLen; i++) { @@ -399,23 +403,44 @@ at = null; } + final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; + + if (DO_TRACE) { + // trace Stroker: + pc2d = transformerPC2D.traceStroker(pc2d); + } + if (USE_SIMPLIFIER) { // Use simplifier after stroker before Renderer // to remove collinear segments (notably due to cap square) pc2d = rdrCtx.simplifier.init(pc2d); } - final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; + // deltaTransformConsumer may adjust the clip rectangle: pc2d = transformerPC2D.deltaTransformConsumer(pc2d, strokerat); - pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit); + // stroker will adjust the clip rectangle (width / miter limit): + pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit, scale); if (dashesD != null) { pc2d = rdrCtx.dasher.init(pc2d, dashesD, dashLen, dashphase, recycleDashes); + } else if (rdrCtx.doClip && (caps != Stroker.CAP_BUTT)) { + if (DO_TRACE) { + pc2d = transformerPC2D.traceClosedPathDetector(pc2d); + } + + // If no dash and clip is enabled: + // detect closedPaths (polygons) for caps + pc2d = transformerPC2D.detectClosedPath(pc2d); } pc2d = transformerPC2D.inverseDeltaTransformConsumer(pc2d, strokerat); + if (DO_TRACE) { + // trace Input: + pc2d = transformerPC2D.traceInput(pc2d); + } + final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx, src.getPathIterator(at)); @@ -805,6 +830,18 @@ clip.getWidth(), clip.getHeight(), PathIterator.WIND_NON_ZERO); + if (DO_CLIP) { + // Define the initial clip bounds: + final double[] clipRect = rdrCtx.clipRect; + clipRect[0] = clip.getLoY(); + clipRect[1] = clip.getLoY() + clip.getHeight(); + clipRect[2] = clip.getLoX(); + clipRect[3] = clip.getLoX() + clip.getWidth(); + + // Enable clipping: + rdrCtx.doClip = true; + } + strokeTo(rdrCtx, s, _at, bs, thin, norm, true, r); } if (r.endRendering()) { @@ -862,8 +899,8 @@ final DRendererContext rdrCtx = getRendererContext(); try { r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(), - clip.getWidth(), clip.getHeight(), - DRenderer.WIND_EVEN_ODD); + clip.getWidth(), clip.getHeight(), + DRenderer.WIND_EVEN_ODD); r.moveTo( x, y); r.lineTo( (x+dx1), (y+dy1)); @@ -1044,6 +1081,8 @@ // optimisation parameters logInfo("sun.java2d.renderer.useSimplifier = " + MarlinConst.USE_SIMPLIFIER); + logInfo("sun.java2d.renderer.clip = " + + MarlinProperties.isDoClip()); // debugging parameters logInfo("sun.java2d.renderer.doStats = " --- old/src/java.desktop/share/classes/sun/java2d/marlin/DRenderer.java 2017-08-28 16:48:27.249663220 +0200 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DRenderer.java 2017-08-28 16:48:27.081663222 +0200 @@ -46,6 +46,9 @@ static final int SUBPIXEL_MASK_X = SUBPIXEL_POSITIONS_X - 1; static final int SUBPIXEL_MASK_Y = SUBPIXEL_POSITIONS_Y - 1; + static final double RDR_OFFSET_X = 0.501d / SUBPIXEL_SCALE_X; + static final double RDR_OFFSET_Y = 0.501d / SUBPIXEL_SCALE_Y; + // number of subpixels corresponding to a tile line private static final int SUBPIXEL_TILE = TILE_H << SUBPIXEL_LG_POSITIONS_Y; @@ -668,7 +671,7 @@ } @Override - public void moveTo(double pix_x0, double pix_y0) { + public void moveTo(final double pix_x0, final double pix_y0) { closePath(); final double sx = tosubpixx(pix_x0); final double sy = tosubpixy(pix_y0); @@ -679,7 +682,7 @@ } @Override - public void lineTo(double pix_x1, double pix_y1) { + public void lineTo(final double pix_x1, final double pix_y1) { final double x1 = tosubpixx(pix_x1); final double y1 = tosubpixy(pix_y1); addLine(x0, y0, x1, y1); @@ -688,24 +691,26 @@ } @Override - public void curveTo(double x1, double y1, - double x2, double y2, - double x3, double y3) + public void curveTo(final double pix_x1, final double pix_y1, + final double pix_x2, final double pix_y2, + final double pix_x3, final double pix_y3) { - final double xe = tosubpixx(x3); - final double ye = tosubpixy(y3); - curve.set(x0, y0, tosubpixx(x1), tosubpixy(y1), - tosubpixx(x2), tosubpixy(y2), xe, ye); + final double xe = tosubpixx(pix_x3); + final double ye = tosubpixy(pix_y3); + curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1), + tosubpixx(pix_x2), tosubpixy(pix_y2), xe, ye); curveBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); x0 = xe; y0 = ye; } @Override - public void quadTo(double x1, double y1, double x2, double y2) { - final double xe = tosubpixx(x2); - final double ye = tosubpixy(y2); - curve.set(x0, y0, tosubpixx(x1), tosubpixy(y1), xe, ye); + public void quadTo(final double pix_x1, final double pix_y1, + final double pix_x2, final double pix_y2) + { + final double xe = tosubpixx(pix_x2); + final double ye = tosubpixy(pix_y2); + curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1), xe, ye); quadBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); x0 = xe; y0 = ye; @@ -713,9 +718,11 @@ @Override public void closePath() { - addLine(x0, y0, sx0, sy0); - x0 = sx0; - y0 = sy0; + if (x0 != sx0 || y0 != sy0) { + addLine(x0, y0, sx0, sy0); + x0 = sx0; + y0 = sy0; + } } @Override --- old/src/java.desktop/share/classes/sun/java2d/marlin/DRendererContext.java 2017-08-28 16:48:27.705663214 +0200 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DRendererContext.java 2017-08-28 16:48:27.557663216 +0200 @@ -75,6 +75,12 @@ final MarlinCache cache; // flag indicating the shape is stroked (1) or filled (0) int stroking = 0; + // flag indicating to clip the shape + boolean doClip = false; + // flag indicating if the path is closed or not (in advance) to handle properly caps + boolean closedPath = false; + // clip rectangle (ymin, ymax, xmin, xmax): + final double[] clipRect = new double[4]; // Array caches: /* clean int[] cache (zero-filled) = 5 refs */ @@ -119,7 +125,7 @@ nPQPathIterator = new NormalizingPathIterator.NearestPixelQuarter(double6); // MarlinRenderingEngine.TransformingPathConsumer2D - transformerPC2D = new DTransformingPathConsumer2D(); + transformerPC2D = new DTransformingPathConsumer2D(this); // Renderer: cache = new MarlinCache(this); @@ -141,7 +147,10 @@ } stats.totalOffHeap = 0L; } - stroking = 0; + stroking = 0; + doClip = false; + closedPath = false; + // if context is maked as DIRTY: if (dirty) { // may happen if an exception if thrown in the pipeline processing: --- old/src/java.desktop/share/classes/sun/java2d/marlin/DStroker.java 2017-08-28 16:48:28.105663208 +0200 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DStroker.java 2017-08-28 16:48:27.933663210 +0200 @@ -26,6 +26,7 @@ package sun.java2d.marlin; import java.util.Arrays; +import sun.java2d.marlin.DHelpers.PolyStack; // TODO: some of the arithmetic here is too verbose and prone to hard to // debug typos. We should consider making a small Point/Vector class that @@ -71,7 +72,11 @@ // it to floating point, so that's why the divisions by 2^16 are there. private static final double ROUND_JOIN_THRESHOLD = 1000.0d/65536.0d; - private static final double C = 0.5522847498307933d; + // kappa = (4/3) * (SQRT(2) - 1) + private static final double C = (4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d); + + // SQRT(2) + private static final double SQRT_2 = Math.sqrt(2.0d); private static final int MAX_N_CURVES = 11; @@ -118,6 +123,20 @@ // dirty curve final DCurve curve; + // Bounds of the drawing region, at pixel precision. + private double[] clipRect; + + // the outcode of the current point + private int cOutCode = 0; + + // the outcode of the starting point + private int sOutCode = 0; + + // flag indicating if the path is opened (clipped) + private boolean opened = false; + // flag indicating if the starting point's cap is done + private boolean capStart = false; + /** * Constructs a DStroker. * @param rdrCtx per-thread renderer context @@ -125,7 +144,15 @@ DStroker(final DRendererContext rdrCtx) { this.rdrCtx = rdrCtx; - this.reverse = new PolyStack(rdrCtx); + this.reverse = (rdrCtx.stats != null) ? + new PolyStack(rdrCtx, + rdrCtx.stats.stat_str_polystack_types, + rdrCtx.stats.stat_str_polystack_curves, + rdrCtx.stats.hist_str_polystack_curves, + rdrCtx.stats.stat_array_str_polystack_curves, + rdrCtx.stats.stat_array_str_polystack_types) + : new PolyStack(rdrCtx); + this.curve = rdrCtx.curve; } @@ -141,13 +168,15 @@ * JOIN_MITER, JOIN_ROUND or * JOIN_BEVEL. * @param miterLimit the desired miter limit + * @param scale scaling factor applied to clip boundaries * @return this instance */ - DStroker init(DPathConsumer2D pc2d, - double lineWidth, - int capStyle, - int joinStyle, - double miterLimit) + DStroker init(final DPathConsumer2D pc2d, + final double lineWidth, + final int capStyle, + final int joinStyle, + final double miterLimit, + final double scale) { this.out = pc2d; @@ -156,13 +185,41 @@ this.capStyle = capStyle; this.joinStyle = joinStyle; - double limit = miterLimit * lineWidth2; + final double limit = miterLimit * lineWidth2; this.miterLimitSq = limit * limit; this.prev = CLOSE; rdrCtx.stroking = 1; + if (rdrCtx.doClip) { + // Adjust the clipping rectangle with the stroker margin (miter limit, width) + double rdrOffX = 0.0d, rdrOffY = 0.0d; + double margin = lineWidth2; + + if (capStyle == CAP_SQUARE) { + margin *= SQRT_2; + } + if ((joinStyle == JOIN_MITER) && (margin < limit)) { + margin = limit; + } + if (scale != 1.0d) { + margin *= scale; + rdrOffX = scale * DRenderer.RDR_OFFSET_X; + rdrOffY = scale * DRenderer.RDR_OFFSET_Y; + } + + // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY + // adjust clip rectangle (ymin, ymax, xmin, xmax): + final double[] _clipRect = rdrCtx.clipRect; + _clipRect[0] -= margin - rdrOffY; + _clipRect[1] += margin + rdrOffY; + _clipRect[2] -= margin - rdrOffX; + _clipRect[3] += margin + rdrOffX; + this.clipRect = _clipRect; + } else { + this.clipRect = null; + } return this; // fluent API } @@ -173,6 +230,9 @@ void dispose() { reverse.dispose(); + opened = false; + capStart = false; + if (DO_CLEAN_DIRTY) { // Force zero-fill dirty arrays: Arrays.fill(offset0, 0.0d); @@ -443,19 +503,62 @@ } @Override - public void moveTo(double x0, double y0) { - if (prev == DRAWING_OP_TO) { - finish(); + public void moveTo(final double x0, final double y0) { + moveTo(x0, y0, cOutCode); + // update starting point: + this.sx0 = x0; + this.sy0 = y0; + this.sdx = 1.0d; + this.sdy = 0.0d; + this.opened = false; + this.capStart = false; + + if (clipRect != null) { + final int outcode = DHelpers.outcode(x0, y0, clipRect); + this.cOutCode = outcode; + this.sOutCode = outcode; + } + } + + private void moveTo(final double x0, final double y0, + final int outcode) + { + if (prev == MOVE_TO) { + this.cx0 = x0; + this.cy0 = y0; + } else { + if (prev == DRAWING_OP_TO) { + finish(outcode); + } + this.prev = MOVE_TO; + this.cx0 = x0; + this.cy0 = y0; + this.cdx = 1.0d; + this.cdy = 0.0d; } - this.sx0 = this.cx0 = x0; - this.sy0 = this.cy0 = y0; - this.cdx = this.sdx = 1.0d; - this.cdy = this.sdy = 0.0d; - this.prev = MOVE_TO; } @Override - public void lineTo(double x1, double y1) { + public void lineTo(final double x1, final double y1) { + lineTo(x1, y1, false); + } + + private void lineTo(final double x1, final double y1, + final boolean force) + { + final int outcode0 = this.cOutCode; + if (!force && clipRect != null) { + final int outcode1 = DHelpers.outcode(x1, y1, clipRect); + this.cOutCode = outcode1; + + // basic rejection criteria + if ((outcode0 & outcode1) != 0) { + moveTo(x1, y1, outcode0); + opened = true; + return; + } + } + double dx = x1 - cx0; double dy = y1 - cy0; if (dx == 0.0d && dy == 0.0d) { @@ -465,7 +568,7 @@ final double mx = offset0[0]; final double my = offset0[1]; - drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my); + drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0); emitLineTo(cx0 + mx, cy0 + my); emitLineTo( x1 + mx, y1 + my); @@ -473,43 +576,64 @@ emitLineToRev(cx0 - mx, cy0 - my); emitLineToRev( x1 - mx, y1 - my); - this.cmx = mx; - this.cmy = my; - this.cdx = dx; - this.cdy = dy; + this.prev = DRAWING_OP_TO; this.cx0 = x1; this.cy0 = y1; - this.prev = DRAWING_OP_TO; + this.cdx = dx; + this.cdy = dy; + this.cmx = mx; + this.cmy = my; } @Override public void closePath() { - if (prev != DRAWING_OP_TO) { + // distinguish empty path at all vs opened path ? + if (prev != DRAWING_OP_TO && !opened) { if (prev == CLOSE) { return; } emitMoveTo(cx0, cy0 - lineWidth2); - this.cmx = this.smx = 0.0d; - this.cmy = this.smy = -lineWidth2; - this.cdx = this.sdx = 1.0d; - this.cdy = this.sdy = 0.0d; - finish(); + + this.sdx = 1.0d; + this.sdy = 0.0d; + this.cdx = 1.0d; + this.cdy = 0.0d; + + this.smx = 0.0d; + this.smy = -lineWidth2; + this.cmx = 0.0d; + this.cmy = -lineWidth2; + + finish(cOutCode); return; } - if (cx0 != sx0 || cy0 != sy0) { - lineTo(sx0, sy0); - } + if (sOutCode == 0) { + if (cx0 != sx0 || cy0 != sy0) { + lineTo(sx0, sy0, true); + } - drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy); + drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, cOutCode); - emitLineTo(sx0 + smx, sy0 + smy); + emitLineTo(sx0 + smx, sy0 + smy); - emitMoveTo(sx0 - smx, sy0 - smy); + if (opened) { + emitLineTo(sx0 - smx, sy0 - smy); + } else { + emitMoveTo(sx0 - smx, sy0 - smy); + } + } + // Ignore caps like finish(false) emitReverse(); this.prev = CLOSE; - emitClose(); + + if (opened) { + // do not emit close + opened = false; + } else { + emitClose(); + } } private void emitReverse() { @@ -519,7 +643,7 @@ @Override public void pathDone() { if (prev == DRAWING_OP_TO) { - finish(); + finish(cOutCode); } out.pathDone(); @@ -532,23 +656,39 @@ dispose(); } - private void finish() { - if (capStyle == CAP_ROUND) { - drawRoundCap(cx0, cy0, cmx, cmy); - } else if (capStyle == CAP_SQUARE) { - emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy); - emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy); - } + private void finish(final int outcode) { + // Problem: impossible to guess if the path will be closed in advance + // i.e. if caps must be drawn or not ? + // Solution: use the ClosedPathDetector before Stroker to determine + // if the path is a closed path or not + if (!rdrCtx.closedPath) { + if (outcode == 0) { + // current point = end's cap: + if (capStyle == CAP_ROUND) { + drawRoundCap(cx0, cy0, cmx, cmy); + } else if (capStyle == CAP_SQUARE) { + emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy); + emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy); + } + } + emitReverse(); - emitReverse(); + if (!capStart) { + capStart = true; - if (capStyle == CAP_ROUND) { - drawRoundCap(sx0, sy0, -smx, -smy); - } else if (capStyle == CAP_SQUARE) { - emitLineTo(sx0 + smy - smx, sy0 - smx - smy); - emitLineTo(sx0 + smy + smx, sy0 - smx + smy); + if (sOutCode == 0) { + // starting point = initial cap: + if (capStyle == CAP_ROUND) { + drawRoundCap(sx0, sy0, -smx, -smy); + } else if (capStyle == CAP_SQUARE) { + emitLineTo(sx0 + smy - smx, sy0 - smx - smy); + emitLineTo(sx0 + smy + smx, sy0 - smx + smy); + } + } + } + } else { + emitReverse(); } - emitClose(); } @@ -620,23 +760,28 @@ double x0, double y0, double dx, double dy, double omx, double omy, - double mx, double my) + double mx, double my, + final int outcode) { if (prev != DRAWING_OP_TO) { emitMoveTo(x0 + mx, y0 + my); - this.sdx = dx; - this.sdy = dy; - this.smx = mx; - this.smy = my; + if (!opened) { + this.sdx = dx; + this.sdy = dy; + this.smx = mx; + this.smy = my; + } } else { - boolean cw = isCW(pdx, pdy, dx, dy); - if (joinStyle == JOIN_MITER) { - drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw); - } else if (joinStyle == JOIN_ROUND) { - drawRoundJoin(x0, y0, - omx, omy, - mx, my, cw, - ROUND_JOIN_THRESHOLD); + final boolean cw = isCW(pdx, pdy, dx, dy); + if (outcode == 0) { + if (joinStyle == JOIN_MITER) { + drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw); + } else if (joinStyle == JOIN_ROUND) { + drawRoundJoin(x0, y0, + omx, omy, + mx, my, cw, + ROUND_JOIN_THRESHOLD); + } } emitLineTo(x0, y0, !cw); } @@ -941,10 +1086,29 @@ return ret; } - @Override public void curveTo(double x1, double y1, - double x2, double y2, - double x3, double y3) - { + @Override + public void curveTo(final double x1, final double y1, + final double x2, final double y2, + final double x3, final double y3) + { + final int outcode0 = this.cOutCode; + if (clipRect != null) { + final int outcode3 = DHelpers.outcode(x3, y3, clipRect); + this.cOutCode = outcode3; + + if (outcode3 != 0) { + final int outcode1 = DHelpers.outcode(x1, y1, clipRect); + final int outcode2 = DHelpers.outcode(x2, y2, clipRect); + + // basic rejection criteria + if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) { + moveTo(x3, y3, outcode0); + opened = true; + return; + } + } + } + final double[] mid = middle; mid[0] = cx0; mid[1] = cy0; @@ -953,7 +1117,7 @@ mid[6] = x3; mid[7] = y3; // need these so we can update the state at the end of this method - final double xf = mid[6], yf = mid[7]; + final double xf = x3, yf = y3; double dxs = mid[2] - mid[0]; double dys = mid[3] - mid[1]; double dxf = mid[6] - mid[4]; @@ -979,6 +1143,10 @@ } if (dxs == 0.0d && dys == 0.0d) { // this happens if the "curve" is just a point + // fix outcode0 for lineTo() call: + if (clipRect != null) { + this.cOutCode = outcode0; + } lineTo(mid[0], mid[1]); return; } @@ -997,7 +1165,7 @@ } computeOffset(dxs, dys, lineWidth2, offset0); - drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]); + drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2); @@ -1032,16 +1200,36 @@ emitLineToRev(r[kind - 2], r[kind - 1]); } - this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d; - this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d; - this.cdx = dxf; - this.cdy = dyf; + this.prev = DRAWING_OP_TO; this.cx0 = xf; this.cy0 = yf; - this.prev = DRAWING_OP_TO; + this.cdx = dxf; + this.cdy = dyf; + this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d; + this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d; } - @Override public void quadTo(double x1, double y1, double x2, double y2) { + @Override + public void quadTo(final double x1, final double y1, + final double x2, final double y2) + { + final int outcode0 = this.cOutCode; + if (clipRect != null) { + final int outcode2 = DHelpers.outcode(x2, y2, clipRect); + this.cOutCode = outcode2; + + if (outcode2 != 0) { + final int outcode1 = DHelpers.outcode(x1, y1, clipRect); + + // basic rejection criteria + if ((outcode0 & outcode1 & outcode2) != 0) { + moveTo(x2, y2, outcode0); + opened = true; + return; + } + } + } + final double[] mid = middle; mid[0] = cx0; mid[1] = cy0; @@ -1049,7 +1237,7 @@ mid[4] = x2; mid[5] = y2; // need these so we can update the state at the end of this method - final double xf = mid[4], yf = mid[5]; + final double xf = x2, yf = y2; double dxs = mid[2] - mid[0]; double dys = mid[3] - mid[1]; double dxf = mid[4] - mid[2]; @@ -1060,6 +1248,10 @@ } if (dxs == 0.0d && dys == 0.0d) { // this happens if the "curve" is just a point + // fix outcode0 for lineTo() call: + if (clipRect != null) { + this.cOutCode = outcode0; + } lineTo(mid[0], mid[1]); return; } @@ -1077,7 +1269,7 @@ } computeOffset(dxs, dys, lineWidth2, offset0); - drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]); + drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2); @@ -1112,214 +1304,16 @@ emitLineToRev(r[kind - 2], r[kind - 1]); } - this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d; - this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d; - this.cdx = dxf; - this.cdy = dyf; + this.prev = DRAWING_OP_TO; this.cx0 = xf; this.cy0 = yf; - this.prev = DRAWING_OP_TO; + this.cdx = dxf; + this.cdy = dyf; + this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d; + this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d; } @Override public long getNativeConsumer() { throw new InternalError("Stroker doesn't use a native consumer"); } - - // a stack of polynomial curves where each curve shares endpoints with - // adjacent ones. - static final class PolyStack { - private static final byte TYPE_LINETO = (byte) 0; - private static final byte TYPE_QUADTO = (byte) 1; - private static final byte TYPE_CUBICTO = (byte) 2; - - // curves capacity = edges count (8192) = edges x 2 (coords) - private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1; - - // types capacity = edges count (4096) - private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT; - - double[] curves; - int end; - byte[] curveTypes; - int numCurves; - - // per-thread renderer context - final DRendererContext rdrCtx; - - // curves ref (dirty) - final DoubleArrayCache.Reference curves_ref; - // curveTypes ref (dirty) - final ByteArrayCache.Reference curveTypes_ref; - - // used marks (stats only) - int curveTypesUseMark; - int curvesUseMark; - - /** - * Constructor - * @param rdrCtx per-thread renderer context - */ - PolyStack(final DRendererContext rdrCtx) { - this.rdrCtx = rdrCtx; - - curves_ref = rdrCtx.newDirtyDoubleArrayRef(INITIAL_CURVES_COUNT); // 32K - curves = curves_ref.initial; - - curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K - curveTypes = curveTypes_ref.initial; - numCurves = 0; - end = 0; - - if (DO_STATS) { - curveTypesUseMark = 0; - curvesUseMark = 0; - } - } - - /** - * Disposes this PolyStack: - * clean up before reusing this instance - */ - void dispose() { - end = 0; - numCurves = 0; - - if (DO_STATS) { - rdrCtx.stats.stat_rdr_poly_stack_types.add(curveTypesUseMark); - rdrCtx.stats.stat_rdr_poly_stack_curves.add(curvesUseMark); - rdrCtx.stats.hist_rdr_poly_stack_curves.add(curvesUseMark); - - // reset marks - curveTypesUseMark = 0; - curvesUseMark = 0; - } - - // Return arrays: - // curves and curveTypes are kept dirty - curves = curves_ref.putArray(curves); - curveTypes = curveTypes_ref.putArray(curveTypes); - } - - private void ensureSpace(final int n) { - // use substraction to avoid integer overflow: - if (curves.length - end < n) { - if (DO_STATS) { - rdrCtx.stats.stat_array_stroker_polystack_curves - .add(end + n); - } - curves = curves_ref.widenArray(curves, end, end + n); - } - if (curveTypes.length <= numCurves) { - if (DO_STATS) { - rdrCtx.stats.stat_array_stroker_polystack_curveTypes - .add(numCurves + 1); - } - curveTypes = curveTypes_ref.widenArray(curveTypes, - numCurves, - numCurves + 1); - } - } - - void pushCubic(double x0, double y0, - double x1, double y1, - double x2, double y2) - { - ensureSpace(6); - curveTypes[numCurves++] = TYPE_CUBICTO; - // we reverse the coordinate order to make popping easier - final double[] _curves = curves; - int e = end; - _curves[e++] = x2; _curves[e++] = y2; - _curves[e++] = x1; _curves[e++] = y1; - _curves[e++] = x0; _curves[e++] = y0; - end = e; - } - - void pushQuad(double x0, double y0, - double x1, double y1) - { - ensureSpace(4); - curveTypes[numCurves++] = TYPE_QUADTO; - final double[] _curves = curves; - int e = end; - _curves[e++] = x1; _curves[e++] = y1; - _curves[e++] = x0; _curves[e++] = y0; - end = e; - } - - void pushLine(double x, double y) { - ensureSpace(2); - curveTypes[numCurves++] = TYPE_LINETO; - curves[end++] = x; curves[end++] = y; - } - - void popAll(DPathConsumer2D io) { - if (DO_STATS) { - // update used marks: - if (numCurves > curveTypesUseMark) { - curveTypesUseMark = numCurves; - } - if (end > curvesUseMark) { - curvesUseMark = end; - } - } - final byte[] _curveTypes = curveTypes; - final double[] _curves = curves; - int nc = numCurves; - int e = end; - - while (nc != 0) { - switch(_curveTypes[--nc]) { - case TYPE_LINETO: - e -= 2; - io.lineTo(_curves[e], _curves[e+1]); - continue; - case TYPE_QUADTO: - e -= 4; - io.quadTo(_curves[e+0], _curves[e+1], - _curves[e+2], _curves[e+3]); - continue; - case TYPE_CUBICTO: - e -= 6; - io.curveTo(_curves[e+0], _curves[e+1], - _curves[e+2], _curves[e+3], - _curves[e+4], _curves[e+5]); - continue; - default: - } - } - numCurves = 0; - end = 0; - } - - @Override - public String toString() { - String ret = ""; - int nc = numCurves; - int last = end; - int len; - while (nc != 0) { - switch(curveTypes[--nc]) { - case TYPE_LINETO: - len = 2; - ret += "line: "; - break; - case TYPE_QUADTO: - len = 4; - ret += "quad: "; - break; - case TYPE_CUBICTO: - len = 6; - ret += "cubic: "; - break; - default: - len = 0; - } - last -= len; - ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len)) - + "\n"; - } - return ret; - } - } } --- old/src/java.desktop/share/classes/sun/java2d/marlin/DTransformingPathConsumer2D.java 2017-08-28 16:48:28.593663201 +0200 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DTransformingPathConsumer2D.java 2017-08-28 16:48:28.457663203 +0200 @@ -27,24 +27,58 @@ import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; +import sun.java2d.marlin.DHelpers.PolyStack; final class DTransformingPathConsumer2D { - DTransformingPathConsumer2D() { - // used by DRendererContext - } + private final DRendererContext rdrCtx; + + // recycled ClosedPathDetector instance from detectClosedPath() + private final ClosedPathDetector cpDetector; // recycled DPathConsumer2D instance from wrapPath2d() private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper(); + // recycled DPathConsumer2D instances from deltaTransformConsumer() + private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter(); + private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter(); + + // recycled DPathConsumer2D instances from inverseDeltaTransformConsumer() + private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter(); + private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter(); + + // recycled PathTracer instances from tracer...() methods + private final PathTracer tracerInput = new PathTracer("[Input]"); + private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector"); + private final PathTracer tracerStroker = new PathTracer("Stroker"); + + DTransformingPathConsumer2D(final DRendererContext rdrCtx) { + // used by RendererContext + this.rdrCtx = rdrCtx; + this.cpDetector = new ClosedPathDetector(rdrCtx); + } + DPathConsumer2D wrapPath2d(Path2D.Double p2d) { return wp_Path2DWrapper.init(p2d); } - // recycled DPathConsumer2D instances from deltaTransformConsumer() - private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter(); - private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter(); + DPathConsumer2D traceInput(DPathConsumer2D out) { + return tracerInput.init(out); + } + + DPathConsumer2D traceClosedPathDetector(DPathConsumer2D out) { + return tracerCPDetector.init(out); + } + + DPathConsumer2D traceStroker(DPathConsumer2D out) { + return tracerStroker.init(out); + } + + DPathConsumer2D detectClosedPath(DPathConsumer2D out) + { + return cpDetector.init(out); + } DPathConsumer2D deltaTransformConsumer(DPathConsumer2D out, AffineTransform at) @@ -61,16 +95,89 @@ if (mxx == 1.0d && myy == 1.0d) { return out; } else { + // Scale only + if (rdrCtx.doClip) { + // adjust clip rectangle (ymin, ymax, xmin, xmax): + adjustClipScale(rdrCtx.clipRect, mxx, myy); + } return dt_DeltaScaleFilter.init(out, mxx, myy); } } else { + if (rdrCtx.doClip) { + // adjust clip rectangle (ymin, ymax, xmin, xmax): + adjustClipInverseDelta(rdrCtx.clipRect, mxx, mxy, myx, myy); + } return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy); } } - // recycled DPathConsumer2D instances from inverseDeltaTransformConsumer() - private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter(); - private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter(); + private static void adjustClipOffset(final double[] clipRect) { + clipRect[0] += Renderer.RDR_OFFSET_Y; + clipRect[1] += Renderer.RDR_OFFSET_Y; + clipRect[2] += Renderer.RDR_OFFSET_X; + clipRect[3] += Renderer.RDR_OFFSET_X; + } + + private static void adjustClipScale(final double[] clipRect, + final double mxx, final double myy) + { + adjustClipOffset(clipRect); + + // Adjust the clipping rectangle (iv_DeltaScaleFilter): + clipRect[0] /= myy; + clipRect[1] /= myy; + clipRect[2] /= mxx; + clipRect[3] /= mxx; + } + + private static void adjustClipInverseDelta(final double[] clipRect, + final double mxx, final double mxy, + final double myx, final double myy) + { + adjustClipOffset(clipRect); + + // Adjust the clipping rectangle (iv_DeltaTransformFilter): + final double det = mxx * myy - mxy * myx; + final double imxx = myy / det; + final double imxy = -mxy / det; + final double imyx = -myx / det; + final double imyy = mxx / det; + + double xmin, xmax, ymin, ymax; + double x, y; + // xmin, ymin: + x = clipRect[2] * imxx + clipRect[0] * imxy; + y = clipRect[2] * imyx + clipRect[0] * imyy; + + xmin = xmax = x; + ymin = ymax = y; + + // xmax, ymin: + x = clipRect[3] * imxx + clipRect[0] * imxy; + y = clipRect[3] * imyx + clipRect[0] * imyy; + + if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } + if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } + + // xmin, ymax: + x = clipRect[2] * imxx + clipRect[1] * imxy; + y = clipRect[2] * imyx + clipRect[1] * imyy; + + if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } + if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } + + // xmax, ymax: + x = clipRect[3] * imxx + clipRect[1] * imxy; + y = clipRect[3] * imyx + clipRect[1] * imyy; + + if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } + if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } + + clipRect[0] = ymin; + clipRect[1] = ymax; + clipRect[2] = xmin; + clipRect[3] = xmax; + } DPathConsumer2D inverseDeltaTransformConsumer(DPathConsumer2D out, AffineTransform at) @@ -90,7 +197,7 @@ return iv_DeltaScaleFilter.init(out, 1.0d/mxx, 1.0d/myy); } } else { - double det = mxx * myy - mxy * myx; + final double det = mxx * myy - mxy * myx; return iv_DeltaTransformFilter.init(out, myy / det, -mxy / det, @@ -99,7 +206,6 @@ } } - static final class DeltaScaleFilter implements DPathConsumer2D { private DPathConsumer2D out; private double sx, sy; @@ -270,6 +376,154 @@ } @Override + public long getNativeConsumer() { + throw new InternalError("Not using a native peer"); + } + } + + static final class ClosedPathDetector implements DPathConsumer2D { + + private final DRendererContext rdrCtx; + private final PolyStack stack; + + private DPathConsumer2D out; + + ClosedPathDetector(final DRendererContext rdrCtx) { + this.rdrCtx = rdrCtx; + this.stack = (rdrCtx.stats != null) ? + new PolyStack(rdrCtx, + rdrCtx.stats.stat_cpd_polystack_types, + rdrCtx.stats.stat_cpd_polystack_curves, + rdrCtx.stats.hist_cpd_polystack_curves, + rdrCtx.stats.stat_array_cpd_polystack_curves, + rdrCtx.stats.stat_array_cpd_polystack_types) + : new PolyStack(rdrCtx); + } + + ClosedPathDetector init(DPathConsumer2D out) { + this.out = out; + return this; // fluent API + } + + /** + * Disposes this instance: + * clean up before reusing this instance + */ + void dispose() { + stack.dispose(); + } + + @Override + public void pathDone() { + // previous path is not closed: + finish(false); + out.pathDone(); + + // TODO: fix possible leak if exception happened + // Dispose this instance: + dispose(); + } + + @Override + public void closePath() { + // path is closed + finish(true); + out.closePath(); + } + + @Override + public void moveTo(double x0, double y0) { + // previous path is not closed: + finish(false); + out.moveTo(x0, y0); + } + + private void finish(final boolean closed) { + rdrCtx.closedPath = closed; + stack.pullAll(out); + } + + @Override + public void lineTo(double x1, double y1) { + stack.pushLine(x1, y1); + } + + @Override + public void curveTo(double x3, double y3, + double x2, double y2, + double x1, double y1) + { + stack.pushCubic(x1, y1, x2, y2, x3, y3); + } + + @Override + public void quadTo(double x2, double y2, double x1, double y1) { + stack.pushQuad(x1, y1, x2, y2); + } + + @Override + public long getNativeConsumer() { + throw new InternalError("Not using a native peer"); + } + } + + static final class PathTracer implements DPathConsumer2D { + private final String prefix; + private DPathConsumer2D out; + + PathTracer(String name) { + this.prefix = name + ": "; + } + + PathTracer init(DPathConsumer2D out) { + this.out = out; + return this; // fluent API + } + + @Override + public void moveTo(double x0, double y0) { + log("moveTo (" + x0 + ", " + y0 + ')'); + out.moveTo(x0, y0); + } + + @Override + public void lineTo(double x1, double y1) { + log("lineTo (" + x1 + ", " + y1 + ')'); + out.lineTo(x1, y1); + } + + @Override + public void curveTo(double x1, double y1, + double x2, double y2, + double x3, double y3) + { + log("curveTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ')'); + out.curveTo(x1, y1, x2, y2, x3, y3); + } + + @Override + public void quadTo(double x1, double y1, double x2, double y2) { + log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ')'); + out.quadTo(x1, y1, x2, y2); + } + + @Override + public void closePath() { + log("closePath"); + out.closePath(); + } + + @Override + public void pathDone() { + log("pathDone"); + out.pathDone(); + } + + private void log(final String message) { + System.out.println(prefix + message); + } + + @Override public long getNativeConsumer() { throw new InternalError("Not using a native peer"); } --- old/src/java.desktop/share/classes/sun/java2d/marlin/Dasher.java 2017-08-28 16:48:29.093663194 +0200 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/Dasher.java 2017-08-28 16:48:28.945663196 +0200 @@ -138,7 +138,7 @@ dashOn = !dashOn; } } - } else if (phase > 0) { + } else if (phase > 0.0f) { if (cycles >= MAX_CYCLES) { phase = 0.0f; } else { @@ -158,12 +158,13 @@ this.dash = dash; this.dashLen = dashLen; - this.startPhase = this.phase = phase; + this.phase = phase; + this.startPhase = phase; this.startDashOn = dashOn; this.startIdx = sidx; this.starting = true; - needsMoveTo = false; - firstSegidx = 0; + this.needsMoveTo = false; + this.firstSegidx = 0; this.recycleDashes = recycleDashes; @@ -202,8 +203,8 @@ } @Override - public void moveTo(float x0, float y0) { - if (firstSegidx > 0) { + public void moveTo(final float x0, final float y0) { + if (firstSegidx != 0) { out.moveTo(sx, sy); emitFirstSegments(); } @@ -211,8 +212,10 @@ this.idx = startIdx; this.dashOn = this.startDashOn; this.phase = this.startPhase; - this.sx = this.x0 = x0; - this.sy = this.y0 = y0; + this.sx = x0; + this.sy = y0; + this.x0 = x0; + this.y0 = y0; this.starting = true; } @@ -237,7 +240,7 @@ private void emitFirstSegments() { final float[] fSegBuf = firstSegmentsBuffer; - for (int i = 0; i < firstSegidx; ) { + for (int i = 0, len = firstSegidx; i < len; ) { int type = (int)fSegBuf[i]; emitSeg(fSegBuf, i + 1, type); i += (type - 1); @@ -252,48 +255,59 @@ private int firstSegidx; // precondition: pts must be in relative coordinates (relative to x0,y0) - private void goTo(float[] pts, int off, final int type) { - float x = pts[off + type - 4]; - float y = pts[off + type - 3]; - if (dashOn) { + private void goTo(final float[] pts, final int off, final int type, + final boolean on) + { + final int index = off + type; + final float x = pts[index - 4]; + final float y = pts[index - 3]; + + if (on) { if (starting) { - int len = type - 1; // - 2 + 1 - int segIdx = firstSegidx; - float[] buf = firstSegmentsBuffer; - if (segIdx + len > buf.length) { - if (DO_STATS) { - rdrCtx.stats.stat_array_dasher_firstSegmentsBuffer - .add(segIdx + len); - } - firstSegmentsBuffer = buf - = firstSegmentsBuffer_ref.widenArray(buf, segIdx, - segIdx + len); - } - buf[segIdx++] = type; - len--; - // small arraycopy (2, 4 or 6) but with offset: - System.arraycopy(pts, off, buf, segIdx, len); - segIdx += len; - firstSegidx = segIdx; + goTo_starting(pts, off, type); } else { if (needsMoveTo) { - out.moveTo(x0, y0); needsMoveTo = false; + out.moveTo(x0, y0); } emitSeg(pts, off, type); } } else { - starting = false; + if (starting) { + // low probability test (hotspot) + starting = false; + } needsMoveTo = true; } this.x0 = x; this.y0 = y; } + private void goTo_starting(final float[] pts, final int off, final int type) { + int len = type - 1; // - 2 + 1 + int segIdx = firstSegidx; + float[] buf = firstSegmentsBuffer; + + if (segIdx + len > buf.length) { + if (DO_STATS) { + rdrCtx.stats.stat_array_dasher_firstSegmentsBuffer + .add(segIdx + len); + } + firstSegmentsBuffer = buf + = firstSegmentsBuffer_ref.widenArray(buf, segIdx, + segIdx + len); + } + buf[segIdx++] = type; + len--; + // small arraycopy (2, 4 or 6) but with offset: + System.arraycopy(pts, off, buf, segIdx, len); + firstSegidx = segIdx + len; + } + @Override - public void lineTo(float x1, float y1) { - float dx = x1 - x0; - float dy = y1 - y0; + public void lineTo(final float x1, final float y1) { + final float dx = x1 - x0; + final float dy = y1 - y0; float len = dx*dx + dy*dy; if (len == 0.0f) { @@ -308,48 +322,61 @@ final float[] _curCurvepts = curCurvepts; final float[] _dash = dash; + final int _dashLen = this.dashLen; + + int _idx = idx; + boolean _dashOn = dashOn; + float _phase = phase; float leftInThisDashSegment; - float dashdx, dashdy, p; + float d, dashdx, dashdy, p; while (true) { - leftInThisDashSegment = _dash[idx] - phase; + d = _dash[_idx]; + leftInThisDashSegment = d - _phase; if (len <= leftInThisDashSegment) { _curCurvepts[0] = x1; _curCurvepts[1] = y1; - goTo(_curCurvepts, 0, 4); + + goTo(_curCurvepts, 0, 4, _dashOn); // Advance phase within current dash segment - phase += len; + _phase += len; + // TODO: compare float values using epsilon: if (len == leftInThisDashSegment) { - phase = 0.0f; - idx = (idx + 1) % dashLen; - dashOn = !dashOn; + _phase = 0.0f; + _idx = (_idx + 1) % _dashLen; + _dashOn = !_dashOn; } + + // Save local state: + idx = _idx; + dashOn = _dashOn; + phase = _phase; return; } - dashdx = _dash[idx] * cx; - dashdy = _dash[idx] * cy; + dashdx = d * cx; + dashdy = d * cy; - if (phase == 0.0f) { + if (_phase == 0.0f) { _curCurvepts[0] = x0 + dashdx; _curCurvepts[1] = y0 + dashdy; } else { - p = leftInThisDashSegment / _dash[idx]; + p = leftInThisDashSegment / d; _curCurvepts[0] = x0 + p * dashdx; _curCurvepts[1] = y0 + p * dashdy; } - goTo(_curCurvepts, 0, 4); + goTo(_curCurvepts, 0, 4, _dashOn); len -= leftInThisDashSegment; // Advance to next dash segment - idx = (idx + 1) % dashLen; - dashOn = !dashOn; - phase = 0.0f; + _idx = (_idx + 1) % _dashLen; + _dashOn = !_dashOn; + _phase = 0.0f; } } @@ -362,39 +389,55 @@ if (pointCurve(curCurvepts, type)) { return; } - li.initializeIterationOnCurve(curCurvepts, type); + final LengthIterator _li = li; + final float[] _curCurvepts = curCurvepts; + final float[] _dash = dash; + final int _dashLen = this.dashLen; + + _li.initializeIterationOnCurve(_curCurvepts, type); + + int _idx = idx; + boolean _dashOn = dashOn; + float _phase = phase; // initially the current curve is at curCurvepts[0...type] int curCurveoff = 0; float lastSplitT = 0.0f; float t; - float leftInThisDashSegment = dash[idx] - phase; + float leftInThisDashSegment = _dash[_idx] - _phase; - while ((t = li.next(leftInThisDashSegment)) < 1.0f) { + while ((t = _li.next(leftInThisDashSegment)) < 1.0f) { if (t != 0.0f) { Helpers.subdivideAt((t - lastSplitT) / (1.0f - lastSplitT), - curCurvepts, curCurveoff, - curCurvepts, 0, - curCurvepts, type, type); + _curCurvepts, curCurveoff, + _curCurvepts, 0, + _curCurvepts, type, type); lastSplitT = t; - goTo(curCurvepts, 2, type); + goTo(_curCurvepts, 2, type, _dashOn); curCurveoff = type; } // Advance to next dash segment - idx = (idx + 1) % dashLen; - dashOn = !dashOn; - phase = 0.0f; - leftInThisDashSegment = dash[idx]; - } - goTo(curCurvepts, curCurveoff+2, type); - phase += li.lastSegLen(); - if (phase >= dash[idx]) { - phase = 0.0f; - idx = (idx + 1) % dashLen; - dashOn = !dashOn; - } + _idx = (_idx + 1) % _dashLen; + _dashOn = !_dashOn; + _phase = 0.0f; + leftInThisDashSegment = _dash[_idx]; + } + + goTo(_curCurvepts, curCurveoff + 2, type, _dashOn); + + _phase += _li.lastSegLen(); + if (_phase >= _dash[_idx]) { + _phase = 0.0f; + _idx = (_idx + 1) % _dashLen; + _dashOn = !_dashOn; + } + // Save local state: + idx = _idx; + dashOn = _dashOn; + phase = _phase; + // reset LengthIterator: - li.reset(); + _li.reset(); } private static boolean pointCurve(float[] curve, int type) { @@ -420,7 +463,7 @@ // tree; however, the trees we are interested in have the property that // every non leaf node has exactly 2 children static final class LengthIterator { - private enum Side {LEFT, RIGHT}; + private enum Side {LEFT, RIGHT} // Holds the curves at various levels of the recursion. The root // (i.e. the original curve) is at recCurveStack[0] (but then it // gets subdivided, the left half is put at 1, so most of the time @@ -670,11 +713,12 @@ // this is a bit of a hack. It returns -1 if we're not on a leaf, and // the length of the leaf if we are on a leaf. private float onLeaf() { - float[] curve = recCurveStack[recLevel]; + final float[] curve = recCurveStack[recLevel]; + final int _curveType = curveType; float polyLen = 0.0f; float x0 = curve[0], y0 = curve[1]; - for (int i = 2; i < curveType; i += 2) { + for (int i = 2; i < _curveType; i += 2) { final float x1 = curve[i], y1 = curve[i+1]; final float len = Helpers.linelen(x0, y0, x1, y1); polyLen += len; @@ -684,8 +728,8 @@ } final float lineLen = Helpers.linelen(curve[0], curve[1], - curve[curveType-2], - curve[curveType-1]); + curve[_curveType-2], + curve[_curveType-1]); if ((polyLen - lineLen) < ERR || recLevel == REC_LIMIT) { return (polyLen + lineLen) / 2.0f; } @@ -694,9 +738,9 @@ } @Override - public void curveTo(float x1, float y1, - float x2, float y2, - float x3, float y3) + public void curveTo(final float x1, final float y1, + final float x2, final float y2, + final float x3, final float y3) { final float[] _curCurvepts = curCurvepts; _curCurvepts[0] = x0; _curCurvepts[1] = y0; @@ -707,7 +751,9 @@ } @Override - public void quadTo(float x1, float y1, float x2, float y2) { + public void quadTo(final float x1, final float y1, + final float x2, final float y2) + { final float[] _curCurvepts = curCurvepts; _curCurvepts[0] = x0; _curCurvepts[1] = y0; _curCurvepts[2] = x1; _curCurvepts[3] = y1; @@ -718,7 +764,7 @@ @Override public void closePath() { lineTo(sx, sy); - if (firstSegidx > 0) { + if (firstSegidx != 0) { if (!dashOn || needsMoveTo) { out.moveTo(sx, sy); } @@ -729,7 +775,7 @@ @Override public void pathDone() { - if (firstSegidx > 0) { + if (firstSegidx != 0) { out.moveTo(sx, sy); emitFirstSegments(); } --- old/src/java.desktop/share/classes/sun/java2d/marlin/Helpers.java 2017-08-28 16:48:29.541663188 +0200 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/Helpers.java 2017-08-28 16:48:29.401663190 +0200 @@ -30,6 +30,11 @@ import static java.lang.Math.sqrt; import static java.lang.Math.cbrt; import static java.lang.Math.acos; +import java.util.Arrays; +import sun.awt.geom.PathConsumer2D; +import static sun.java2d.marlin.MarlinConst.INITIAL_EDGES_COUNT; +import sun.java2d.marlin.stats.Histogram; +import sun.java2d.marlin.stats.StatLong; final class Helpers implements MarlinConst { @@ -176,15 +181,6 @@ return ret; } - static float polyLineLength(float[] poly, final int off, final int nCoords) { - assert nCoords % 2 == 0 && poly.length >= off + nCoords : ""; - float acc = 0.0f; - for (int i = off + 2; i < off + nCoords; i += 2) { - acc += linelen(poly[i], poly[i+1], poly[i-2], poly[i-1]); - } - return acc; - } - static float linelen(float x1, float y1, float x2, float y2) { final float dx = x2 - x1; final float dy = y2 - y1; @@ -438,4 +434,283 @@ return; } } + + // From sun.java2d.loops.GeneralRenderer: + + static final int OUTCODE_TOP = 1; + static final int OUTCODE_BOTTOM = 2; + static final int OUTCODE_LEFT = 4; + static final int OUTCODE_RIGHT = 8; + + static int outcode(final float x, final float y, + final float[] clipRect) + { + int code; + if (y < clipRect[0]) { + code = OUTCODE_TOP; + } else if (y >= clipRect[1]) { + code = OUTCODE_BOTTOM; + } else { + code = 0; + } + if (x < clipRect[2]) { + code |= OUTCODE_LEFT; + } else if (x >= clipRect[3]) { + code |= OUTCODE_RIGHT; + } + return code; + } + + // a stack of polynomial curves where each curve shares endpoints with + // adjacent ones. + static final class PolyStack { + private static final byte TYPE_LINETO = (byte) 0; + private static final byte TYPE_QUADTO = (byte) 1; + private static final byte TYPE_CUBICTO = (byte) 2; + + // curves capacity = edges count (8192) = edges x 2 (coords) + private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1; + + // types capacity = edges count (4096) + private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT; + + float[] curves; + int end; + byte[] curveTypes; + int numCurves; + + // curves ref (dirty) + final FloatArrayCache.Reference curves_ref; + // curveTypes ref (dirty) + final ByteArrayCache.Reference curveTypes_ref; + + // used marks (stats only) + int curveTypesUseMark; + int curvesUseMark; + + private final StatLong stat_polystack_types; + private final StatLong stat_polystack_curves; + private final Histogram hist_polystack_curves; + private final StatLong stat_array_polystack_curves; + private final StatLong stat_array_polystack_curveTypes; + + PolyStack(final RendererContext rdrCtx) { + this(rdrCtx, null, null, null, null, null); + } + + PolyStack(final RendererContext rdrCtx, + final StatLong stat_polystack_types, + final StatLong stat_polystack_curves, + final Histogram hist_polystack_curves, + final StatLong stat_array_polystack_curves, + final StatLong stat_array_polystack_curveTypes) + { + curves_ref = rdrCtx.newDirtyFloatArrayRef(INITIAL_CURVES_COUNT); // 32K + curves = curves_ref.initial; + + curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K + curveTypes = curveTypes_ref.initial; + numCurves = 0; + end = 0; + + if (DO_STATS) { + curveTypesUseMark = 0; + curvesUseMark = 0; + } + this.stat_polystack_types = stat_polystack_types; + this.stat_polystack_curves = stat_polystack_curves; + this.hist_polystack_curves = hist_polystack_curves; + this.stat_array_polystack_curves = stat_array_polystack_curves; + this.stat_array_polystack_curveTypes = stat_array_polystack_curveTypes; + } + + /** + * Disposes this PolyStack: + * clean up before reusing this instance + */ + void dispose() { + end = 0; + numCurves = 0; + + if (DO_STATS) { + stat_polystack_types.add(curveTypesUseMark); + stat_polystack_curves.add(curvesUseMark); + hist_polystack_curves.add(curvesUseMark); + + // reset marks + curveTypesUseMark = 0; + curvesUseMark = 0; + } + + // Return arrays: + // curves and curveTypes are kept dirty + curves = curves_ref.putArray(curves); + curveTypes = curveTypes_ref.putArray(curveTypes); + } + + private void ensureSpace(final int n) { + // use substraction to avoid integer overflow: + if (curves.length - end < n) { + if (DO_STATS) { + stat_array_polystack_curves.add(end + n); + } + curves = curves_ref.widenArray(curves, end, end + n); + } + if (curveTypes.length <= numCurves) { + if (DO_STATS) { + stat_array_polystack_curveTypes.add(numCurves + 1); + } + curveTypes = curveTypes_ref.widenArray(curveTypes, + numCurves, + numCurves + 1); + } + } + + void pushCubic(float x0, float y0, + float x1, float y1, + float x2, float y2) + { + ensureSpace(6); + curveTypes[numCurves++] = TYPE_CUBICTO; + // we reverse the coordinate order to make popping easier + final float[] _curves = curves; + int e = end; + _curves[e++] = x2; _curves[e++] = y2; + _curves[e++] = x1; _curves[e++] = y1; + _curves[e++] = x0; _curves[e++] = y0; + end = e; + } + + void pushQuad(float x0, float y0, + float x1, float y1) + { + ensureSpace(4); + curveTypes[numCurves++] = TYPE_QUADTO; + final float[] _curves = curves; + int e = end; + _curves[e++] = x1; _curves[e++] = y1; + _curves[e++] = x0; _curves[e++] = y0; + end = e; + } + + void pushLine(float x, float y) { + ensureSpace(2); + curveTypes[numCurves++] = TYPE_LINETO; + curves[end++] = x; curves[end++] = y; + } + + void pullAll(final PathConsumer2D io) { + final int nc = numCurves; + if (nc == 0) { + return; + } + if (DO_STATS) { + // update used marks: + if (numCurves > curveTypesUseMark) { + curveTypesUseMark = numCurves; + } + if (end > curvesUseMark) { + curvesUseMark = end; + } + } + final byte[] _curveTypes = curveTypes; + final float[] _curves = curves; + int e = 0; + + for (int i = 0; i < nc; i++) { + switch(_curveTypes[i]) { + case TYPE_LINETO: + io.lineTo(_curves[e], _curves[e+1]); + e += 2; + continue; + case TYPE_QUADTO: + io.quadTo(_curves[e+0], _curves[e+1], + _curves[e+2], _curves[e+3]); + e += 4; + continue; + case TYPE_CUBICTO: + io.curveTo(_curves[e+0], _curves[e+1], + _curves[e+2], _curves[e+3], + _curves[e+4], _curves[e+5]); + e += 6; + continue; + default: + } + } + numCurves = 0; + end = 0; + } + + void popAll(final PathConsumer2D io) { + int nc = numCurves; + if (nc == 0) { + return; + } + if (DO_STATS) { + // update used marks: + if (numCurves > curveTypesUseMark) { + curveTypesUseMark = numCurves; + } + if (end > curvesUseMark) { + curvesUseMark = end; + } + } + final byte[] _curveTypes = curveTypes; + final float[] _curves = curves; + int e = end; + + while (nc != 0) { + switch(_curveTypes[--nc]) { + case TYPE_LINETO: + e -= 2; + io.lineTo(_curves[e], _curves[e+1]); + continue; + case TYPE_QUADTO: + e -= 4; + io.quadTo(_curves[e+0], _curves[e+1], + _curves[e+2], _curves[e+3]); + continue; + case TYPE_CUBICTO: + e -= 6; + io.curveTo(_curves[e+0], _curves[e+1], + _curves[e+2], _curves[e+3], + _curves[e+4], _curves[e+5]); + continue; + default: + } + } + numCurves = 0; + end = 0; + } + + @Override + public String toString() { + String ret = ""; + int nc = numCurves; + int last = end; + int len; + while (nc != 0) { + switch(curveTypes[--nc]) { + case TYPE_LINETO: + len = 2; + ret += "line: "; + break; + case TYPE_QUADTO: + len = 4; + ret += "quad: "; + break; + case TYPE_CUBICTO: + len = 6; + ret += "cubic: "; + break; + default: + len = 0; + } + last -= len; + ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len)) + + "\n"; + } + return ret; + } + } } --- old/src/java.desktop/share/classes/sun/java2d/marlin/MarlinProperties.java 2017-08-28 16:48:29.965663182 +0200 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/MarlinProperties.java 2017-08-28 16:48:29.821663184 +0200 @@ -145,6 +145,10 @@ return getBoolean("sun.java2d.renderer.useSimplifier", "false"); } + public static boolean isDoClip() { + return getBoolean("sun.java2d.renderer.clip", "true"); + } + // debugging parameters public static boolean isDoStats() { --- old/src/java.desktop/share/classes/sun/java2d/marlin/MarlinRenderingEngine.java 2017-08-28 16:48:30.473663175 +0200 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/MarlinRenderingEngine.java 2017-08-28 16:48:30.337663177 +0200 @@ -85,6 +85,9 @@ static final float UPPER_BND = Float.MAX_VALUE / 2.0f; static final float LOWER_BND = -UPPER_BND; + static final boolean DO_CLIP = MarlinProperties.isDoClip(); + static final boolean DO_TRACE = false; + /** * Public constructor */ @@ -324,6 +327,7 @@ int dashLen = -1; boolean recycleDashes = false; + float scale = 1.0f; if (at != null && !at.isIdentity()) { final double a = at.getScaleX(); @@ -356,7 +360,7 @@ // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we // leave a bit of room for error. if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) { - final float scale = (float) Math.sqrt(a*a + c*c); + scale = (float) Math.sqrt(a*a + c*c); if (dashes != null) { recycleDashes = true; @@ -394,16 +398,24 @@ at = null; } + final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; + + if (DO_TRACE) { + // trace Stroker: + pc2d = transformerPC2D.traceStroker(pc2d); + } + if (USE_SIMPLIFIER) { // Use simplifier after stroker before Renderer // to remove collinear segments (notably due to cap square) pc2d = rdrCtx.simplifier.init(pc2d); } - final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; + // deltaTransformConsumer may adjust the clip rectangle: pc2d = transformerPC2D.deltaTransformConsumer(pc2d, strokerat); - pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit); + // stroker will adjust the clip rectangle (width / miter limit): + pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit, scale); if (dashes != null) { if (!recycleDashes) { @@ -411,9 +423,22 @@ } pc2d = rdrCtx.dasher.init(pc2d, dashes, dashLen, dashphase, recycleDashes); + } else if (rdrCtx.doClip && (caps != Stroker.CAP_BUTT)) { + if (DO_TRACE) { + pc2d = transformerPC2D.traceClosedPathDetector(pc2d); + } + + // If no dash and clip is enabled: + // detect closedPaths (polygons) for caps + pc2d = transformerPC2D.detectClosedPath(pc2d); } pc2d = transformerPC2D.inverseDeltaTransformConsumer(pc2d, strokerat); + if (DO_TRACE) { + // trace Input: + pc2d = transformerPC2D.traceInput(pc2d); + } + final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx, src.getPathIterator(at)); @@ -803,6 +828,18 @@ clip.getWidth(), clip.getHeight(), PathIterator.WIND_NON_ZERO); + if (DO_CLIP) { + // Define the initial clip bounds: + final float[] clipRect = rdrCtx.clipRect; + clipRect[0] = clip.getLoY(); + clipRect[1] = clip.getLoY() + clip.getHeight(); + clipRect[2] = clip.getLoX(); + clipRect[3] = clip.getLoX() + clip.getWidth(); + + // Enable clipping: + rdrCtx.doClip = true; + } + strokeTo(rdrCtx, s, _at, bs, thin, norm, true, r); } if (r.endRendering()) { @@ -860,8 +897,8 @@ final RendererContext rdrCtx = getRendererContext(); try { r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(), - clip.getWidth(), clip.getHeight(), - Renderer.WIND_EVEN_ODD); + clip.getWidth(), clip.getHeight(), + Renderer.WIND_EVEN_ODD); r.moveTo((float) x, (float) y); r.lineTo((float) (x+dx1), (float) (y+dy1)); @@ -1045,6 +1082,8 @@ // optimisation parameters logInfo("sun.java2d.renderer.useSimplifier = " + MarlinConst.USE_SIMPLIFIER); + logInfo("sun.java2d.renderer.clip = " + + MarlinProperties.isDoClip()); // debugging parameters logInfo("sun.java2d.renderer.doStats = " --- old/src/java.desktop/share/classes/sun/java2d/marlin/Renderer.java 2017-08-28 16:48:31.057663167 +0200 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/Renderer.java 2017-08-28 16:48:30.841663170 +0200 @@ -47,6 +47,9 @@ static final int SUBPIXEL_MASK_X = SUBPIXEL_POSITIONS_X - 1; static final int SUBPIXEL_MASK_Y = SUBPIXEL_POSITIONS_Y - 1; + static final float RDR_OFFSET_X = 0.501f / SUBPIXEL_SCALE_X; + static final float RDR_OFFSET_Y = 0.501f / SUBPIXEL_SCALE_Y; + // number of subpixels corresponding to a tile line private static final int SUBPIXEL_TILE = TILE_H << SUBPIXEL_LG_POSITIONS_Y; @@ -672,7 +675,7 @@ } @Override - public void moveTo(float pix_x0, float pix_y0) { + public void moveTo(final float pix_x0, final float pix_y0) { closePath(); final float sx = tosubpixx(pix_x0); final float sy = tosubpixy(pix_y0); @@ -683,7 +686,7 @@ } @Override - public void lineTo(float pix_x1, float pix_y1) { + public void lineTo(final float pix_x1, final float pix_y1) { final float x1 = tosubpixx(pix_x1); final float y1 = tosubpixy(pix_y1); addLine(x0, y0, x1, y1); @@ -692,24 +695,26 @@ } @Override - public void curveTo(float x1, float y1, - float x2, float y2, - float x3, float y3) + public void curveTo(final float pix_x1, final float pix_y1, + final float pix_x2, final float pix_y2, + final float pix_x3, final float pix_y3) { - final float xe = tosubpixx(x3); - final float ye = tosubpixy(y3); - curve.set(x0, y0, tosubpixx(x1), tosubpixy(y1), - tosubpixx(x2), tosubpixy(y2), xe, ye); + final float xe = tosubpixx(pix_x3); + final float ye = tosubpixy(pix_y3); + curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1), + tosubpixx(pix_x2), tosubpixy(pix_y2), xe, ye); curveBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); x0 = xe; y0 = ye; } @Override - public void quadTo(float x1, float y1, float x2, float y2) { - final float xe = tosubpixx(x2); - final float ye = tosubpixy(y2); - curve.set(x0, y0, tosubpixx(x1), tosubpixy(y1), xe, ye); + public void quadTo(final float pix_x1, final float pix_y1, + final float pix_x2, final float pix_y2) + { + final float xe = tosubpixx(pix_x2); + final float ye = tosubpixy(pix_y2); + curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1), xe, ye); quadBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); x0 = xe; y0 = ye; @@ -717,9 +722,11 @@ @Override public void closePath() { - addLine(x0, y0, sx0, sy0); - x0 = sx0; - y0 = sy0; + if (x0 != sx0 || y0 != sy0) { + addLine(x0, y0, sx0, sy0); + x0 = sx0; + y0 = sy0; + } } @Override --- old/src/java.desktop/share/classes/sun/java2d/marlin/RendererContext.java 2017-08-28 16:48:31.629663159 +0200 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/RendererContext.java 2017-08-28 16:48:31.465663162 +0200 @@ -25,6 +25,7 @@ package sun.java2d.marlin; +import java.awt.Rectangle; import java.awt.geom.Path2D; import java.lang.ref.WeakReference; import java.util.concurrent.atomic.AtomicInteger; @@ -75,6 +76,12 @@ final MarlinCache cache; // flag indicating the shape is stroked (1) or filled (0) int stroking = 0; + // flag indicating to clip the shape + boolean doClip = false; + // flag indicating if the path is closed or not (in advance) to handle properly caps + boolean closedPath = false; + // clip rectangle (ymin, ymax, xmin, xmax): + final float[] clipRect = new float[4]; // Array caches: /* clean int[] cache (zero-filled) = 5 refs */ @@ -116,7 +123,7 @@ nPQPathIterator = new NormalizingPathIterator.NearestPixelQuarter(float6); // MarlinRenderingEngine.TransformingPathConsumer2D - transformerPC2D = new TransformingPathConsumer2D(); + transformerPC2D = new TransformingPathConsumer2D(this); // Renderer: cache = new MarlinCache(this); @@ -138,7 +145,10 @@ } stats.totalOffHeap = 0L; } - stroking = 0; + stroking = 0; + doClip = false; + closedPath = false; + // if context is maked as DIRTY: if (dirty) { // may happen if an exception if thrown in the pipeline processing: --- old/src/java.desktop/share/classes/sun/java2d/marlin/RendererStats.java 2017-08-28 16:48:32.149663152 +0200 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/RendererStats.java 2017-08-28 16:48:31.949663155 +0200 @@ -66,10 +66,6 @@ = new StatLong("cache.rowAAChunk"); final StatLong stat_cache_tiles = new StatLong("cache.tiles"); - final StatLong stat_rdr_poly_stack_curves - = new StatLong("renderer.poly.stack.curves"); - final StatLong stat_rdr_poly_stack_types - = new StatLong("renderer.poly.stack.types"); final StatLong stat_rdr_addLine = new StatLong("renderer.addLine"); final StatLong stat_rdr_addLine_skip @@ -106,15 +102,19 @@ = new StatLong("renderer.crossings.bsearch"); final StatLong stat_rdr_crossings_msorts = new StatLong("renderer.crossings.msorts"); + final StatLong stat_str_polystack_curves + = new StatLong("stroker.polystack.curves"); + final StatLong stat_str_polystack_types + = new StatLong("stroker.polystack.types"); + final StatLong stat_cpd_polystack_curves + = new StatLong("closedPathDetector.polystack.curves"); + final StatLong stat_cpd_polystack_types + = new StatLong("closedPathDetector.polystack.types"); // growable arrays final StatLong stat_array_dasher_dasher = new StatLong("array.dasher.dasher.d_float"); final StatLong stat_array_dasher_firstSegmentsBuffer = new StatLong("array.dasher.firstSegmentsBuffer.d_float"); - final StatLong stat_array_stroker_polystack_curves - = new StatLong("array.stroker.polystack.curves.d_float"); - final StatLong stat_array_stroker_polystack_curveTypes - = new StatLong("array.stroker.polystack.curveTypes.d_byte"); final StatLong stat_array_marlincache_rowAAChunk = new StatLong("array.marlincache.rowAAChunk.resize"); final StatLong stat_array_marlincache_touchedTile @@ -133,11 +133,17 @@ = new StatLong("array.renderer.edgePtrs.int"); final StatLong stat_array_renderer_aux_edgePtrs = new StatLong("array.renderer.aux_edgePtrs.int"); + final StatLong stat_array_str_polystack_curves + = new StatLong("array.stroker.polystack.curves.d_float"); + final StatLong stat_array_str_polystack_types + = new StatLong("array.stroker.polystack.curveTypes.d_byte"); + final StatLong stat_array_cpd_polystack_curves + = new StatLong("array.closedPathDetector.polystack.curves.d_float"); + final StatLong stat_array_cpd_polystack_types + = new StatLong("array.closedPathDetector.polystack.curveTypes.d_byte"); // histograms final Histogram hist_rdr_edges_count = new Histogram("renderer.edges.count"); - final Histogram hist_rdr_poly_stack_curves - = new Histogram("renderer.polystack.curves"); final Histogram hist_rdr_crossings = new Histogram("renderer.crossings"); final Histogram hist_rdr_crossings_ratio @@ -148,6 +154,8 @@ = new Histogram("renderer.crossings.msorts"); final Histogram hist_rdr_crossings_msorts_adds = new Histogram("renderer.crossings.msorts.adds"); + final Histogram hist_str_polystack_curves + = new Histogram("stroker.polystack.curves"); final Histogram hist_tile_generator_alpha = new Histogram("tile_generator.alpha"); final Histogram hist_tile_generator_encoding @@ -158,13 +166,13 @@ = new Histogram("tile_generator.encoding.ratio"); final Histogram hist_tile_generator_encoding_runLen = new Histogram("tile_generator.encoding.runLen"); + final Histogram hist_cpd_polystack_curves + = new Histogram("closedPathDetector.polystack.curves"); // all stats final StatLong[] statistics = new StatLong[]{ stat_cache_rowAA, stat_cache_rowAAChunk, stat_cache_tiles, - stat_rdr_poly_stack_types, - stat_rdr_poly_stack_curves, stat_rdr_addLine, stat_rdr_addLine_skip, stat_rdr_curveBreak, @@ -183,8 +191,11 @@ stat_rdr_crossings_sorts, stat_rdr_crossings_bsearch, stat_rdr_crossings_msorts, + stat_str_polystack_types, + stat_str_polystack_curves, + stat_cpd_polystack_curves, + stat_cpd_polystack_types, hist_rdr_edges_count, - hist_rdr_poly_stack_curves, hist_rdr_crossings, hist_rdr_crossings_ratio, hist_rdr_crossings_adds, @@ -195,10 +206,10 @@ hist_tile_generator_encoding_dist, hist_tile_generator_encoding_ratio, hist_tile_generator_encoding_runLen, + hist_str_polystack_curves, + hist_cpd_polystack_curves, stat_array_dasher_dasher, stat_array_dasher_firstSegmentsBuffer, - stat_array_stroker_polystack_curves, - stat_array_stroker_polystack_curveTypes, stat_array_marlincache_rowAAChunk, stat_array_marlincache_touchedTile, stat_array_renderer_alphaline, @@ -207,7 +218,11 @@ stat_array_renderer_edgeBuckets, stat_array_renderer_edgeBucketCounts, stat_array_renderer_edgePtrs, - stat_array_renderer_aux_edgePtrs + stat_array_renderer_aux_edgePtrs, + stat_array_str_polystack_curves, + stat_array_str_polystack_types, + stat_array_cpd_polystack_curves, + stat_array_cpd_polystack_types }; // monitors final Monitor mon_pre_getAATileGenerator --- old/src/java.desktop/share/classes/sun/java2d/marlin/Stroker.java 2017-08-28 16:48:32.685663145 +0200 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/Stroker.java 2017-08-28 16:48:32.525663147 +0200 @@ -28,6 +28,7 @@ import java.util.Arrays; import sun.awt.geom.PathConsumer2D; +import sun.java2d.marlin.Helpers.PolyStack; // TODO: some of the arithmetic here is too verbose and prone to hard to // debug typos. We should consider making a small Point/Vector class that @@ -73,7 +74,11 @@ // it to floating point, so that's why the divisions by 2^16 are there. private static final float ROUND_JOIN_THRESHOLD = 1000.0f/65536.0f; - private static final float C = 0.5522847498307933f; + // kappa = (4/3) * (SQRT(2) - 1) + private static final float C = (float)(4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d); + + // SQRT(2) + private static final float SQRT_2 = (float)Math.sqrt(2.0d); private static final int MAX_N_CURVES = 11; @@ -120,6 +125,20 @@ // dirty curve final Curve curve; + // Bounds of the drawing region, at pixel precision. + private float[] clipRect; + + // the outcode of the current point + private int cOutCode = 0; + + // the outcode of the starting point + private int sOutCode = 0; + + // flag indicating if the path is opened (clipped) + private boolean opened = false; + // flag indicating if the starting point's cap is done + private boolean capStart = false; + /** * Constructs a Stroker. * @param rdrCtx per-thread renderer context @@ -127,7 +146,15 @@ Stroker(final RendererContext rdrCtx) { this.rdrCtx = rdrCtx; - this.reverse = new PolyStack(rdrCtx); + this.reverse = (rdrCtx.stats != null) ? + new PolyStack(rdrCtx, + rdrCtx.stats.stat_str_polystack_types, + rdrCtx.stats.stat_str_polystack_curves, + rdrCtx.stats.hist_str_polystack_curves, + rdrCtx.stats.stat_array_str_polystack_curves, + rdrCtx.stats.stat_array_str_polystack_types) + : new PolyStack(rdrCtx); + this.curve = rdrCtx.curve; } @@ -143,13 +170,15 @@ * JOIN_MITER, JOIN_ROUND or * JOIN_BEVEL. * @param miterLimit the desired miter limit + * @param scale scaling factor applied to clip boundaries * @return this instance */ - Stroker init(PathConsumer2D pc2d, - float lineWidth, - int capStyle, - int joinStyle, - float miterLimit) + Stroker init(final PathConsumer2D pc2d, + final float lineWidth, + final int capStyle, + final int joinStyle, + final float miterLimit, + final float scale) { this.out = pc2d; @@ -158,13 +187,41 @@ this.capStyle = capStyle; this.joinStyle = joinStyle; - float limit = miterLimit * lineWidth2; + final float limit = miterLimit * lineWidth2; this.miterLimitSq = limit * limit; this.prev = CLOSE; rdrCtx.stroking = 1; + if (rdrCtx.doClip) { + // Adjust the clipping rectangle with the stroker margin (miter limit, width) + float rdrOffX = 0.0f, rdrOffY = 0.0f; + float margin = lineWidth2; + + if (capStyle == CAP_SQUARE) { + margin *= SQRT_2; + } + if ((joinStyle == JOIN_MITER) && (margin < limit)) { + margin = limit; + } + if (scale != 1.0f) { + margin *= scale; + rdrOffX = scale * Renderer.RDR_OFFSET_X; + rdrOffY = scale * Renderer.RDR_OFFSET_Y; + } + + // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY + // adjust clip rectangle (ymin, ymax, xmin, xmax): + final float[] _clipRect = rdrCtx.clipRect; + _clipRect[0] -= margin - rdrOffY; + _clipRect[1] += margin + rdrOffY; + _clipRect[2] -= margin - rdrOffX; + _clipRect[3] += margin + rdrOffX; + this.clipRect = _clipRect; + } else { + this.clipRect = null; + } return this; // fluent API } @@ -175,6 +232,9 @@ void dispose() { reverse.dispose(); + opened = false; + capStart = false; + if (DO_CLEAN_DIRTY) { // Force zero-fill dirty arrays: Arrays.fill(offset0, 0.0f); @@ -445,19 +505,62 @@ } @Override - public void moveTo(float x0, float y0) { - if (prev == DRAWING_OP_TO) { - finish(); + public void moveTo(final float x0, final float y0) { + moveTo(x0, y0, cOutCode); + // update starting point: + this.sx0 = x0; + this.sy0 = y0; + this.sdx = 1.0f; + this.sdy = 0.0f; + this.opened = false; + this.capStart = false; + + if (clipRect != null) { + final int outcode = Helpers.outcode(x0, y0, clipRect); + this.cOutCode = outcode; + this.sOutCode = outcode; + } + } + + private void moveTo(final float x0, final float y0, + final int outcode) + { + if (prev == MOVE_TO) { + this.cx0 = x0; + this.cy0 = y0; + } else { + if (prev == DRAWING_OP_TO) { + finish(outcode); + } + this.prev = MOVE_TO; + this.cx0 = x0; + this.cy0 = y0; + this.cdx = 1.0f; + this.cdy = 0.0f; } - this.sx0 = this.cx0 = x0; - this.sy0 = this.cy0 = y0; - this.cdx = this.sdx = 1.0f; - this.cdy = this.sdy = 0.0f; - this.prev = MOVE_TO; } @Override - public void lineTo(float x1, float y1) { + public void lineTo(final float x1, final float y1) { + lineTo(x1, y1, false); + } + + private void lineTo(final float x1, final float y1, + final boolean force) + { + final int outcode0 = this.cOutCode; + if (!force && clipRect != null) { + final int outcode1 = Helpers.outcode(x1, y1, clipRect); + this.cOutCode = outcode1; + + // basic rejection criteria + if ((outcode0 & outcode1) != 0) { + moveTo(x1, y1, outcode0); + opened = true; + return; + } + } + float dx = x1 - cx0; float dy = y1 - cy0; if (dx == 0.0f && dy == 0.0f) { @@ -467,7 +570,7 @@ final float mx = offset0[0]; final float my = offset0[1]; - drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my); + drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0); emitLineTo(cx0 + mx, cy0 + my); emitLineTo( x1 + mx, y1 + my); @@ -475,43 +578,64 @@ emitLineToRev(cx0 - mx, cy0 - my); emitLineToRev( x1 - mx, y1 - my); - this.cmx = mx; - this.cmy = my; - this.cdx = dx; - this.cdy = dy; + this.prev = DRAWING_OP_TO; this.cx0 = x1; this.cy0 = y1; - this.prev = DRAWING_OP_TO; + this.cdx = dx; + this.cdy = dy; + this.cmx = mx; + this.cmy = my; } @Override public void closePath() { - if (prev != DRAWING_OP_TO) { + // distinguish empty path at all vs opened path ? + if (prev != DRAWING_OP_TO && !opened) { if (prev == CLOSE) { return; } emitMoveTo(cx0, cy0 - lineWidth2); - this.cmx = this.smx = 0.0f; - this.cmy = this.smy = -lineWidth2; - this.cdx = this.sdx = 1.0f; - this.cdy = this.sdy = 0.0f; - finish(); + + this.sdx = 1.0f; + this.sdy = 0.0f; + this.cdx = 1.0f; + this.cdy = 0.0f; + + this.smx = 0.0f; + this.smy = -lineWidth2; + this.cmx = 0.0f; + this.cmy = -lineWidth2; + + finish(cOutCode); return; } - if (cx0 != sx0 || cy0 != sy0) { - lineTo(sx0, sy0); - } + if (sOutCode == 0) { + if (cx0 != sx0 || cy0 != sy0) { + lineTo(sx0, sy0, true); + } - drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy); + drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, cOutCode); - emitLineTo(sx0 + smx, sy0 + smy); + emitLineTo(sx0 + smx, sy0 + smy); - emitMoveTo(sx0 - smx, sy0 - smy); + if (opened) { + emitLineTo(sx0 - smx, sy0 - smy); + } else { + emitMoveTo(sx0 - smx, sy0 - smy); + } + } + // Ignore caps like finish(false) emitReverse(); this.prev = CLOSE; - emitClose(); + + if (opened) { + // do not emit close + opened = false; + } else { + emitClose(); + } } private void emitReverse() { @@ -521,7 +645,7 @@ @Override public void pathDone() { if (prev == DRAWING_OP_TO) { - finish(); + finish(cOutCode); } out.pathDone(); @@ -534,23 +658,39 @@ dispose(); } - private void finish() { - if (capStyle == CAP_ROUND) { - drawRoundCap(cx0, cy0, cmx, cmy); - } else if (capStyle == CAP_SQUARE) { - emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy); - emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy); - } + private void finish(final int outcode) { + // Problem: impossible to guess if the path will be closed in advance + // i.e. if caps must be drawn or not ? + // Solution: use the ClosedPathDetector before Stroker to determine + // if the path is a closed path or not + if (!rdrCtx.closedPath) { + if (outcode == 0) { + // current point = end's cap: + if (capStyle == CAP_ROUND) { + drawRoundCap(cx0, cy0, cmx, cmy); + } else if (capStyle == CAP_SQUARE) { + emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy); + emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy); + } + } + emitReverse(); - emitReverse(); + if (!capStart) { + capStart = true; - if (capStyle == CAP_ROUND) { - drawRoundCap(sx0, sy0, -smx, -smy); - } else if (capStyle == CAP_SQUARE) { - emitLineTo(sx0 + smy - smx, sy0 - smx - smy); - emitLineTo(sx0 + smy + smx, sy0 - smx + smy); + if (sOutCode == 0) { + // starting point = initial cap: + if (capStyle == CAP_ROUND) { + drawRoundCap(sx0, sy0, -smx, -smy); + } else if (capStyle == CAP_SQUARE) { + emitLineTo(sx0 + smy - smx, sy0 - smx - smy); + emitLineTo(sx0 + smy + smx, sy0 - smx + smy); + } + } + } + } else { + emitReverse(); } - emitClose(); } @@ -622,23 +762,28 @@ float x0, float y0, float dx, float dy, float omx, float omy, - float mx, float my) + float mx, float my, + final int outcode) { if (prev != DRAWING_OP_TO) { emitMoveTo(x0 + mx, y0 + my); - this.sdx = dx; - this.sdy = dy; - this.smx = mx; - this.smy = my; + if (!opened) { + this.sdx = dx; + this.sdy = dy; + this.smx = mx; + this.smy = my; + } } else { - boolean cw = isCW(pdx, pdy, dx, dy); - if (joinStyle == JOIN_MITER) { - drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw); - } else if (joinStyle == JOIN_ROUND) { - drawRoundJoin(x0, y0, - omx, omy, - mx, my, cw, - ROUND_JOIN_THRESHOLD); + final boolean cw = isCW(pdx, pdy, dx, dy); + if (outcode == 0) { + if (joinStyle == JOIN_MITER) { + drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw); + } else if (joinStyle == JOIN_ROUND) { + drawRoundJoin(x0, y0, + omx, omy, + mx, my, cw, + ROUND_JOIN_THRESHOLD); + } } emitLineTo(x0, y0, !cw); } @@ -943,10 +1088,29 @@ return ret; } - @Override public void curveTo(float x1, float y1, - float x2, float y2, - float x3, float y3) - { + @Override + public void curveTo(final float x1, final float y1, + final float x2, final float y2, + final float x3, final float y3) + { + final int outcode0 = this.cOutCode; + if (clipRect != null) { + final int outcode3 = Helpers.outcode(x3, y3, clipRect); + this.cOutCode = outcode3; + + if (outcode3 != 0) { + final int outcode1 = Helpers.outcode(x1, y1, clipRect); + final int outcode2 = Helpers.outcode(x2, y2, clipRect); + + // basic rejection criteria + if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) { + moveTo(x3, y3, outcode0); + opened = true; + return; + } + } + } + final float[] mid = middle; mid[0] = cx0; mid[1] = cy0; @@ -955,7 +1119,7 @@ mid[6] = x3; mid[7] = y3; // need these so we can update the state at the end of this method - final float xf = mid[6], yf = mid[7]; + final float xf = x3, yf = y3; float dxs = mid[2] - mid[0]; float dys = mid[3] - mid[1]; float dxf = mid[6] - mid[4]; @@ -981,6 +1145,10 @@ } if (dxs == 0.0f && dys == 0.0f) { // this happens if the "curve" is just a point + // fix outcode0 for lineTo() call: + if (clipRect != null) { + this.cOutCode = outcode0; + } lineTo(mid[0], mid[1]); return; } @@ -999,7 +1167,7 @@ } computeOffset(dxs, dys, lineWidth2, offset0); - drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]); + drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2); @@ -1034,16 +1202,36 @@ emitLineToRev(r[kind - 2], r[kind - 1]); } - this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; - this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; - this.cdx = dxf; - this.cdy = dyf; + this.prev = DRAWING_OP_TO; this.cx0 = xf; this.cy0 = yf; - this.prev = DRAWING_OP_TO; + this.cdx = dxf; + this.cdy = dyf; + this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; + this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; } - @Override public void quadTo(float x1, float y1, float x2, float y2) { + @Override + public void quadTo(final float x1, final float y1, + final float x2, final float y2) + { + final int outcode0 = this.cOutCode; + if (clipRect != null) { + final int outcode2 = Helpers.outcode(x2, y2, clipRect); + this.cOutCode = outcode2; + + if (outcode2 != 0) { + final int outcode1 = Helpers.outcode(x1, y1, clipRect); + + // basic rejection criteria + if ((outcode0 & outcode1 & outcode2) != 0) { + moveTo(x2, y2, outcode0); + opened = true; + return; + } + } + } + final float[] mid = middle; mid[0] = cx0; mid[1] = cy0; @@ -1051,7 +1239,7 @@ mid[4] = x2; mid[5] = y2; // need these so we can update the state at the end of this method - final float xf = mid[4], yf = mid[5]; + final float xf = x2, yf = y2; float dxs = mid[2] - mid[0]; float dys = mid[3] - mid[1]; float dxf = mid[4] - mid[2]; @@ -1062,6 +1250,10 @@ } if (dxs == 0.0f && dys == 0.0f) { // this happens if the "curve" is just a point + // fix outcode0 for lineTo() call: + if (clipRect != null) { + this.cOutCode = outcode0; + } lineTo(mid[0], mid[1]); return; } @@ -1079,7 +1271,7 @@ } computeOffset(dxs, dys, lineWidth2, offset0); - drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]); + drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2); @@ -1114,214 +1306,16 @@ emitLineToRev(r[kind - 2], r[kind - 1]); } - this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; - this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; - this.cdx = dxf; - this.cdy = dyf; + this.prev = DRAWING_OP_TO; this.cx0 = xf; this.cy0 = yf; - this.prev = DRAWING_OP_TO; + this.cdx = dxf; + this.cdy = dyf; + this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; + this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; } @Override public long getNativeConsumer() { throw new InternalError("Stroker doesn't use a native consumer"); } - - // a stack of polynomial curves where each curve shares endpoints with - // adjacent ones. - static final class PolyStack { - private static final byte TYPE_LINETO = (byte) 0; - private static final byte TYPE_QUADTO = (byte) 1; - private static final byte TYPE_CUBICTO = (byte) 2; - - // curves capacity = edges count (8192) = edges x 2 (coords) - private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1; - - // types capacity = edges count (4096) - private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT; - - float[] curves; - int end; - byte[] curveTypes; - int numCurves; - - // per-thread renderer context - final RendererContext rdrCtx; - - // curves ref (dirty) - final FloatArrayCache.Reference curves_ref; - // curveTypes ref (dirty) - final ByteArrayCache.Reference curveTypes_ref; - - // used marks (stats only) - int curveTypesUseMark; - int curvesUseMark; - - /** - * Constructor - * @param rdrCtx per-thread renderer context - */ - PolyStack(final RendererContext rdrCtx) { - this.rdrCtx = rdrCtx; - - curves_ref = rdrCtx.newDirtyFloatArrayRef(INITIAL_CURVES_COUNT); // 32K - curves = curves_ref.initial; - - curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K - curveTypes = curveTypes_ref.initial; - numCurves = 0; - end = 0; - - if (DO_STATS) { - curveTypesUseMark = 0; - curvesUseMark = 0; - } - } - - /** - * Disposes this PolyStack: - * clean up before reusing this instance - */ - void dispose() { - end = 0; - numCurves = 0; - - if (DO_STATS) { - rdrCtx.stats.stat_rdr_poly_stack_types.add(curveTypesUseMark); - rdrCtx.stats.stat_rdr_poly_stack_curves.add(curvesUseMark); - rdrCtx.stats.hist_rdr_poly_stack_curves.add(curvesUseMark); - - // reset marks - curveTypesUseMark = 0; - curvesUseMark = 0; - } - - // Return arrays: - // curves and curveTypes are kept dirty - curves = curves_ref.putArray(curves); - curveTypes = curveTypes_ref.putArray(curveTypes); - } - - private void ensureSpace(final int n) { - // use substraction to avoid integer overflow: - if (curves.length - end < n) { - if (DO_STATS) { - rdrCtx.stats.stat_array_stroker_polystack_curves - .add(end + n); - } - curves = curves_ref.widenArray(curves, end, end + n); - } - if (curveTypes.length <= numCurves) { - if (DO_STATS) { - rdrCtx.stats.stat_array_stroker_polystack_curveTypes - .add(numCurves + 1); - } - curveTypes = curveTypes_ref.widenArray(curveTypes, - numCurves, - numCurves + 1); - } - } - - void pushCubic(float x0, float y0, - float x1, float y1, - float x2, float y2) - { - ensureSpace(6); - curveTypes[numCurves++] = TYPE_CUBICTO; - // we reverse the coordinate order to make popping easier - final float[] _curves = curves; - int e = end; - _curves[e++] = x2; _curves[e++] = y2; - _curves[e++] = x1; _curves[e++] = y1; - _curves[e++] = x0; _curves[e++] = y0; - end = e; - } - - void pushQuad(float x0, float y0, - float x1, float y1) - { - ensureSpace(4); - curveTypes[numCurves++] = TYPE_QUADTO; - final float[] _curves = curves; - int e = end; - _curves[e++] = x1; _curves[e++] = y1; - _curves[e++] = x0; _curves[e++] = y0; - end = e; - } - - void pushLine(float x, float y) { - ensureSpace(2); - curveTypes[numCurves++] = TYPE_LINETO; - curves[end++] = x; curves[end++] = y; - } - - void popAll(PathConsumer2D io) { - if (DO_STATS) { - // update used marks: - if (numCurves > curveTypesUseMark) { - curveTypesUseMark = numCurves; - } - if (end > curvesUseMark) { - curvesUseMark = end; - } - } - final byte[] _curveTypes = curveTypes; - final float[] _curves = curves; - int nc = numCurves; - int e = end; - - while (nc != 0) { - switch(_curveTypes[--nc]) { - case TYPE_LINETO: - e -= 2; - io.lineTo(_curves[e], _curves[e+1]); - continue; - case TYPE_QUADTO: - e -= 4; - io.quadTo(_curves[e+0], _curves[e+1], - _curves[e+2], _curves[e+3]); - continue; - case TYPE_CUBICTO: - e -= 6; - io.curveTo(_curves[e+0], _curves[e+1], - _curves[e+2], _curves[e+3], - _curves[e+4], _curves[e+5]); - continue; - default: - } - } - numCurves = 0; - end = 0; - } - - @Override - public String toString() { - String ret = ""; - int nc = numCurves; - int last = end; - int len; - while (nc != 0) { - switch(curveTypes[--nc]) { - case TYPE_LINETO: - len = 2; - ret += "line: "; - break; - case TYPE_QUADTO: - len = 4; - ret += "quad: "; - break; - case TYPE_CUBICTO: - len = 6; - ret += "cubic: "; - break; - default: - len = 0; - } - last -= len; - ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len)) - + "\n"; - } - return ret; - } - } } --- old/src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java 2017-08-28 16:48:33.181663138 +0200 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java 2017-08-28 16:48:33.041663140 +0200 @@ -28,24 +28,58 @@ import sun.awt.geom.PathConsumer2D; import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; +import sun.java2d.marlin.Helpers.PolyStack; final class TransformingPathConsumer2D { - TransformingPathConsumer2D() { - // used by RendererContext - } + private final RendererContext rdrCtx; + + // recycled ClosedPathDetector instance from detectClosedPath() + private final ClosedPathDetector cpDetector; // recycled PathConsumer2D instance from wrapPath2d() private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper(); + // recycled PathConsumer2D instances from deltaTransformConsumer() + private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter(); + private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter(); + + // recycled PathConsumer2D instances from inverseDeltaTransformConsumer() + private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter(); + private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter(); + + // recycled PathTracer instances from tracer...() methods + private final PathTracer tracerInput = new PathTracer("[Input]"); + private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector"); + private final PathTracer tracerStroker = new PathTracer("Stroker"); + + TransformingPathConsumer2D(final RendererContext rdrCtx) { + // used by RendererContext + this.rdrCtx = rdrCtx; + this.cpDetector = new ClosedPathDetector(rdrCtx); + } + PathConsumer2D wrapPath2d(Path2D.Float p2d) { return wp_Path2DWrapper.init(p2d); } - // recycled PathConsumer2D instances from deltaTransformConsumer() - private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter(); - private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter(); + PathConsumer2D traceInput(PathConsumer2D out) { + return tracerInput.init(out); + } + + PathConsumer2D traceClosedPathDetector(PathConsumer2D out) { + return tracerCPDetector.init(out); + } + + PathConsumer2D traceStroker(PathConsumer2D out) { + return tracerStroker.init(out); + } + + PathConsumer2D detectClosedPath(PathConsumer2D out) + { + return cpDetector.init(out); + } PathConsumer2D deltaTransformConsumer(PathConsumer2D out, AffineTransform at) @@ -53,25 +87,98 @@ if (at == null) { return out; } - float mxx = (float) at.getScaleX(); - float mxy = (float) at.getShearX(); - float myx = (float) at.getShearY(); - float myy = (float) at.getScaleY(); + final float mxx = (float) at.getScaleX(); + final float mxy = (float) at.getShearX(); + final float myx = (float) at.getShearY(); + final float myy = (float) at.getScaleY(); if (mxy == 0.0f && myx == 0.0f) { if (mxx == 1.0f && myy == 1.0f) { return out; } else { + // Scale only + if (rdrCtx.doClip) { + // adjust clip rectangle (ymin, ymax, xmin, xmax): + adjustClipScale(rdrCtx.clipRect, mxx, myy); + } return dt_DeltaScaleFilter.init(out, mxx, myy); } } else { + if (rdrCtx.doClip) { + // adjust clip rectangle (ymin, ymax, xmin, xmax): + adjustClipInverseDelta(rdrCtx.clipRect, mxx, mxy, myx, myy); + } return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy); } } - // recycled PathConsumer2D instances from inverseDeltaTransformConsumer() - private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter(); - private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter(); + private static void adjustClipOffset(final float[] clipRect) { + clipRect[0] += Renderer.RDR_OFFSET_Y; + clipRect[1] += Renderer.RDR_OFFSET_Y; + clipRect[2] += Renderer.RDR_OFFSET_X; + clipRect[3] += Renderer.RDR_OFFSET_X; + } + + private static void adjustClipScale(final float[] clipRect, + final float mxx, final float myy) + { + adjustClipOffset(clipRect); + + // Adjust the clipping rectangle (iv_DeltaScaleFilter): + clipRect[0] /= myy; + clipRect[1] /= myy; + clipRect[2] /= mxx; + clipRect[3] /= mxx; + } + + private static void adjustClipInverseDelta(final float[] clipRect, + final float mxx, final float mxy, + final float myx, final float myy) + { + adjustClipOffset(clipRect); + + // Adjust the clipping rectangle (iv_DeltaTransformFilter): + final float det = mxx * myy - mxy * myx; + final float imxx = myy / det; + final float imxy = -mxy / det; + final float imyx = -myx / det; + final float imyy = mxx / det; + + float xmin, xmax, ymin, ymax; + float x, y; + // xmin, ymin: + x = clipRect[2] * imxx + clipRect[0] * imxy; + y = clipRect[2] * imyx + clipRect[0] * imyy; + + xmin = xmax = x; + ymin = ymax = y; + + // xmax, ymin: + x = clipRect[3] * imxx + clipRect[0] * imxy; + y = clipRect[3] * imyx + clipRect[0] * imyy; + + if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } + if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } + + // xmin, ymax: + x = clipRect[2] * imxx + clipRect[1] * imxy; + y = clipRect[2] * imyx + clipRect[1] * imyy; + + if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } + if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } + + // xmax, ymax: + x = clipRect[3] * imxx + clipRect[1] * imxy; + y = clipRect[3] * imyx + clipRect[1] * imyy; + + if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } + if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } + + clipRect[0] = ymin; + clipRect[1] = ymax; + clipRect[2] = xmin; + clipRect[3] = xmax; + } PathConsumer2D inverseDeltaTransformConsumer(PathConsumer2D out, AffineTransform at) @@ -91,7 +198,7 @@ return iv_DeltaScaleFilter.init(out, 1.0f/mxx, 1.0f/myy); } } else { - float det = mxx * myy - mxy * myx; + final float det = mxx * myy - mxy * myx; return iv_DeltaTransformFilter.init(out, myy / det, -mxy / det, @@ -100,7 +207,6 @@ } } - static final class DeltaScaleFilter implements PathConsumer2D { private PathConsumer2D out; private float sx, sy; @@ -271,6 +377,154 @@ } @Override + public long getNativeConsumer() { + throw new InternalError("Not using a native peer"); + } + } + + static final class ClosedPathDetector implements PathConsumer2D { + + private final RendererContext rdrCtx; + private final PolyStack stack; + + private PathConsumer2D out; + + ClosedPathDetector(final RendererContext rdrCtx) { + this.rdrCtx = rdrCtx; + this.stack = (rdrCtx.stats != null) ? + new PolyStack(rdrCtx, + rdrCtx.stats.stat_cpd_polystack_types, + rdrCtx.stats.stat_cpd_polystack_curves, + rdrCtx.stats.hist_cpd_polystack_curves, + rdrCtx.stats.stat_array_cpd_polystack_curves, + rdrCtx.stats.stat_array_cpd_polystack_types) + : new PolyStack(rdrCtx); + } + + ClosedPathDetector init(PathConsumer2D out) { + this.out = out; + return this; // fluent API + } + + /** + * Disposes this instance: + * clean up before reusing this instance + */ + void dispose() { + stack.dispose(); + } + + @Override + public void pathDone() { + // previous path is not closed: + finish(false); + out.pathDone(); + + // TODO: fix possible leak if exception happened + // Dispose this instance: + dispose(); + } + + @Override + public void closePath() { + // path is closed + finish(true); + out.closePath(); + } + + @Override + public void moveTo(float x0, float y0) { + // previous path is not closed: + finish(false); + out.moveTo(x0, y0); + } + + private void finish(final boolean closed) { + rdrCtx.closedPath = closed; + stack.pullAll(out); + } + + @Override + public void lineTo(float x1, float y1) { + stack.pushLine(x1, y1); + } + + @Override + public void curveTo(float x3, float y3, + float x2, float y2, + float x1, float y1) + { + stack.pushCubic(x1, y1, x2, y2, x3, y3); + } + + @Override + public void quadTo(float x2, float y2, float x1, float y1) { + stack.pushQuad(x1, y1, x2, y2); + } + + @Override + public long getNativeConsumer() { + throw new InternalError("Not using a native peer"); + } + } + + static final class PathTracer implements PathConsumer2D { + private final String prefix; + private PathConsumer2D out; + + PathTracer(String name) { + this.prefix = name + ": "; + } + + PathTracer init(PathConsumer2D out) { + this.out = out; + return this; // fluent API + } + + @Override + public void moveTo(float x0, float y0) { + log("moveTo (" + x0 + ", " + y0 + ')'); + out.moveTo(x0, y0); + } + + @Override + public void lineTo(float x1, float y1) { + log("lineTo (" + x1 + ", " + y1 + ')'); + out.lineTo(x1, y1); + } + + @Override + public void curveTo(float x1, float y1, + float x2, float y2, + float x3, float y3) + { + log("curveTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ')'); + out.curveTo(x1, y1, x2, y2, x3, y3); + } + + @Override + public void quadTo(float x1, float y1, float x2, float y2) { + log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ')'); + out.quadTo(x1, y1, x2, y2); + } + + @Override + public void closePath() { + log("closePath"); + out.closePath(); + } + + @Override + public void pathDone() { + log("pathDone"); + out.pathDone(); + } + + private void log(final String message) { + System.out.println(prefix + message); + } + + @Override public long getNativeConsumer() { throw new InternalError("Not using a native peer"); } --- old/src/java.desktop/share/classes/sun/java2d/marlin/Version.java 2017-08-28 16:48:33.685663131 +0200 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/Version.java 2017-08-28 16:48:33.541663133 +0200 @@ -27,7 +27,7 @@ public final class Version { - private static final String VERSION = "marlin-0.7.5-Unsafe-OpenJDK"; + private static final String VERSION = "marlin-0.8.0-Unsafe-OpenJDK"; public static String getVersion() { return VERSION;