changeset 48369:fd7fbc929001

8191814: Marlin rasterizer spends time computing geometry for stroked segments that do not intersect the clip Summary: upgrade to Marlin 0.8.2 providing efficient path clipping (Stroker and Filler) Reviewed-by: prr, serb
author lbourges
date Mon, 11 Dec 2017 21:14:43 +0100
parents da1b57b17101
children 7e8a0c4ee95e
files src/java.desktop/share/classes/sun/java2d/marlin/Curve.java src/java.desktop/share/classes/sun/java2d/marlin/DCurve.java src/java.desktop/share/classes/sun/java2d/marlin/DDasher.java src/java.desktop/share/classes/sun/java2d/marlin/DHelpers.java src/java.desktop/share/classes/sun/java2d/marlin/DMarlinRenderingEngine.java src/java.desktop/share/classes/sun/java2d/marlin/DRenderer.java src/java.desktop/share/classes/sun/java2d/marlin/DRendererContext.java src/java.desktop/share/classes/sun/java2d/marlin/DStroker.java src/java.desktop/share/classes/sun/java2d/marlin/DTransformingPathConsumer2D.java src/java.desktop/share/classes/sun/java2d/marlin/Dasher.java src/java.desktop/share/classes/sun/java2d/marlin/Helpers.java src/java.desktop/share/classes/sun/java2d/marlin/MarlinCache.java src/java.desktop/share/classes/sun/java2d/marlin/MarlinConst.java src/java.desktop/share/classes/sun/java2d/marlin/MarlinProperties.java src/java.desktop/share/classes/sun/java2d/marlin/MarlinRenderingEngine.java src/java.desktop/share/classes/sun/java2d/marlin/Renderer.java src/java.desktop/share/classes/sun/java2d/marlin/RendererContext.java src/java.desktop/share/classes/sun/java2d/marlin/RendererStats.java src/java.desktop/share/classes/sun/java2d/marlin/Stroker.java src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java src/java.desktop/share/classes/sun/java2d/marlin/Version.java test/jdk/sun/java2d/marlin/ClipShapeTest.java
diffstat 22 files changed, 4026 insertions(+), 1071 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.desktop/share/classes/sun/java2d/marlin/Curve.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/Curve.java	Mon Dec 11 21:14:43 2017 +0100
@@ -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;
--- a/src/java.desktop/share/classes/sun/java2d/marlin/DCurve.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/DCurve.java	Mon Dec 11 21:14:43 2017 +0100
@@ -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;
--- a/src/java.desktop/share/classes/sun/java2d/marlin/DDasher.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/DDasher.java	Mon Dec 11 21:14:43 2017 +0100
@@ -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) {
+                    needsMoveTo = false;
                     out.moveTo(x0, y0);
-                    needsMoveTo = false;
                 }
                 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;
         }
     }
 
@@ -357,43 +384,59 @@
 
     // preconditions: curCurvepts must be an array of length at least 2 * type,
     // that contains the curve we want to dash in the first type elements
-    private void somethingTo(int type) {
+    private void somethingTo(final int type) {
         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];
+            _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;
+
+        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,22 +712,23 @@
         // 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;
-                curLeafCtrlPolyLengths[i/2 - 1] = len;
+                curLeafCtrlPolyLengths[(i >> 1) - 1] = len;
                 x0 = x1;
                 y0 = y1;
             }
 
             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();
         }
--- a/src/java.desktop/share/classes/sun/java2d/marlin/DHelpers.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/DHelpers.java	Mon Dec 11 21:14:43 2017 +0100
@@ -26,10 +26,9 @@
 package sun.java2d.marlin;
 
 import static java.lang.Math.PI;
-import static java.lang.Math.cos;
-import static java.lang.Math.sqrt;
-import static java.lang.Math.cbrt;
-import static java.lang.Math.acos;
+import java.util.Arrays;
+import sun.java2d.marlin.stats.Histogram;
+import sun.java2d.marlin.stats.StatLong;
 
 final class DHelpers implements MarlinConst {
 
@@ -115,17 +114,17 @@
         int num;
         if (D < 0.0d) {
             // see: http://en.wikipedia.org/wiki/Cubic_function#Trigonometric_.28and_hyperbolic.29_method
-            final double phi = (1.0d/3.0d) * acos(-q / sqrt(-cb_p));
-            final double t = 2.0d * sqrt(-p);
+            final double phi = (1.0d/3.0d) * Math.acos(-q / Math.sqrt(-cb_p));
+            final double t = 2.0d * Math.sqrt(-p);
 
-            pts[ off+0 ] = ( t * cos(phi));
-            pts[ off+1 ] = (-t * cos(phi + (PI / 3.0d)));
-            pts[ off+2 ] = (-t * cos(phi - (PI / 3.0d)));
+            pts[ off+0 ] = ( t * Math.cos(phi));
+            pts[ off+1 ] = (-t * Math.cos(phi + (PI / 3.0d)));
+            pts[ off+2 ] = (-t * Math.cos(phi - (PI / 3.0d)));
             num = 3;
         } else {
-            final double sqrt_D = sqrt(D);
-            final double u = cbrt(sqrt_D - q);
-            final double v = - cbrt(sqrt_D + q);
+            final double sqrt_D = Math.sqrt(D);
+            final double u =   Math.cbrt(sqrt_D - q);
+            final double v = - Math.cbrt(sqrt_D + q);
 
             pts[ off ] = (u + v);
             num = 1;
@@ -171,15 +170,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 +423,388 @@
             return;
         }
     }
+
+    // From sun.java2d.loops.GeneralRenderer:
+
+    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;
+        }
+    }
+
+    // a stack of integer indices
+    static final class IndexStack {
+
+        // integer capacity = edges count / 4 ~ 1024
+        private static final int INITIAL_COUNT = INITIAL_EDGES_COUNT >> 2;
+
+        private int end;
+        private int[] indices;
+
+        // indices ref (dirty)
+        private final IntArrayCache.Reference indices_ref;
+
+        // used marks (stats only)
+        private int indicesUseMark;
+
+        private final StatLong stat_idxstack_indices;
+        private final Histogram hist_idxstack_indices;
+        private final StatLong stat_array_idxstack_indices;
+
+        IndexStack(final DRendererContext rdrCtx) {
+            this(rdrCtx, null, null, null);
+        }
+
+        IndexStack(final DRendererContext rdrCtx,
+                   final StatLong stat_idxstack_indices,
+                   final Histogram hist_idxstack_indices,
+                   final StatLong stat_array_idxstack_indices)
+        {
+            indices_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_COUNT); // 4K
+            indices     = indices_ref.initial;
+            end = 0;
+
+            if (DO_STATS) {
+                indicesUseMark = 0;
+            }
+            this.stat_idxstack_indices = stat_idxstack_indices;
+            this.hist_idxstack_indices = hist_idxstack_indices;
+            this.stat_array_idxstack_indices = stat_array_idxstack_indices;
+        }
+
+        /**
+         * Disposes this PolyStack:
+         * clean up before reusing this instance
+         */
+        void dispose() {
+            end = 0;
+
+            if (DO_STATS) {
+                stat_idxstack_indices.add(indicesUseMark);
+                hist_idxstack_indices.add(indicesUseMark);
+
+                // reset marks
+                indicesUseMark = 0;
+            }
+
+            // Return arrays:
+            // values is kept dirty
+            indices = indices_ref.putArray(indices);
+        }
+
+        boolean isEmpty() {
+            return (end == 0);
+        }
+
+        void reset() {
+            end = 0;
+        }
+
+        void push(final int v) {
+            // remove redundant values (reverse order):
+            int[] _values = indices;
+            final int nc = end;
+            if (nc != 0) {
+                if (_values[nc - 1] == v) {
+                    // remove both duplicated values:
+                    end--;
+                    return;
+                }
+            }
+            if (_values.length <= nc) {
+                if (DO_STATS) {
+                    stat_array_idxstack_indices.add(nc + 1);
+                }
+                indices = _values = indices_ref.widenArray(_values, nc, nc + 1);
+            }
+            _values[end++] = v;
+
+            if (DO_STATS) {
+                // update used marks:
+                if (end > indicesUseMark) {
+                    indicesUseMark = end;
+                }
+            }
+        }
+
+        void pullAll(final double[] points, final DPathConsumer2D io) {
+            final int nc = end;
+            if (nc == 0) {
+                return;
+            }
+            final int[] _values = indices;
+
+            for (int i = 0, j; i < nc; i++) {
+                j = _values[i] << 1;
+                io.lineTo(points[j], points[j + 1]);
+            }
+            end = 0;
+        }
+    }
 }
--- a/src/java.desktop/share/classes/sun/java2d/marlin/DMarlinRenderingEngine.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/DMarlinRenderingEngine.java	Mon Dec 11 21:14:43 2017 +0100
@@ -84,6 +84,13 @@
     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_CLIP_FILL = true;
+
+    static final boolean DO_TRACE_PATH = false;
+
+    static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag();
+
     /**
      * Public constructor
      */
@@ -133,7 +140,7 @@
                      miterlimit,
                      dashes,
                      dashphase,
-                     rdrCtx.transformerPC2D.wrapPath2d(p2d)
+                     rdrCtx.transformerPC2D.wrapPath2D(p2d)
                     );
 
             // Use Path2D copy constructor (trim)
@@ -195,14 +202,14 @@
         }
     }
 
-    final void strokeTo(final DRendererContext rdrCtx,
-                        Shape src,
-                        AffineTransform at,
-                        BasicStroke bs,
-                        boolean thin,
-                        NormMode normalize,
-                        boolean antialias,
-                        DPathConsumer2D pc2d)
+    void strokeTo(final DRendererContext rdrCtx,
+                  Shape src,
+                  AffineTransform at,
+                  BasicStroke bs,
+                  boolean thin,
+                  NormMode normalize,
+                  boolean antialias,
+                  DPathConsumer2D pc2d)
     {
         double lw;
         if (thin) {
@@ -295,17 +302,17 @@
         return (lw / widthScale);
     }
 
-    final void strokeTo(final DRendererContext rdrCtx,
-                        Shape src,
-                        AffineTransform at,
-                        double width,
-                        NormMode norm,
-                        int caps,
-                        int join,
-                        float miterlimit,
-                        float[] dashes,
-                        float dashphase,
-                        DPathConsumer2D pc2d)
+    void strokeTo(final DRendererContext rdrCtx,
+                  Shape src,
+                  AffineTransform at,
+                  double width,
+                  NormMode norm,
+                  int caps,
+                  int join,
+                  float miterlimit,
+                  float[] dashes,
+                  float dashphase,
+                  DPathConsumer2D pc2d)
     {
         // We use strokerat so that in Stroker and Dasher we can work only
         // with the pre-transformation coordinates. This will repeat a lot of
@@ -324,6 +331,7 @@
 
         int dashLen = -1;
         boolean recycleDashes = false;
+        double scale = 1.0d;
         double[] dashesD = null;
 
         // Ensure converting dashes to double precision:
@@ -364,7 +372,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 +407,44 @@
             at = null;
         }
 
+        final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
+
+        if (DO_TRACE_PATH) {
+            // 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_PATH) {
+                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_PATH) {
+            // trace Input:
+            pc2d = transformerPC2D.traceInput(pc2d);
+        }
+
         final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx,
                                          src.getPathIterator(at));
 
@@ -596,14 +625,12 @@
     }
 
     private static void pathTo(final DRendererContext rdrCtx, final PathIterator pi,
-                               final DPathConsumer2D pc2d)
+                               DPathConsumer2D pc2d)
     {
         // mark context as DIRTY:
         rdrCtx.dirty = true;
 
-        final double[] coords = rdrCtx.double6;
-
-        pathToLoop(coords, pi, pc2d);
+        pathToLoop(rdrCtx.double6, pi, pc2d);
 
         // mark context as CLEAN:
         rdrCtx.dirty = false;
@@ -781,6 +808,19 @@
 
         final DRendererContext rdrCtx = getRendererContext();
         try {
+            if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) {
+                // 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;
+            }
+
             // Test if at is identity:
             final AffineTransform _at = (at != null && !at.isIdentity()) ? at
                                         : null;
@@ -797,13 +837,29 @@
                                          clip.getWidth(), clip.getHeight(),
                                          pi.getWindingRule());
 
+                DPathConsumer2D pc2d = r;
+
+                if (DO_CLIP_FILL && rdrCtx.doClip) {
+                    if (DO_TRACE_PATH) {
+                        // trace Filler:
+                        pc2d = rdrCtx.transformerPC2D.traceFiller(pc2d);
+                    }
+                    pc2d = rdrCtx.transformerPC2D.pathClipper(pc2d);
+                }
+
+                if (DO_TRACE_PATH) {
+                    // trace Input:
+                    pc2d = rdrCtx.transformerPC2D.traceInput(pc2d);
+                }
+
                 // TODO: subdivide quad/cubic curves into monotonic curves ?
-                pathTo(rdrCtx, pi, r);
+                pathTo(rdrCtx, pi, pc2d);
+
             } else {
                 // draw shape with given stroke:
                 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
                                          clip.getWidth(), clip.getHeight(),
-                                         PathIterator.WIND_NON_ZERO);
+                                         WIND_NON_ZERO);
 
                 strokeTo(rdrCtx, s, _at, bs, thin, norm, true, r);
             }
@@ -826,12 +882,12 @@
     }
 
     @Override
-    public final AATileGenerator getAATileGenerator(double x, double y,
-                                                    double dx1, double dy1,
-                                                    double dx2, double dy2,
-                                                    double lw1, double lw2,
-                                                    Region clip,
-                                                    int[] bbox)
+    public AATileGenerator getAATileGenerator(double x, double y,
+                                              double dx1, double dy1,
+                                              double dx2, double dy2,
+                                              double lw1, double lw2,
+                                              Region clip,
+                                              int[] bbox)
     {
         // REMIND: Deal with large coordinates!
         double ldx1, ldy1, ldx2, ldy2;
@@ -862,8 +918,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(),
+                                     WIND_EVEN_ODD);
 
             r.moveTo( x,  y);
             r.lineTo( (x+dx1),  (y+dy1));
@@ -915,14 +971,14 @@
     }
 
     static {
-        if (PathIterator.WIND_NON_ZERO != DRenderer.WIND_NON_ZERO ||
-            PathIterator.WIND_EVEN_ODD != DRenderer.WIND_EVEN_ODD ||
-            BasicStroke.JOIN_MITER != DStroker.JOIN_MITER ||
-            BasicStroke.JOIN_ROUND != DStroker.JOIN_ROUND ||
-            BasicStroke.JOIN_BEVEL != DStroker.JOIN_BEVEL ||
-            BasicStroke.CAP_BUTT != DStroker.CAP_BUTT ||
-            BasicStroke.CAP_ROUND != DStroker.CAP_ROUND ||
-            BasicStroke.CAP_SQUARE != DStroker.CAP_SQUARE)
+        if (PathIterator.WIND_NON_ZERO != WIND_NON_ZERO ||
+            PathIterator.WIND_EVEN_ODD != WIND_EVEN_ODD ||
+            BasicStroke.JOIN_MITER != JOIN_MITER ||
+            BasicStroke.JOIN_ROUND != JOIN_ROUND ||
+            BasicStroke.JOIN_BEVEL != JOIN_BEVEL ||
+            BasicStroke.CAP_BUTT != CAP_BUTT ||
+            BasicStroke.CAP_ROUND != CAP_ROUND ||
+            BasicStroke.CAP_SQUARE != CAP_SQUARE)
         {
             throw new InternalError("mismatched renderer constants");
         }
@@ -1045,6 +1101,11 @@
         logInfo("sun.java2d.renderer.useSimplifier    = "
                 + MarlinConst.USE_SIMPLIFIER);
 
+        logInfo("sun.java2d.renderer.clip             = "
+                + MarlinProperties.isDoClip());
+        logInfo("sun.java2d.renderer.clip.runtime.enable = "
+                + MarlinProperties.isDoClipRuntimeFlag());
+
         // debugging parameters
         logInfo("sun.java2d.renderer.doStats          = "
                 + MarlinConst.DO_STATS);
--- a/src/java.desktop/share/classes/sun/java2d/marlin/DRenderer.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/DRenderer.java	Mon Dec 11 21:14:43 2017 +0100
@@ -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.5d / SUBPIXEL_SCALE_X;
+    static final double RDR_OFFSET_Y = 0.5d / SUBPIXEL_SCALE_Y;
+
     // number of subpixels corresponding to a tile line
     private static final int SUBPIXEL_TILE
         = TILE_H << SUBPIXEL_LG_POSITIONS_Y;
@@ -57,9 +60,6 @@
     // crossing capacity = edges count / 4 ~ 1024
     static final int INITIAL_CROSSING_COUNT = INITIAL_EDGES_COUNT >> 2;
 
-    public static final int WIND_EVEN_ODD = 0;
-    public static final int WIND_NON_ZERO = 1;
-
     // common to all types of input path segments.
     // OFFSET as bytes
     // only integer values:
@@ -522,11 +522,11 @@
 
     DRenderer(final DRendererContext rdrCtx) {
         this.rdrCtx = rdrCtx;
+        this.curve = rdrCtx.curve;
+        this.cache = rdrCtx.cache;
 
         this.edges = rdrCtx.newOffHeapArray(INITIAL_EDGES_CAPACITY); // 96K
 
-        this.curve = rdrCtx.curve;
-
         edgeBuckets_ref      = rdrCtx.newCleanIntArrayRef(INITIAL_BUCKET_ARRAY); // 64K
         edgeBucketCounts_ref = rdrCtx.newCleanIntArrayRef(INITIAL_BUCKET_ARRAY); // 64K
 
@@ -537,8 +537,6 @@
         alphaLine_ref = rdrCtx.newCleanIntArrayRef(INITIAL_AA_ARRAY); // 8K
         alphaLine     = alphaLine_ref.initial;
 
-        this.cache = rdrCtx.cache;
-
         crossings_ref     = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K
         aux_crossings_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K
         edgePtrs_ref      = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K
@@ -668,7 +666,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 +677,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 +686,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 +713,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
--- a/src/java.desktop/share/classes/sun/java2d/marlin/DRendererContext.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/DRendererContext.java	Mon Dec 11 21:14:43 2017 +0100
@@ -75,16 +75,22 @@
     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 */
     private final IntArrayCache cleanIntCache = new IntArrayCache(true, 5);
-    /* dirty int[] cache = 4 refs */
-    private final IntArrayCache dirtyIntCache = new IntArrayCache(false, 4);
-    /* dirty double[] cache = 3 refs */
-    private final DoubleArrayCache dirtyDoubleCache = new DoubleArrayCache(false, 3);
-    /* dirty byte[] cache = 1 ref */
-    private final ByteArrayCache dirtyByteCache = new ByteArrayCache(false, 1);
+    /* dirty int[] cache = 5 refs */
+    private final IntArrayCache dirtyIntCache = new IntArrayCache(false, 5);
+    /* dirty double[] cache = 4 refs (2 polystack) */
+    private final DoubleArrayCache dirtyDoubleCache = new DoubleArrayCache(false, 4);
+    /* dirty byte[] cache = 2 ref (2 polystack) */
+    private final ByteArrayCache dirtyByteCache = new ByteArrayCache(false, 2);
 
     // RendererContext statistics
     final RendererStats stats;
@@ -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:
@@ -162,12 +171,11 @@
 
     Path2D.Double getPath2D() {
         // resolve reference:
-        Path2D.Double p2d
-            = (refPath2D != null) ? refPath2D.get() : null;
+        Path2D.Double p2d = (refPath2D != null) ? refPath2D.get() : null;
 
         // create a new Path2D ?
         if (p2d == null) {
-            p2d = new Path2D.Double(Path2D.WIND_NON_ZERO, INITIAL_EDGES_COUNT); // 32K
+            p2d = new Path2D.Double(WIND_NON_ZERO, INITIAL_EDGES_COUNT); // 32K
 
             // update weak reference:
             refPath2D = new WeakReference<Path2D.Double>(p2d);
--- a/src/java.desktop/share/classes/sun/java2d/marlin/DStroker.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/DStroker.java	Mon Dec 11 21:14:43 2017 +0100
@@ -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
@@ -36,42 +37,16 @@
     private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
     private static final int CLOSE = 2;
 
-    /**
-     * Constant value for join style.
-     */
-    public static final int JOIN_MITER = 0;
-
-    /**
-     * Constant value for join style.
-     */
-    public static final int JOIN_ROUND = 1;
-
-    /**
-     * Constant value for join style.
-     */
-    public static final int JOIN_BEVEL = 2;
-
-    /**
-     * Constant value for end cap style.
-     */
-    public static final int CAP_BUTT = 0;
-
-    /**
-     * Constant value for end cap style.
-     */
-    public static final int CAP_ROUND = 1;
-
-    /**
-     * Constant value for end cap style.
-     */
-    public static final int CAP_SQUARE = 2;
-
     // pisces used to use fixed point arithmetic with 16 decimal digits. I
     // didn't want to change the values of the constant below when I converted
     // 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 +93,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 <code>DStroker</code>.
      * @param rdrCtx per-thread renderer context
@@ -125,7 +114,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 +138,15 @@
      * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
      * <code>JOIN_BEVEL</code>.
      * @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 +155,45 @@
         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;
+            }
+            // add a small rounding error:
+            margin += 1e-3d;
+
+            // 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;
+            this.cOutCode = 0;
+            this.sOutCode = 0;
+        }
         return this; // fluent API
     }
 
@@ -173,6 +204,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 +477,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;
         }
-        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;
+    }
+
+    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;
+        }
     }
 
     @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 +542,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 +550,65 @@
         emitLineToRev(cx0 - mx, cy0 - my);
         emitLineToRev( x1 - mx,  y1 - my);
 
+        this.prev = DRAWING_OP_TO;
+        this.cx0 = x1;
+        this.cy0 = y1;
+        this.cdx = dx;
+        this.cdy = dy;
         this.cmx = mx;
         this.cmy = my;
-        this.cdx = dx;
-        this.cdy = dy;
-        this.cx0 = x1;
-        this.cy0 = y1;
-        this.prev = DRAWING_OP_TO;
     }
 
     @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);
+        // basic acceptance criteria
+        if ((sOutCode & cOutCode) == 0) {
+            if (cx0 != sx0 || cy0 != sy0) {
+                lineTo(sx0, sy0, true);
+            }
+
+            drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, sOutCode);
+
+            emitLineTo(sx0 + smx, sy0 + smy);
+
+            if (opened) {
+                emitLineTo(sx0 - smx, sy0 - smy);
+            } else {
+                emitMoveTo(sx0 - smx, sy0 - smy);
+            }
         }
-
-        drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy);
-
-        emitLineTo(sx0 + smx, sy0 + smy);
-
-        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 +618,7 @@
     @Override
     public void pathDone() {
         if (prev == DRAWING_OP_TO) {
-            finish();
+            finish(cOutCode);
         }
 
         out.pathDone();
@@ -532,23 +631,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();
+
+            if (!capStart) {
+                capStart = true;
+
+                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();
         }
-
-        emitReverse();
-
-        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);
-        }
-
         emitClose();
     }
 
@@ -620,23 +735,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 +1061,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 ((outcode0 & 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 +1092,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 +1118,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 +1140,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 +1175,36 @@
             emitLineToRev(r[kind - 2], r[kind - 1]);
         }
 
+        this.prev = DRAWING_OP_TO;
+        this.cx0 = xf;
+        this.cy0 = yf;
+        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;
-        this.cdx = dxf;
-        this.cdy = dyf;
-        this.cx0 = xf;
-        this.cy0 = yf;
-        this.prev = DRAWING_OP_TO;
     }
 
-    @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 ((outcode0 & 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 +1212,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 +1223,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 +1244,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 +1279,16 @@
             emitLineToRev(r[kind - 2], r[kind - 1]);
         }
 
+        this.prev = DRAWING_OP_TO;
+        this.cx0 = xf;
+        this.cy0 = yf;
+        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;
-        this.cdx = dxf;
-        this.cdy = dyf;
-        this.cx0 = xf;
-        this.cy0 = yf;
-        this.prev = DRAWING_OP_TO;
     }
 
     @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;
-        }
-    }
 }
--- a/src/java.desktop/share/classes/sun/java2d/marlin/DTransformingPathConsumer2D.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/DTransformingPathConsumer2D.java	Mon Dec 11 21:14:43 2017 +0100
@@ -27,50 +27,169 @@
 
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Path2D;
+import sun.java2d.marlin.DHelpers.IndexStack;
+import sun.java2d.marlin.DHelpers.PolyStack;
 
 final class DTransformingPathConsumer2D {
 
-    DTransformingPathConsumer2D() {
-        // used by DRendererContext
-    }
+    private final DRendererContext rdrCtx;
 
-    // recycled DPathConsumer2D instance from wrapPath2d()
+    // recycled ClosedPathDetector instance from detectClosedPath()
+    private final ClosedPathDetector   cpDetector;
+
+    // recycled PathClipFilter instance from pathClipper()
+    private final PathClipFilter       pathClipper;
+
+    // recycled DPathConsumer2D instance from wrapPath2D()
     private final Path2DWrapper        wp_Path2DWrapper        = new Path2DWrapper();
 
-    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();
 
+    // 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 tracerFiller     = new PathTracer("Filler");
+    private final PathTracer tracerStroker    = new PathTracer("Stroker");
+
+    DTransformingPathConsumer2D(final DRendererContext rdrCtx) {
+        // used by RendererContext
+        this.rdrCtx = rdrCtx;
+        this.cpDetector = new ClosedPathDetector(rdrCtx);
+        this.pathClipper = new PathClipFilter(rdrCtx);
+    }
+
+    DPathConsumer2D wrapPath2D(Path2D.Double p2d) {
+        return wp_Path2DWrapper.init(p2d);
+    }
+
+    DPathConsumer2D traceInput(DPathConsumer2D out) {
+        return tracerInput.init(out);
+    }
+
+    DPathConsumer2D traceClosedPathDetector(DPathConsumer2D out) {
+        return tracerCPDetector.init(out);
+    }
+
+    DPathConsumer2D traceFiller(DPathConsumer2D out) {
+        return tracerFiller.init(out);
+    }
+
+    DPathConsumer2D traceStroker(DPathConsumer2D out) {
+        return tracerStroker.init(out);
+    }
+
+    DPathConsumer2D detectClosedPath(DPathConsumer2D out) {
+        return cpDetector.init(out);
+    }
+
+    DPathConsumer2D pathClipper(DPathConsumer2D out) {
+        return pathClipper.init(out);
+    }
+
     DPathConsumer2D deltaTransformConsumer(DPathConsumer2D out,
                                           AffineTransform at)
     {
         if (at == null) {
             return out;
         }
-        double mxx = at.getScaleX();
-        double mxy = at.getShearX();
-        double myx = at.getShearY();
-        double myy = at.getScaleY();
+        final double mxx = at.getScaleX();
+        final double mxy = at.getShearX();
+        final double myx = at.getShearY();
+        final double myy = at.getScaleY();
 
         if (mxy == 0.0d && myx == 0.0d) {
             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 +209,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 +218,6 @@
         }
     }
 
-
     static final class DeltaScaleFilter implements DPathConsumer2D {
         private DPathConsumer2D out;
         private double sx, sy;
@@ -274,4 +392,427 @@
             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 PathClipFilter implements DPathConsumer2D {
+
+        private DPathConsumer2D out;
+
+        // Bounds of the drawing region, at pixel precision.
+        private final double[] clipRect;
+
+        private final double[] corners = new double[8];
+        private boolean init_corners = false;
+
+        private final IndexStack stack;
+
+        // the current outcode of the current sub path
+        private int cOutCode = 0;
+
+        // the cumulated (and) outcode of the complete path
+        private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
+
+        private boolean outside = false;
+
+        // The current point OUTSIDE
+        private double cx0, cy0;
+
+        PathClipFilter(final DRendererContext rdrCtx) {
+            this.clipRect = rdrCtx.clipRect;
+            this.stack = (rdrCtx.stats != null) ?
+                new IndexStack(rdrCtx,
+                        rdrCtx.stats.stat_pcf_idxstack_indices,
+                        rdrCtx.stats.hist_pcf_idxstack_indices,
+                        rdrCtx.stats.stat_array_pcf_idxstack_indices)
+                : new IndexStack(rdrCtx);
+        }
+
+        PathClipFilter init(final DPathConsumer2D out) {
+            this.out = out;
+
+            // Adjust the clipping rectangle with the renderer offsets
+            final double rdrOffX = DRenderer.RDR_OFFSET_X;
+            final double rdrOffY = DRenderer.RDR_OFFSET_Y;
+
+            // add a small rounding error:
+            final double margin = 1e-3d;
+
+            final double[] _clipRect = this.clipRect;
+            _clipRect[0] -= margin - rdrOffY;
+            _clipRect[1] += margin + rdrOffY;
+            _clipRect[2] -= margin - rdrOffX;
+            _clipRect[3] += margin + rdrOffX;
+
+            this.init_corners = true;
+            this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
+
+            return this; // fluent API
+        }
+
+        /**
+         * Disposes this instance:
+         * clean up before reusing this instance
+         */
+        void dispose() {
+            stack.dispose();
+        }
+
+        private void finishPath() {
+            if (outside) {
+                // criteria: inside or totally outside ?
+                if (gOutCode == 0) {
+                    finish();
+                } else {
+                    this.outside = false;
+                    stack.reset();
+                }
+            }
+        }
+
+        private void finish() {
+            this.outside = false;
+
+            if (!stack.isEmpty()) {
+                if (init_corners) {
+                    init_corners = false;
+
+                    final double[] _corners = corners;
+                    final double[] _clipRect = clipRect;
+                    // Top Left (0):
+                    _corners[0] = _clipRect[2];
+                    _corners[1] = _clipRect[0];
+                    // Bottom Left (1):
+                    _corners[2] = _clipRect[2];
+                    _corners[3] = _clipRect[1];
+                    // Top right (2):
+                    _corners[4] = _clipRect[3];
+                    _corners[5] = _clipRect[0];
+                    // Bottom Right (3):
+                    _corners[6] = _clipRect[3];
+                    _corners[7] = _clipRect[1];
+                }
+                stack.pullAll(corners, out);
+            }
+            out.lineTo(cx0, cy0);
+        }
+
+        @Override
+        public void pathDone() {
+            finishPath();
+
+            out.pathDone();
+
+            // TODO: fix possible leak if exception happened
+            // Dispose this instance:
+            dispose();
+        }
+
+        @Override
+        public void closePath() {
+            finishPath();
+
+            out.closePath();
+        }
+
+        @Override
+        public void moveTo(final double x0, final double y0) {
+            finishPath();
+
+            final int outcode = DHelpers.outcode(x0, y0, clipRect);
+            this.cOutCode = outcode;
+            this.outside = false;
+            out.moveTo(x0, y0);
+        }
+
+        @Override
+        public void lineTo(final double xe, final double ye) {
+            final int outcode0 = this.cOutCode;
+            final int outcode1 = DHelpers.outcode(xe, ye, clipRect);
+            this.cOutCode = outcode1;
+
+            final int sideCode = (outcode0 & outcode1);
+
+            // basic rejection criteria:
+            if (sideCode == 0) {
+                this.gOutCode = 0;
+            } else {
+                this.gOutCode &= sideCode;
+                // keep last point coordinate before entering the clip again:
+                this.outside = true;
+                this.cx0 = xe;
+                this.cy0 = ye;
+
+                clip(sideCode, outcode0, outcode1);
+                return;
+            }
+            if (outside) {
+                finish();
+            }
+            // clipping disabled:
+            out.lineTo(xe, ye);
+        }
+
+        private void clip(final int sideCode,
+                          final int outcode0,
+                          final int outcode1)
+        {
+            // corner or cross-boundary on left or right side:
+            if ((outcode0 != outcode1)
+                    && ((sideCode & MarlinConst.OUTCODE_MASK_L_R) != 0))
+            {
+                // combine outcodes:
+                final int mergeCode = (outcode0 | outcode1);
+                final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B;
+                final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R;
+                final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2;
+
+                // add corners to outside stack:
+                switch (tbCode) {
+                    case MarlinConst.OUTCODE_TOP:
+// System.out.println("TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
+                        stack.push(off); // top
+                        return;
+                    case MarlinConst.OUTCODE_BOTTOM:
+// System.out.println("BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
+                        stack.push(off + 1); // bottom
+                        return;
+                    default:
+                        // both TOP / BOTTOM:
+                        if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {
+// System.out.println("TOP + BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
+                            // top to bottom
+                            stack.push(off); // top
+                            stack.push(off + 1); // bottom
+                        } else {
+// System.out.println("BOTTOM + TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
+                            // bottom to top
+                            stack.push(off + 1); // bottom
+                            stack.push(off); // top
+                        }
+                }
+            }
+        }
+
+        @Override
+        public void curveTo(final double x1, final double y1,
+                            final double x2, final double y2,
+                            final double xe, final double ye)
+        {
+            final int outcode0 = this.cOutCode;
+            final int outcode3 = DHelpers.outcode(xe, ye, clipRect);
+            this.cOutCode = outcode3;
+
+            int sideCode = outcode0 & outcode3;
+
+            if (sideCode == 0) {
+                this.gOutCode = 0;
+            } else {
+                sideCode &= DHelpers.outcode(x1, y1, clipRect);
+                sideCode &= DHelpers.outcode(x2, y2, clipRect);
+                this.gOutCode &= sideCode;
+
+                // basic rejection criteria:
+                if (sideCode != 0) {
+                    // keep last point coordinate before entering the clip again:
+                    this.outside = true;
+                    this.cx0 = xe;
+                    this.cy0 = ye;
+
+                    clip(sideCode, outcode0, outcode3);
+                    return;
+                }
+            }
+            if (outside) {
+                finish();
+            }
+            // clipping disabled:
+            out.curveTo(x1, y1, x2, y2, xe, ye);
+        }
+
+        @Override
+        public void quadTo(final double x1, final double y1,
+                           final double xe, final double ye)
+        {
+            final int outcode0 = this.cOutCode;
+            final int outcode2 = DHelpers.outcode(xe, ye, clipRect);
+            this.cOutCode = outcode2;
+
+            int sideCode = outcode0 & outcode2;
+
+            if (sideCode == 0) {
+                this.gOutCode = 0;
+            } else {
+                sideCode &= DHelpers.outcode(x1, y1, clipRect);
+                this.gOutCode &= sideCode;
+
+                // basic rejection criteria:
+                if (sideCode != 0) {
+                    // keep last point coordinate before entering the clip again:
+                    this.outside = true;
+                    this.cx0 = xe;
+                    this.cy0 = ye;
+
+                    clip(sideCode, outcode0, outcode2);
+                    return;
+                }
+            }
+            if (outside) {
+                finish();
+            }
+            // clipping disabled:
+            out.quadTo(x1, y1, xe, ye);
+        }
+
+        @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");
+        }
+    }
 }
--- a/src/java.desktop/share/classes/sun/java2d/marlin/Dasher.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/Dasher.java	Mon Dec 11 21:14:43 2017 +0100
@@ -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) {
+                    needsMoveTo = false;
                     out.moveTo(x0, y0);
-                    needsMoveTo = false;
                 }
                 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;
         }
     }
 
@@ -358,43 +385,59 @@
 
     // preconditions: curCurvepts must be an array of length at least 2 * type,
     // that contains the curve we want to dash in the first type elements
-    private void somethingTo(int type) {
+    private void somethingTo(final int type) {
         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];
+            _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;
+
+        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,22 +713,23 @@
         // 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;
-                curLeafCtrlPolyLengths[i/2 - 1] = len;
+                curLeafCtrlPolyLengths[(i >> 1) - 1] = len;
                 x0 = x1;
                 y0 = y1;
             }
 
             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();
         }
--- a/src/java.desktop/share/classes/sun/java2d/marlin/Helpers.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/Helpers.java	Mon Dec 11 21:14:43 2017 +0100
@@ -26,10 +26,10 @@
 package sun.java2d.marlin;
 
 import static java.lang.Math.PI;
-import static java.lang.Math.cos;
-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 sun.java2d.marlin.stats.Histogram;
+import sun.java2d.marlin.stats.StatLong;
 
 final class Helpers implements MarlinConst {
 
@@ -120,17 +120,17 @@
         int num;
         if (D < 0.0d) {
             // see: http://en.wikipedia.org/wiki/Cubic_function#Trigonometric_.28and_hyperbolic.29_method
-            final double phi = (1.0d/3.0d) * acos(-q / sqrt(-cb_p));
-            final double t = 2.0d * sqrt(-p);
+            final double phi = (1.0d/3.0d) * Math.acos(-q / Math.sqrt(-cb_p));
+            final double t = 2.0d * Math.sqrt(-p);
 
-            pts[ off+0 ] = (float) ( t * cos(phi));
-            pts[ off+1 ] = (float) (-t * cos(phi + (PI / 3.0d)));
-            pts[ off+2 ] = (float) (-t * cos(phi - (PI / 3.0d)));
+            pts[ off+0 ] = (float) ( t * Math.cos(phi));
+            pts[ off+1 ] = (float) (-t * Math.cos(phi + (PI / 3.0d)));
+            pts[ off+2 ] = (float) (-t * Math.cos(phi - (PI / 3.0d)));
             num = 3;
         } else {
-            final double sqrt_D = sqrt(D);
-            final double u = cbrt(sqrt_D - q);
-            final double v = - cbrt(sqrt_D + q);
+            final double sqrt_D = Math.sqrt(D);
+            final double u =   Math.cbrt(sqrt_D - q);
+            final double v = - Math.cbrt(sqrt_D + q);
 
             pts[ off ] = (float) (u + v);
             num = 1;
@@ -176,15 +176,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 +429,388 @@
             return;
         }
     }
+
+    // From sun.java2d.loops.GeneralRenderer:
+
+    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;
+        }
+    }
+
+    // a stack of integer indices
+    static final class IndexStack {
+
+        // integer capacity = edges count / 4 ~ 1024
+        private static final int INITIAL_COUNT = INITIAL_EDGES_COUNT >> 2;
+
+        private int end;
+        private int[] indices;
+
+        // indices ref (dirty)
+        private final IntArrayCache.Reference indices_ref;
+
+        // used marks (stats only)
+        private int indicesUseMark;
+
+        private final StatLong stat_idxstack_indices;
+        private final Histogram hist_idxstack_indices;
+        private final StatLong stat_array_idxstack_indices;
+
+        IndexStack(final RendererContext rdrCtx) {
+            this(rdrCtx, null, null, null);
+        }
+
+        IndexStack(final RendererContext rdrCtx,
+                   final StatLong stat_idxstack_indices,
+                   final Histogram hist_idxstack_indices,
+                   final StatLong stat_array_idxstack_indices)
+        {
+            indices_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_COUNT); // 4K
+            indices     = indices_ref.initial;
+            end = 0;
+
+            if (DO_STATS) {
+                indicesUseMark = 0;
+            }
+            this.stat_idxstack_indices = stat_idxstack_indices;
+            this.hist_idxstack_indices = hist_idxstack_indices;
+            this.stat_array_idxstack_indices = stat_array_idxstack_indices;
+        }
+
+        /**
+         * Disposes this PolyStack:
+         * clean up before reusing this instance
+         */
+        void dispose() {
+            end = 0;
+
+            if (DO_STATS) {
+                stat_idxstack_indices.add(indicesUseMark);
+                hist_idxstack_indices.add(indicesUseMark);
+
+                // reset marks
+                indicesUseMark = 0;
+            }
+
+            // Return arrays:
+            // values is kept dirty
+            indices = indices_ref.putArray(indices);
+        }
+
+        boolean isEmpty() {
+            return (end == 0);
+        }
+
+        void reset() {
+            end = 0;
+        }
+
+        void push(final int v) {
+            // remove redundant values (reverse order):
+            int[] _values = indices;
+            final int nc = end;
+            if (nc != 0) {
+                if (_values[nc - 1] == v) {
+                    // remove both duplicated values:
+                    end--;
+                    return;
+                }
+            }
+            if (_values.length <= nc) {
+                if (DO_STATS) {
+                    stat_array_idxstack_indices.add(nc + 1);
+                }
+                indices = _values = indices_ref.widenArray(_values, nc, nc + 1);
+            }
+            _values[end++] = v;
+
+            if (DO_STATS) {
+                // update used marks:
+                if (end > indicesUseMark) {
+                    indicesUseMark = end;
+                }
+            }
+        }
+
+        void pullAll(final float[] points, final PathConsumer2D io) {
+            final int nc = end;
+            if (nc == 0) {
+                return;
+            }
+            final int[] _values = indices;
+
+            for (int i = 0, j; i < nc; i++) {
+                j = _values[i] << 1;
+                io.lineTo(points[j], points[j + 1]);
+            }
+            end = 0;
+        }
+    }
 }
--- a/src/java.desktop/share/classes/sun/java2d/marlin/MarlinCache.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/MarlinCache.java	Mon Dec 11 21:14:43 2017 +0100
@@ -139,11 +139,7 @@
             // ie number of primitives:
 
             // fast check min and max width (maxx < 23bits):
-            if (width <= RLE_MIN_WIDTH || width >= RLE_MAX_WIDTH) {
-                useRLE = false;
-            } else {
-                useRLE = true;
-            }
+            useRLE = (width > RLE_MIN_WIDTH && width < RLE_MAX_WIDTH);
         }
 
         // the ceiling of (maxy - miny + 1) / TILE_SIZE;
--- a/src/java.desktop/share/classes/sun/java2d/marlin/MarlinConst.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/MarlinConst.java	Mon Dec 11 21:14:43 2017 +0100
@@ -128,4 +128,47 @@
 
     public static final int BLOCK_SIZE_LG = MarlinProperties.getBlockSize_Log2();
     public static final int BLOCK_SIZE    = 1 << BLOCK_SIZE_LG;
+
+    // Constants
+    public static final int WIND_EVEN_ODD = 0;
+    public static final int WIND_NON_ZERO = 1;
+
+    /**
+     * Constant value for join style.
+     */
+    public static final int JOIN_MITER = 0;
+
+    /**
+     * Constant value for join style.
+     */
+    public static final int JOIN_ROUND = 1;
+
+    /**
+     * Constant value for join style.
+     */
+    public static final int JOIN_BEVEL = 2;
+
+    /**
+     * Constant value for end cap style.
+     */
+    public static final int CAP_BUTT = 0;
+
+    /**
+     * Constant value for end cap style.
+     */
+    public static final int CAP_ROUND = 1;
+
+    /**
+     * Constant value for end cap style.
+     */
+    public static final int CAP_SQUARE = 2;
+
+    // Out codes
+    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 final int OUTCODE_MASK_T_B = OUTCODE_TOP  | OUTCODE_BOTTOM;
+    static final int OUTCODE_MASK_L_R = OUTCODE_LEFT | OUTCODE_RIGHT;
+    static final int OUTCODE_MASK_T_B_L_R = OUTCODE_MASK_T_B | OUTCODE_MASK_L_R;
 }
--- a/src/java.desktop/share/classes/sun/java2d/marlin/MarlinProperties.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/MarlinProperties.java	Mon Dec 11 21:14:43 2017 +0100
@@ -145,6 +145,18 @@
         return getBoolean("sun.java2d.renderer.useSimplifier", "false");
     }
 
+    public static boolean isDoClip() {
+        return getBoolean("sun.java2d.renderer.clip", "true");
+    }
+
+    public static boolean isDoClipRuntimeFlag() {
+        return getBoolean("sun.java2d.renderer.clip.runtime.enable", "false");
+    }
+
+    public static boolean isDoClipAtRuntime() {
+        return getBoolean("sun.java2d.renderer.clip.runtime", "true");
+    }
+
     // debugging parameters
 
     public static boolean isDoStats() {
--- a/src/java.desktop/share/classes/sun/java2d/marlin/MarlinRenderingEngine.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/MarlinRenderingEngine.java	Mon Dec 11 21:14:43 2017 +0100
@@ -85,6 +85,13 @@
     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_CLIP_FILL = true;
+
+    static final boolean DO_TRACE_PATH = false;
+
+    static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag();
+
     /**
      * Public constructor
      */
@@ -134,7 +141,7 @@
                      miterlimit,
                      dashes,
                      dashphase,
-                     rdrCtx.transformerPC2D.wrapPath2d(p2d)
+                     rdrCtx.transformerPC2D.wrapPath2D(p2d)
                     );
 
             // Use Path2D copy constructor (trim)
@@ -195,14 +202,14 @@
         }
     }
 
-    final void strokeTo(final RendererContext rdrCtx,
-                        Shape src,
-                        AffineTransform at,
-                        BasicStroke bs,
-                        boolean thin,
-                        NormMode normalize,
-                        boolean antialias,
-                        PathConsumer2D pc2d)
+    void strokeTo(final RendererContext rdrCtx,
+                  Shape src,
+                  AffineTransform at,
+                  BasicStroke bs,
+                  boolean thin,
+                  NormMode normalize,
+                  boolean antialias,
+                  PathConsumer2D pc2d)
     {
         float lw;
         if (thin) {
@@ -295,17 +302,17 @@
         return (lw / widthScale);
     }
 
-    final void strokeTo(final RendererContext rdrCtx,
-                        Shape src,
-                        AffineTransform at,
-                        float width,
-                        NormMode norm,
-                        int caps,
-                        int join,
-                        float miterlimit,
-                        float[] dashes,
-                        float dashphase,
-                        PathConsumer2D pc2d)
+    void strokeTo(final RendererContext rdrCtx,
+                  Shape src,
+                  AffineTransform at,
+                  float width,
+                  NormMode norm,
+                  int caps,
+                  int join,
+                  float miterlimit,
+                  float[] dashes,
+                  float dashphase,
+                  PathConsumer2D pc2d)
     {
         // We use strokerat so that in Stroker and Dasher we can work only
         // with the pre-transformation coordinates. This will repeat a lot of
@@ -324,6 +331,7 @@
 
         int dashLen = -1;
         boolean recycleDashes = false;
+        float scale = 1.0f;
 
         if (at != null && !at.isIdentity()) {
             final double a = at.getScaleX();
@@ -356,7 +364,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 +402,24 @@
             at = null;
         }
 
+        final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
+
+        if (DO_TRACE_PATH) {
+            // 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 +427,22 @@
             }
             pc2d = rdrCtx.dasher.init(pc2d, dashes, dashLen, dashphase,
                                       recycleDashes);
+        } else if (rdrCtx.doClip && (caps != Stroker.CAP_BUTT)) {
+            if (DO_TRACE_PATH) {
+                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_PATH) {
+            // trace Input:
+            pc2d = transformerPC2D.traceInput(pc2d);
+        }
+
         final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx,
                                          src.getPathIterator(at));
 
@@ -594,14 +623,12 @@
     }
 
     private static void pathTo(final RendererContext rdrCtx, final PathIterator pi,
-                               final PathConsumer2D pc2d)
+                               PathConsumer2D pc2d)
     {
         // mark context as DIRTY:
         rdrCtx.dirty = true;
 
-        final float[] coords = rdrCtx.float6;
-
-        pathToLoop(coords, pi, pc2d);
+        pathToLoop(rdrCtx.float6, pi, pc2d);
 
         // mark context as CLEAN:
         rdrCtx.dirty = false;
@@ -779,6 +806,19 @@
 
         final RendererContext rdrCtx = getRendererContext();
         try {
+            if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) {
+                // 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;
+            }
+
             // Test if at is identity:
             final AffineTransform _at = (at != null && !at.isIdentity()) ? at
                                         : null;
@@ -795,13 +835,29 @@
                                          clip.getWidth(), clip.getHeight(),
                                          pi.getWindingRule());
 
+                PathConsumer2D pc2d = r;
+
+                if (DO_CLIP_FILL && rdrCtx.doClip) {
+                    if (DO_TRACE_PATH) {
+                        // trace Filler:
+                        pc2d = rdrCtx.transformerPC2D.traceFiller(pc2d);
+                    }
+                    pc2d = rdrCtx.transformerPC2D.pathClipper(pc2d);
+                }
+
+                if (DO_TRACE_PATH) {
+                    // trace Input:
+                    pc2d = rdrCtx.transformerPC2D.traceInput(pc2d);
+                }
+
                 // TODO: subdivide quad/cubic curves into monotonic curves ?
-                pathTo(rdrCtx, pi, r);
+                pathTo(rdrCtx, pi, pc2d);
+
             } else {
                 // draw shape with given stroke:
                 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
                                          clip.getWidth(), clip.getHeight(),
-                                         PathIterator.WIND_NON_ZERO);
+                                         WIND_NON_ZERO);
 
                 strokeTo(rdrCtx, s, _at, bs, thin, norm, true, r);
             }
@@ -824,12 +880,12 @@
     }
 
     @Override
-    public final AATileGenerator getAATileGenerator(double x, double y,
-                                                    double dx1, double dy1,
-                                                    double dx2, double dy2,
-                                                    double lw1, double lw2,
-                                                    Region clip,
-                                                    int[] bbox)
+    public AATileGenerator getAATileGenerator(double x, double y,
+                                              double dx1, double dy1,
+                                              double dx2, double dy2,
+                                              double lw1, double lw2,
+                                              Region clip,
+                                              int[] bbox)
     {
         // REMIND: Deal with large coordinates!
         double ldx1, ldy1, ldx2, ldy2;
@@ -860,8 +916,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(),
+                                     WIND_EVEN_ODD);
 
             r.moveTo((float) x, (float) y);
             r.lineTo((float) (x+dx1), (float) (y+dy1));
@@ -913,14 +969,14 @@
     }
 
     static {
-        if (PathIterator.WIND_NON_ZERO != Renderer.WIND_NON_ZERO ||
-            PathIterator.WIND_EVEN_ODD != Renderer.WIND_EVEN_ODD ||
-            BasicStroke.JOIN_MITER != Stroker.JOIN_MITER ||
-            BasicStroke.JOIN_ROUND != Stroker.JOIN_ROUND ||
-            BasicStroke.JOIN_BEVEL != Stroker.JOIN_BEVEL ||
-            BasicStroke.CAP_BUTT != Stroker.CAP_BUTT ||
-            BasicStroke.CAP_ROUND != Stroker.CAP_ROUND ||
-            BasicStroke.CAP_SQUARE != Stroker.CAP_SQUARE)
+        if (PathIterator.WIND_NON_ZERO != WIND_NON_ZERO ||
+            PathIterator.WIND_EVEN_ODD != WIND_EVEN_ODD ||
+            BasicStroke.JOIN_MITER != JOIN_MITER ||
+            BasicStroke.JOIN_ROUND != JOIN_ROUND ||
+            BasicStroke.JOIN_BEVEL != JOIN_BEVEL ||
+            BasicStroke.CAP_BUTT != CAP_BUTT ||
+            BasicStroke.CAP_ROUND != CAP_ROUND ||
+            BasicStroke.CAP_SQUARE != CAP_SQUARE)
         {
             throw new InternalError("mismatched renderer constants");
         }
@@ -1046,6 +1102,11 @@
         logInfo("sun.java2d.renderer.useSimplifier    = "
                 + MarlinConst.USE_SIMPLIFIER);
 
+        logInfo("sun.java2d.renderer.clip             = "
+                + MarlinProperties.isDoClip());
+        logInfo("sun.java2d.renderer.clip.runtime.enable = "
+                + MarlinProperties.isDoClipRuntimeFlag());
+
         // debugging parameters
         logInfo("sun.java2d.renderer.doStats          = "
                 + MarlinConst.DO_STATS);
--- a/src/java.desktop/share/classes/sun/java2d/marlin/Renderer.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/Renderer.java	Mon Dec 11 21:14:43 2017 +0100
@@ -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.5f / SUBPIXEL_SCALE_X;
+    static final float RDR_OFFSET_Y = 0.5f / SUBPIXEL_SCALE_Y;
+
     // number of subpixels corresponding to a tile line
     private static final int SUBPIXEL_TILE
         = TILE_H << SUBPIXEL_LG_POSITIONS_Y;
@@ -58,9 +61,6 @@
     // crossing capacity = edges count / 4 ~ 1024
     static final int INITIAL_CROSSING_COUNT = INITIAL_EDGES_COUNT >> 2;
 
-    public static final int WIND_EVEN_ODD = 0;
-    public static final int WIND_NON_ZERO = 1;
-
     // common to all types of input path segments.
     // OFFSET as bytes
     // only integer values:
@@ -526,11 +526,11 @@
 
     Renderer(final RendererContext rdrCtx) {
         this.rdrCtx = rdrCtx;
+        this.curve = rdrCtx.curve;
+        this.cache = rdrCtx.cache;
 
         this.edges = rdrCtx.newOffHeapArray(INITIAL_EDGES_CAPACITY); // 96K
 
-        this.curve = rdrCtx.curve;
-
         edgeBuckets_ref      = rdrCtx.newCleanIntArrayRef(INITIAL_BUCKET_ARRAY); // 64K
         edgeBucketCounts_ref = rdrCtx.newCleanIntArrayRef(INITIAL_BUCKET_ARRAY); // 64K
 
@@ -541,8 +541,6 @@
         alphaLine_ref = rdrCtx.newCleanIntArrayRef(INITIAL_AA_ARRAY); // 8K
         alphaLine     = alphaLine_ref.initial;
 
-        this.cache = rdrCtx.cache;
-
         crossings_ref     = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K
         aux_crossings_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K
         edgePtrs_ref      = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K
@@ -672,7 +670,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 +681,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 +690,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 +717,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
--- a/src/java.desktop/share/classes/sun/java2d/marlin/RendererContext.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/RendererContext.java	Mon Dec 11 21:14:43 2017 +0100
@@ -75,16 +75,22 @@
     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 */
     private final IntArrayCache cleanIntCache = new IntArrayCache(true, 5);
-    /* dirty int[] cache = 4 refs */
-    private final IntArrayCache dirtyIntCache = new IntArrayCache(false, 4);
-    /* dirty float[] cache = 3 refs */
-    private final FloatArrayCache dirtyFloatCache = new FloatArrayCache(false, 3);
-    /* dirty byte[] cache = 1 ref */
-    private final ByteArrayCache dirtyByteCache = new ByteArrayCache(false, 1);
+    /* dirty int[] cache = 5 refs */
+    private final IntArrayCache dirtyIntCache = new IntArrayCache(false, 5);
+    /* dirty float[] cache = 4 refs (2 polystack) */
+    private final FloatArrayCache dirtyFloatCache = new FloatArrayCache(false, 4);
+    /* dirty byte[] cache = 2 ref (2 polystack) */
+    private final ByteArrayCache dirtyByteCache = new ByteArrayCache(false, 2);
 
     // RendererContext statistics
     final RendererStats stats;
@@ -116,7 +122,7 @@
         nPQPathIterator  = new NormalizingPathIterator.NearestPixelQuarter(float6);
 
         // MarlinRenderingEngine.TransformingPathConsumer2D
-        transformerPC2D = new TransformingPathConsumer2D();
+        transformerPC2D = new TransformingPathConsumer2D(this);
 
         // Renderer:
         cache = new MarlinCache(this);
@@ -138,7 +144,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:
@@ -159,12 +168,11 @@
 
     Path2D.Float getPath2D() {
         // resolve reference:
-        Path2D.Float p2d
-            = (refPath2D != null) ? refPath2D.get() : null;
+        Path2D.Float p2d = (refPath2D != null) ? refPath2D.get() : null;
 
         // create a new Path2D ?
         if (p2d == null) {
-            p2d = new Path2D.Float(Path2D.WIND_NON_ZERO, INITIAL_EDGES_COUNT); // 32K
+            p2d = new Path2D.Float(WIND_NON_ZERO, INITIAL_EDGES_COUNT); // 32K
 
             // update weak reference:
             refPath2D = new WeakReference<Path2D.Float>(p2d);
--- a/src/java.desktop/share/classes/sun/java2d/marlin/RendererStats.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/RendererStats.java	Mon Dec 11 21:14:43 2017 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -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,21 @@
         = 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");
+    final StatLong stat_pcf_idxstack_indices
+        = new StatLong("pathClipFilter.stack.indices");
     // 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 +135,19 @@
         = 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");
+    final StatLong stat_array_pcf_idxstack_indices
+        = new StatLong("array.pathClipFilter.stack.indices.d_int");
     // 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 +158,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 +170,15 @@
         = 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");
+    final Histogram hist_pcf_idxstack_indices
+        = new Histogram("pathClipFilter.stack.indices");
     // 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 +197,12 @@
         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,
+        stat_pcf_idxstack_indices,
         hist_rdr_edges_count,
-        hist_rdr_poly_stack_curves,
         hist_rdr_crossings,
         hist_rdr_crossings_ratio,
         hist_rdr_crossings_adds,
@@ -195,10 +213,11 @@
         hist_tile_generator_encoding_dist,
         hist_tile_generator_encoding_ratio,
         hist_tile_generator_encoding_runLen,
+        hist_str_polystack_curves,
+        hist_cpd_polystack_curves,
+        hist_pcf_idxstack_indices,
         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 +226,12 @@
         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,
+        stat_array_pcf_idxstack_indices
     };
     // monitors
     final Monitor mon_pre_getAATileGenerator
--- a/src/java.desktop/share/classes/sun/java2d/marlin/Stroker.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/Stroker.java	Mon Dec 11 21:14:43 2017 +0100
@@ -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
@@ -38,42 +39,16 @@
     private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
     private static final int CLOSE = 2;
 
-    /**
-     * Constant value for join style.
-     */
-    public static final int JOIN_MITER = 0;
-
-    /**
-     * Constant value for join style.
-     */
-    public static final int JOIN_ROUND = 1;
-
-    /**
-     * Constant value for join style.
-     */
-    public static final int JOIN_BEVEL = 2;
-
-    /**
-     * Constant value for end cap style.
-     */
-    public static final int CAP_BUTT = 0;
-
-    /**
-     * Constant value for end cap style.
-     */
-    public static final int CAP_ROUND = 1;
-
-    /**
-     * Constant value for end cap style.
-     */
-    public static final int CAP_SQUARE = 2;
-
     // pisces used to use fixed point arithmetic with 16 decimal digits. I
     // didn't want to change the values of the constant below when I converted
     // 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 +95,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 <code>Stroker</code>.
      * @param rdrCtx per-thread renderer context
@@ -127,7 +116,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 +140,15 @@
      * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
      * <code>JOIN_BEVEL</code>.
      * @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 +157,45 @@
         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;
+            }
+            // add a small rounding error:
+            margin += 1e-3f;
+
+            // 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;
+            this.cOutCode = 0;
+            this.sOutCode = 0;
+        }
         return this; // fluent API
     }
 
@@ -175,6 +206,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 +479,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;
         }
-        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;
+    }
+
+    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;
+        }
     }
 
     @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 +544,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 +552,65 @@
         emitLineToRev(cx0 - mx, cy0 - my);
         emitLineToRev( x1 - mx,  y1 - my);
 
+        this.prev = DRAWING_OP_TO;
+        this.cx0 = x1;
+        this.cy0 = y1;
+        this.cdx = dx;
+        this.cdy = dy;
         this.cmx = mx;
         this.cmy = my;
-        this.cdx = dx;
-        this.cdy = dy;
-        this.cx0 = x1;
-        this.cy0 = y1;
-        this.prev = DRAWING_OP_TO;
     }
 
     @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);
+        // basic acceptance criteria
+        if ((sOutCode & cOutCode) == 0) {
+            if (cx0 != sx0 || cy0 != sy0) {
+                lineTo(sx0, sy0, true);
+            }
+
+            drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, sOutCode);
+
+            emitLineTo(sx0 + smx, sy0 + smy);
+
+            if (opened) {
+                emitLineTo(sx0 - smx, sy0 - smy);
+            } else {
+                emitMoveTo(sx0 - smx, sy0 - smy);
+            }
         }
-
-        drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy);
-
-        emitLineTo(sx0 + smx, sy0 + smy);
-
-        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 +620,7 @@
     @Override
     public void pathDone() {
         if (prev == DRAWING_OP_TO) {
-            finish();
+            finish(cOutCode);
         }
 
         out.pathDone();
@@ -534,23 +633,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();
+
+            if (!capStart) {
+                capStart = true;
+
+                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();
         }
-
-        emitReverse();
-
-        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);
-        }
-
         emitClose();
     }
 
@@ -622,23 +737,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 +1063,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 ((outcode0 & 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 +1094,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 +1120,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 +1142,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 +1177,36 @@
             emitLineToRev(r[kind - 2], r[kind - 1]);
         }
 
+        this.prev = DRAWING_OP_TO;
+        this.cx0 = xf;
+        this.cy0 = yf;
+        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;
-        this.cdx = dxf;
-        this.cdy = dyf;
-        this.cx0 = xf;
-        this.cy0 = yf;
-        this.prev = DRAWING_OP_TO;
     }
 
-    @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 ((outcode0 & 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 +1214,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 +1225,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 +1246,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 +1281,16 @@
             emitLineToRev(r[kind - 2], r[kind - 1]);
         }
 
+        this.prev = DRAWING_OP_TO;
+        this.cx0 = xf;
+        this.cy0 = yf;
+        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;
-        this.cdx = dxf;
-        this.cdy = dyf;
-        this.cx0 = xf;
-        this.cy0 = yf;
-        this.prev = DRAWING_OP_TO;
     }
 
     @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;
-        }
-    }
 }
--- a/src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java	Mon Dec 11 21:14:43 2017 +0100
@@ -28,50 +28,169 @@
 import sun.awt.geom.PathConsumer2D;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Path2D;
+import sun.java2d.marlin.Helpers.IndexStack;
+import sun.java2d.marlin.Helpers.PolyStack;
 
 final class TransformingPathConsumer2D {
 
-    TransformingPathConsumer2D() {
-        // used by RendererContext
-    }
+    private final RendererContext rdrCtx;
 
-    // recycled PathConsumer2D instance from wrapPath2d()
+    // recycled ClosedPathDetector instance from detectClosedPath()
+    private final ClosedPathDetector   cpDetector;
+
+    // recycled PathClipFilter instance from pathClipper()
+    private final PathClipFilter       pathClipper;
+
+    // recycled PathConsumer2D instance from wrapPath2D()
     private final Path2DWrapper        wp_Path2DWrapper        = new Path2DWrapper();
 
-    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();
 
+    // 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 tracerFiller     = new PathTracer("Filler");
+    private final PathTracer tracerStroker    = new PathTracer("Stroker");
+
+    TransformingPathConsumer2D(final RendererContext rdrCtx) {
+        // used by RendererContext
+        this.rdrCtx = rdrCtx;
+        this.cpDetector = new ClosedPathDetector(rdrCtx);
+        this.pathClipper = new PathClipFilter(rdrCtx);
+    }
+
+    PathConsumer2D wrapPath2D(Path2D.Float p2d) {
+        return wp_Path2DWrapper.init(p2d);
+    }
+
+    PathConsumer2D traceInput(PathConsumer2D out) {
+        return tracerInput.init(out);
+    }
+
+    PathConsumer2D traceClosedPathDetector(PathConsumer2D out) {
+        return tracerCPDetector.init(out);
+    }
+
+    PathConsumer2D traceFiller(PathConsumer2D out) {
+        return tracerFiller.init(out);
+    }
+
+    PathConsumer2D traceStroker(PathConsumer2D out) {
+        return tracerStroker.init(out);
+    }
+
+    PathConsumer2D detectClosedPath(PathConsumer2D out) {
+        return cpDetector.init(out);
+    }
+
+    PathConsumer2D pathClipper(PathConsumer2D out) {
+        return pathClipper.init(out);
+    }
+
     PathConsumer2D deltaTransformConsumer(PathConsumer2D out,
                                           AffineTransform at)
     {
         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 +210,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 +219,6 @@
         }
     }
 
-
     static final class DeltaScaleFilter implements PathConsumer2D {
         private PathConsumer2D out;
         private float sx, sy;
@@ -275,4 +393,427 @@
             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 PathClipFilter implements PathConsumer2D {
+
+        private PathConsumer2D out;
+
+        // Bounds of the drawing region, at pixel precision.
+        private final float[] clipRect;
+
+        private final float[] corners = new float[8];
+        private boolean init_corners = false;
+
+        private final IndexStack stack;
+
+        // the current outcode of the current sub path
+        private int cOutCode = 0;
+
+        // the cumulated (and) outcode of the complete path
+        private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
+
+        private boolean outside = false;
+
+        // The current point OUTSIDE
+        private float cx0, cy0;
+
+        PathClipFilter(final RendererContext rdrCtx) {
+            this.clipRect = rdrCtx.clipRect;
+            this.stack = (rdrCtx.stats != null) ?
+                new IndexStack(rdrCtx,
+                        rdrCtx.stats.stat_pcf_idxstack_indices,
+                        rdrCtx.stats.hist_pcf_idxstack_indices,
+                        rdrCtx.stats.stat_array_pcf_idxstack_indices)
+                : new IndexStack(rdrCtx);
+        }
+
+        PathClipFilter init(final PathConsumer2D out) {
+            this.out = out;
+
+            // Adjust the clipping rectangle with the renderer offsets
+            final float rdrOffX = Renderer.RDR_OFFSET_X;
+            final float rdrOffY = Renderer.RDR_OFFSET_Y;
+
+            // add a small rounding error:
+            final float margin = 1e-3f;
+
+            final float[] _clipRect = this.clipRect;
+            _clipRect[0] -= margin - rdrOffY;
+            _clipRect[1] += margin + rdrOffY;
+            _clipRect[2] -= margin - rdrOffX;
+            _clipRect[3] += margin + rdrOffX;
+
+            this.init_corners = true;
+            this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
+
+            return this; // fluent API
+        }
+
+        /**
+         * Disposes this instance:
+         * clean up before reusing this instance
+         */
+        void dispose() {
+            stack.dispose();
+        }
+
+        private void finishPath() {
+            if (outside) {
+                // criteria: inside or totally outside ?
+                if (gOutCode == 0) {
+                    finish();
+                } else {
+                    this.outside = false;
+                    stack.reset();
+                }
+            }
+        }
+
+        private void finish() {
+            this.outside = false;
+
+            if (!stack.isEmpty()) {
+                if (init_corners) {
+                    init_corners = false;
+
+                    final float[] _corners = corners;
+                    final float[] _clipRect = clipRect;
+                    // Top Left (0):
+                    _corners[0] = _clipRect[2];
+                    _corners[1] = _clipRect[0];
+                    // Bottom Left (1):
+                    _corners[2] = _clipRect[2];
+                    _corners[3] = _clipRect[1];
+                    // Top right (2):
+                    _corners[4] = _clipRect[3];
+                    _corners[5] = _clipRect[0];
+                    // Bottom Right (3):
+                    _corners[6] = _clipRect[3];
+                    _corners[7] = _clipRect[1];
+                }
+                stack.pullAll(corners, out);
+            }
+            out.lineTo(cx0, cy0);
+        }
+
+        @Override
+        public void pathDone() {
+            finishPath();
+
+            out.pathDone();
+
+            // TODO: fix possible leak if exception happened
+            // Dispose this instance:
+            dispose();
+        }
+
+        @Override
+        public void closePath() {
+            finishPath();
+
+            out.closePath();
+        }
+
+        @Override
+        public void moveTo(final float x0, final float y0) {
+            finishPath();
+
+            final int outcode = Helpers.outcode(x0, y0, clipRect);
+            this.cOutCode = outcode;
+            this.outside = false;
+            out.moveTo(x0, y0);
+        }
+
+        @Override
+        public void lineTo(final float xe, final float ye) {
+            final int outcode0 = this.cOutCode;
+            final int outcode1 = Helpers.outcode(xe, ye, clipRect);
+            this.cOutCode = outcode1;
+
+            final int sideCode = (outcode0 & outcode1);
+
+            // basic rejection criteria:
+            if (sideCode == 0) {
+                this.gOutCode = 0;
+            } else {
+                this.gOutCode &= sideCode;
+                // keep last point coordinate before entering the clip again:
+                this.outside = true;
+                this.cx0 = xe;
+                this.cy0 = ye;
+
+                clip(sideCode, outcode0, outcode1);
+                return;
+            }
+            if (outside) {
+                finish();
+            }
+            // clipping disabled:
+            out.lineTo(xe, ye);
+        }
+
+        private void clip(final int sideCode,
+                          final int outcode0,
+                          final int outcode1)
+        {
+            // corner or cross-boundary on left or right side:
+            if ((outcode0 != outcode1)
+                    && ((sideCode & MarlinConst.OUTCODE_MASK_L_R) != 0))
+            {
+                // combine outcodes:
+                final int mergeCode = (outcode0 | outcode1);
+                final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B;
+                final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R;
+                final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2;
+
+                // add corners to outside stack:
+                switch (tbCode) {
+                    case MarlinConst.OUTCODE_TOP:
+// System.out.println("TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
+                        stack.push(off); // top
+                        return;
+                    case MarlinConst.OUTCODE_BOTTOM:
+// System.out.println("BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
+                        stack.push(off + 1); // bottom
+                        return;
+                    default:
+                        // both TOP / BOTTOM:
+                        if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {
+// System.out.println("TOP + BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
+                            // top to bottom
+                            stack.push(off); // top
+                            stack.push(off + 1); // bottom
+                        } else {
+// System.out.println("BOTTOM + TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
+                            // bottom to top
+                            stack.push(off + 1); // bottom
+                            stack.push(off); // top
+                        }
+                }
+            }
+        }
+
+        @Override
+        public void curveTo(final float x1, final float y1,
+                            final float x2, final float y2,
+                            final float xe, final float ye)
+        {
+            final int outcode0 = this.cOutCode;
+            final int outcode3 = Helpers.outcode(xe, ye, clipRect);
+            this.cOutCode = outcode3;
+
+            int sideCode = outcode0 & outcode3;
+
+            if (sideCode == 0) {
+                this.gOutCode = 0;
+            } else {
+                sideCode &= Helpers.outcode(x1, y1, clipRect);
+                sideCode &= Helpers.outcode(x2, y2, clipRect);
+                this.gOutCode &= sideCode;
+
+                // basic rejection criteria:
+                if (sideCode != 0) {
+                    // keep last point coordinate before entering the clip again:
+                    this.outside = true;
+                    this.cx0 = xe;
+                    this.cy0 = ye;
+
+                    clip(sideCode, outcode0, outcode3);
+                    return;
+                }
+            }
+            if (outside) {
+                finish();
+            }
+            // clipping disabled:
+            out.curveTo(x1, y1, x2, y2, xe, ye);
+        }
+
+        @Override
+        public void quadTo(final float x1, final float y1,
+                           final float xe, final float ye)
+        {
+            final int outcode0 = this.cOutCode;
+            final int outcode2 = Helpers.outcode(xe, ye, clipRect);
+            this.cOutCode = outcode2;
+
+            int sideCode = outcode0 & outcode2;
+
+            if (sideCode == 0) {
+                this.gOutCode = 0;
+            } else {
+                sideCode &= Helpers.outcode(x1, y1, clipRect);
+                this.gOutCode &= sideCode;
+
+                // basic rejection criteria:
+                if (sideCode != 0) {
+                    // keep last point coordinate before entering the clip again:
+                    this.outside = true;
+                    this.cx0 = xe;
+                    this.cy0 = ye;
+
+                    clip(sideCode, outcode0, outcode2);
+                    return;
+                }
+            }
+            if (outside) {
+                finish();
+            }
+            // clipping disabled:
+            out.quadTo(x1, y1, xe, ye);
+        }
+
+        @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");
+        }
+    }
 }
--- a/src/java.desktop/share/classes/sun/java2d/marlin/Version.java	Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/Version.java	Mon Dec 11 21:14:43 2017 +0100
@@ -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.2-Unsafe-OpenJDK";
 
     public static String getVersion() {
         return VERSION;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/sun/java2d/marlin/ClipShapeTest.java	Mon Dec 11 21:14:43 2017 +0100
@@ -0,0 +1,865 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.PathIterator;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.stream.ImageOutputStream;
+
+/**
+ * @test
+ * @bug 8191814
+ * @summary Verifies that Marlin rendering generates the same
+ * images with and without clipping optimization with all possible
+ * stroke (cap/join) and fill modes (EO rules)
+ * Note: Use the argument -slow to run more intensive tests (too much time)
+ * @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine ClipShapeTest
+ * @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.DMarlinRenderingEngine ClipShapeTest
+ */
+public final class ClipShapeTest {
+
+    static final boolean TEST_STROKER = true;
+    static final boolean TEST_FILLER = true;
+
+    // complementary tests in slow mode:
+    static boolean USE_DASHES = false;
+    static boolean USE_VAR_STROKE = false;
+
+    static int NUM_TESTS = 5000;
+    static final int TESTW = 100;
+    static final int TESTH = 100;
+
+    // shape settings:
+    static final ShapeMode SHAPE_MODE = ShapeMode.NINE_LINE_POLYS;
+    static final boolean SHAPE_REPEAT = true;
+
+    // dump path on console:
+    static final boolean DUMP_SHAPE = true;
+
+    static final boolean SHOW_DETAILS = true;
+    static final boolean SHOW_OUTLINE = true;
+    static final boolean SHOW_POINTS = true;
+    static final boolean SHOW_INFO = false;
+
+    static final int MAX_SHOW_FRAMES = 10;
+
+    // use fixed seed to reproduce always same polygons between tests
+    static final boolean FIXED_SEED = false;
+    static final double RAND_SCALE = 3.0;
+    static final double RANDW = TESTW * RAND_SCALE;
+    static final double OFFW = (TESTW - RANDW) / 2.0;
+    static final double RANDH = TESTH * RAND_SCALE;
+    static final double OFFH = (TESTH - RANDH) / 2.0;
+
+    static enum ShapeMode {
+        TWO_CUBICS,
+        FOUR_QUADS,
+        FIVE_LINE_POLYS,
+        NINE_LINE_POLYS,
+        FIFTY_LINE_POLYS,
+        MIXED
+    }
+
+    static final long SEED = 1666133789L;
+    // Fixed seed to avoid any difference between runs:
+    static final Random RANDOM = new Random(SEED);
+
+    static final File OUTPUT_DIR = new File(".");
+
+    /**
+     * Test
+     * @param args
+     */
+    public static void main(String[] args) {
+        boolean runSlowTests = (args.length != 0 && "-slow".equals(args[0]));
+
+        if (runSlowTests) {
+            NUM_TESTS = 20000; // or 100000 (very slow)
+            USE_DASHES = true;
+            USE_VAR_STROKE = true;
+        }
+
+        Locale.setDefault(Locale.US);
+
+        // Get Marlin runtime state from its log:
+        final AtomicBoolean isMarlin = new AtomicBoolean();
+        final AtomicBoolean isClipRuntime = new AtomicBoolean();
+
+        // initialize j.u.l Looger:
+        final Logger log = Logger.getLogger("sun.java2d.marlin");
+        log.addHandler(new Handler() {
+            @Override
+            public void publish(LogRecord record) {
+                final String msg = record.getMessage();
+                if (msg != null) {
+                    // last space to avoid matching other settings:
+                    if (msg.startsWith("sun.java2d.renderer ")) {
+                        isMarlin.set(msg.contains("MarlinRenderingEngine"));
+                    }
+                    if (msg.startsWith("sun.java2d.renderer.clip.runtime.enable")) {
+                        isClipRuntime.set(msg.contains("true"));
+                    }
+                }
+
+                final Throwable th = record.getThrown();
+                // detect any Throwable:
+                if (th != null) {
+                    System.out.println("Test failed:\n" + record.getMessage());
+                    th.printStackTrace(System.out);
+
+                    throw new RuntimeException("Test failed: ", th);
+                }
+            }
+
+            @Override
+            public void flush() {
+            }
+
+            @Override
+            public void close() throws SecurityException {
+            }
+        });
+
+        // enable Marlin logging & internal checks:
+        System.setProperty("sun.java2d.renderer.log", "true");
+        System.setProperty("sun.java2d.renderer.useLogger", "true");
+
+        // disable static clipping setting:
+        System.setProperty("sun.java2d.renderer.clip", "false");
+        System.setProperty("sun.java2d.renderer.clip.runtime.enable", "true");
+
+        System.out.println("ClipShapeTests: image = " + TESTW + " x " + TESTH);
+
+        int failures = 0;
+        final long start = System.nanoTime();
+        try {
+            // TODO: test affine transforms ?
+
+            if (TEST_STROKER) {
+                final float[][] dashArrays = (USE_DASHES)
+                        ? new float[][]{null, new float[]{1f, 2f}}
+                        : new float[][]{null};
+
+                System.out.println("dashes: " + Arrays.toString(dashArrays));
+
+                final float[] strokeWidths = (USE_VAR_STROKE)
+                        ? new float[5] : new float[]{8f};
+
+                int nsw = 0;
+                if (USE_VAR_STROKE) {
+                    for (float width = 0.1f; width < 110f; width *= 5f) {
+                        strokeWidths[nsw++] = width;
+                    }
+                } else {
+                    nsw = 1;
+                }
+
+                System.out.println("stroke widths: " + Arrays.toString(strokeWidths));
+
+                // Stroker tests:
+                for (int w = 0; w < nsw; w++) {
+                    final float width = strokeWidths[w];
+
+                    for (float[] dashes : dashArrays) {
+
+                        for (int cap = 0; cap <= 2; cap++) {
+
+                            for (int join = 0; join <= 2; join++) {
+
+                                failures += paintPaths(new TestSetup(SHAPE_MODE, false, width, cap, join, dashes));
+                                failures += paintPaths(new TestSetup(SHAPE_MODE, true, width, cap, join, dashes));
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (TEST_FILLER) {
+                // Filler tests:
+                failures += paintPaths(new TestSetup(SHAPE_MODE, false, Path2D.WIND_NON_ZERO));
+                failures += paintPaths(new TestSetup(SHAPE_MODE, true, Path2D.WIND_NON_ZERO));
+
+                failures += paintPaths(new TestSetup(SHAPE_MODE, false, Path2D.WIND_EVEN_ODD));
+                failures += paintPaths(new TestSetup(SHAPE_MODE, true, Path2D.WIND_EVEN_ODD));
+            }
+        } catch (IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+        System.out.println("main: duration= " + (1e-6 * (System.nanoTime() - start)) + " ms.");
+
+        if (!isMarlin.get()) {
+            throw new RuntimeException("Marlin renderer not used at runtime !");
+        }
+        if (!isClipRuntime.get()) {
+            throw new RuntimeException("Marlin clipping not enabled at runtime !");
+        }
+        if (failures != 0) {
+            throw new RuntimeException("Clip test failures : " + failures);
+        }
+    }
+
+    static int paintPaths(final TestSetup ts) throws IOException {
+        final long start = System.nanoTime();
+
+        if (FIXED_SEED) {
+            // Reset seed for random numbers:
+            RANDOM.setSeed(SEED);
+        }
+
+        System.out.println("paintPaths: " + NUM_TESTS
+                + " paths (" + SHAPE_MODE + ") - setup: " + ts);
+
+        final boolean fill = !ts.isStroke();
+        final Path2D p2d = new Path2D.Double(ts.windingRule);
+
+        final BufferedImage imgOn = newImage(TESTW, TESTH);
+        final Graphics2D g2dOn = initialize(imgOn, ts);
+
+        final BufferedImage imgOff = newImage(TESTW, TESTH);
+        final Graphics2D g2dOff = initialize(imgOff, ts);
+
+        final BufferedImage imgDiff = newImage(TESTW, TESTH);
+
+        final DiffContext globalCtx = new DiffContext("All tests");
+
+        int nd = 0;
+        try {
+            final DiffContext testCtx = new DiffContext("Test");
+            BufferedImage diffImage;
+
+            for (int n = 0; n < NUM_TESTS; n++) {
+                genShape(p2d, ts);
+
+                // Runtime clip setting OFF:
+                paintShape(p2d, g2dOff, fill, false);
+
+                // Runtime clip setting ON:
+                paintShape(p2d, g2dOn, fill, true);
+
+                /* compute image difference if possible */
+                diffImage = computeDiffImage(testCtx, imgOn, imgOff, imgDiff, globalCtx);
+
+                final String testName = "Setup_" + ts.id + "_test_" + n;
+
+                if (diffImage != null) {
+                    nd++;
+
+                    final double ratio = (100.0 * testCtx.histPix.count) / testCtx.histAll.count;
+                    System.out.println("Diff ratio: " + testName + " = " + trimTo3Digits(ratio) + " %");
+
+                    if (false) {
+                        saveImage(diffImage, OUTPUT_DIR, testName + "-diff.png");
+                    }
+
+                    if (DUMP_SHAPE) {
+                        dumpShape(p2d);
+                    }
+                    if (nd < MAX_SHOW_FRAMES) {
+                        if (SHOW_DETAILS) {
+                            paintShapeDetails(g2dOff, p2d);
+                            paintShapeDetails(g2dOn, p2d);
+                        }
+
+                        saveImage(imgOff, OUTPUT_DIR, testName + "-off.png");
+                        saveImage(imgOn, OUTPUT_DIR, testName + "-on.png");
+                        saveImage(diffImage, OUTPUT_DIR, testName + "-diff.png");
+                    }
+                }
+            }
+        } finally {
+            g2dOff.dispose();
+            g2dOn.dispose();
+
+            if (nd != 0) {
+                System.out.println("paintPaths: " + NUM_TESTS + " paths - "
+                        + "Number of differences = " + nd
+                        + " ratio = " + (100f * nd) / NUM_TESTS + " %");
+            }
+
+            globalCtx.dump();
+        }
+        System.out.println("paintPaths: duration= " + (1e-6 * (System.nanoTime() - start)) + " ms.");
+        return nd;
+    }
+
+    private static void paintShape(final Path2D p2d, final Graphics2D g2d,
+                                   final boolean fill, final boolean clip) {
+        reset(g2d);
+
+        setClip(g2d, clip);
+
+        if (fill) {
+            g2d.fill(p2d);
+        } else {
+            g2d.draw(p2d);
+        }
+    }
+
+    private static Graphics2D initialize(final BufferedImage img,
+                                         final TestSetup ts) {
+        final Graphics2D g2d = (Graphics2D) img.getGraphics();
+        g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
+                RenderingHints.VALUE_RENDER_QUALITY);
+        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+                RenderingHints.VALUE_STROKE_PURE);
+
+        if (ts.isStroke()) {
+            g2d.setStroke(createStroke(ts));
+        }
+        g2d.setColor(Color.GRAY);
+
+        return g2d;
+    }
+
+    private static void reset(final Graphics2D g2d) {
+        // Disable antialiasing:
+        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                RenderingHints.VALUE_ANTIALIAS_OFF);
+        g2d.setBackground(Color.WHITE);
+        g2d.clearRect(0, 0, TESTW, TESTH);
+    }
+
+    private static void setClip(final Graphics2D g2d, final boolean clip) {
+        // Enable antialiasing:
+        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                RenderingHints.VALUE_ANTIALIAS_ON);
+
+        // Enable or Disable clipping:
+        System.setProperty("sun.java2d.renderer.clip.runtime", (clip) ? "true" : "false");
+    }
+
+    static void genShape(final Path2D p2d, final TestSetup ts) {
+        p2d.reset();
+
+        final int end = (SHAPE_REPEAT) ? 2 : 1;
+
+        for (int p = 0; p < end; p++) {
+            p2d.moveTo(randX(), randY());
+
+            switch (ts.shapeMode) {
+                case MIXED:
+                case FIFTY_LINE_POLYS:
+                case NINE_LINE_POLYS:
+                case FIVE_LINE_POLYS:
+                    p2d.lineTo(randX(), randY());
+                    p2d.lineTo(randX(), randY());
+                    p2d.lineTo(randX(), randY());
+                    p2d.lineTo(randX(), randY());
+                    if (ts.shapeMode == ShapeMode.FIVE_LINE_POLYS) {
+                        // And an implicit close makes 5 lines
+                        break;
+                    }
+                    p2d.lineTo(randX(), randY());
+                    p2d.lineTo(randX(), randY());
+                    p2d.lineTo(randX(), randY());
+                    p2d.lineTo(randX(), randY());
+                    if (ts.shapeMode == ShapeMode.NINE_LINE_POLYS) {
+                        // And an implicit close makes 9 lines
+                        break;
+                    }
+                    if (ts.shapeMode == ShapeMode.FIFTY_LINE_POLYS) {
+                        for (int i = 0; i < 41; i++) {
+                            p2d.lineTo(randX(), randY());
+                        }
+                        // And an implicit close makes 50 lines
+                        break;
+                    }
+                case TWO_CUBICS:
+                    p2d.curveTo(randX(), randY(), randX(), randY(), randX(), randY());
+                    p2d.curveTo(randX(), randY(), randX(), randY(), randX(), randY());
+                    if (ts.shapeMode == ShapeMode.TWO_CUBICS) {
+                        break;
+                    }
+                case FOUR_QUADS:
+                    p2d.quadTo(randX(), randY(), randX(), randY());
+                    p2d.quadTo(randX(), randY(), randX(), randY());
+                    p2d.quadTo(randX(), randY(), randX(), randY());
+                    p2d.quadTo(randX(), randY(), randX(), randY());
+                    if (ts.shapeMode == ShapeMode.FOUR_QUADS) {
+                        break;
+                    }
+                default:
+            }
+
+            if (ts.closed) {
+                p2d.closePath();
+            }
+        }
+    }
+
+    static final float POINT_RADIUS = 2f;
+    static final float LINE_WIDTH = 1f;
+
+    static final Stroke OUTLINE_STROKE = new BasicStroke(LINE_WIDTH);
+    static final int COLOR_ALPHA = 128;
+    static final Color COLOR_MOVETO = new Color(255, 0, 0, COLOR_ALPHA);
+    static final Color COLOR_LINETO_ODD = new Color(0, 0, 255, COLOR_ALPHA);
+    static final Color COLOR_LINETO_EVEN = new Color(0, 255, 0, COLOR_ALPHA);
+
+    static final Ellipse2D.Float ELL_POINT = new Ellipse2D.Float();
+
+    private static void paintShapeDetails(final Graphics2D g2d, final Shape shape) {
+
+        final Stroke oldStroke = g2d.getStroke();
+        final Color oldColor = g2d.getColor();
+
+        setClip(g2d, false);
+
+        if (SHOW_OUTLINE) {
+            g2d.setStroke(OUTLINE_STROKE);
+            g2d.setColor(COLOR_LINETO_ODD);
+            g2d.draw(shape);
+        }
+
+        final float[] coords = new float[6];
+        float px, py;
+
+        int nMove = 0;
+        int nLine = 0;
+        int n = 0;
+
+        for (final PathIterator it = shape.getPathIterator(null); !it.isDone(); it.next()) {
+            int type = it.currentSegment(coords);
+            switch (type) {
+                case PathIterator.SEG_MOVETO:
+                    if (SHOW_POINTS) {
+                        g2d.setColor(COLOR_MOVETO);
+                    }
+                    break;
+                case PathIterator.SEG_LINETO:
+                    if (SHOW_POINTS) {
+                        g2d.setColor((nLine % 2 == 0) ? COLOR_LINETO_ODD : COLOR_LINETO_EVEN);
+                    }
+                    nLine++;
+                    break;
+                case PathIterator.SEG_CLOSE:
+                    continue;
+                default:
+                    System.out.println("unsupported segment type= " + type);
+                    continue;
+            }
+            px = coords[0];
+            py = coords[1];
+
+            if (SHOW_INFO) {
+                System.out.println("point[" + (n++) + "|seg=" + type + "]: " + px + " " + py);
+            }
+
+            if (SHOW_POINTS) {
+                ELL_POINT.setFrame(px - POINT_RADIUS, py - POINT_RADIUS,
+                        POINT_RADIUS * 2f, POINT_RADIUS * 2f);
+                g2d.fill(ELL_POINT);
+            }
+        }
+        if (SHOW_INFO) {
+            System.out.println("Path moveTo=" + nMove + ", lineTo=" + nLine);
+            System.out.println("--------------------------------------------------");
+        }
+
+        g2d.setStroke(oldStroke);
+        g2d.setColor(oldColor);
+    }
+
+    private static void dumpShape(final Shape shape) {
+        final float[] coords = new float[6];
+
+        for (final PathIterator it = shape.getPathIterator(null); !it.isDone(); it.next()) {
+            final int type = it.currentSegment(coords);
+            switch (type) {
+                case PathIterator.SEG_MOVETO:
+                    System.out.println("p2d.moveTo(" + coords[0] + ", " + coords[1] + ");");
+                    break;
+                case PathIterator.SEG_LINETO:
+                    System.out.println("p2d.lineTo(" + coords[0] + ", " + coords[1] + ");");
+                    break;
+                case PathIterator.SEG_CLOSE:
+                    System.out.println("p2d.closePath();");
+                    break;
+                default:
+                    System.out.println("// Unsupported segment type= " + type);
+            }
+        }
+        System.out.println("--------------------------------------------------");
+    }
+
+    static double randX() {
+        return RANDOM.nextDouble() * RANDW + OFFW;
+    }
+
+    static double randY() {
+        return RANDOM.nextDouble() * RANDH + OFFH;
+    }
+
+    private static BasicStroke createStroke(final TestSetup ts) {
+        return new BasicStroke(ts.strokeWidth, ts.strokeCap, ts.strokeJoin, 10.0f, ts.dashes, 0.0f);
+    }
+
+    private final static class TestSetup {
+
+        static final AtomicInteger COUNT = new AtomicInteger();
+
+        final int id;
+        final ShapeMode shapeMode;
+        final boolean closed;
+        // stroke
+        final float strokeWidth;
+        final int strokeCap;
+        final int strokeJoin;
+        final float[] dashes;
+        // fill
+        final int windingRule;
+
+        TestSetup(ShapeMode shapeMode, final boolean closed,
+                  final float strokeWidth, final int strokeCap, final int strokeJoin, final float[] dashes) {
+            this.id = COUNT.incrementAndGet();
+            this.shapeMode = shapeMode;
+            this.closed = closed;
+            this.strokeWidth = strokeWidth;
+            this.strokeCap = strokeCap;
+            this.strokeJoin = strokeJoin;
+            this.dashes = dashes;
+            this.windingRule = Path2D.WIND_NON_ZERO;
+        }
+
+        TestSetup(ShapeMode shapeMode, final boolean closed, final int windingRule) {
+            this.id = COUNT.incrementAndGet();
+            this.shapeMode = shapeMode;
+            this.closed = closed;
+            this.strokeWidth = 0f;
+            this.strokeCap = this.strokeJoin = -1; // invalid
+            this.dashes = null;
+            this.windingRule = windingRule;
+        }
+
+        boolean isStroke() {
+            return this.strokeWidth > 0f;
+        }
+
+        @Override
+        public String toString() {
+            return "TestSetup{id=" + id + ", shapeMode=" + shapeMode + ", closed=" + closed
+                    + ", strokeWidth=" + strokeWidth + ", strokeCap=" + strokeCap + ", strokeJoin=" + strokeJoin
+                    + ((dashes != null) ? ", dashes: " + Arrays.toString(dashes) : "")
+                    + ", windingRule=" + windingRule + '}';
+        }
+    }
+
+    // --- utilities ---
+    private static final int DCM_ALPHA_MASK = 0xff000000;
+
+    public static BufferedImage newImage(final int w, final int h) {
+        return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE);
+    }
+
+    public static BufferedImage computeDiffImage(final DiffContext localCtx,
+                                                 final BufferedImage tstImage,
+                                                 final BufferedImage refImage,
+                                                 final BufferedImage diffImage,
+                                                 final DiffContext globalCtx) {
+
+        final int[] aRefPix = ((DataBufferInt) refImage.getRaster().getDataBuffer()).getData();
+        final int[] aTstPix = ((DataBufferInt) tstImage.getRaster().getDataBuffer()).getData();
+        final int[] aDifPix = ((DataBufferInt) diffImage.getRaster().getDataBuffer()).getData();
+
+        // reset local diff context:
+        localCtx.reset();
+
+        int ref, tst, dg, v;
+        for (int i = 0, len = aRefPix.length; i < len; i++) {
+            ref = aRefPix[i];
+            tst = aTstPix[i];
+
+            // grayscale diff:
+            dg = (r(ref) + g(ref) + b(ref)) - (r(tst) + g(tst) + b(tst));
+
+            // max difference on grayscale values:
+            v = (int) Math.ceil(Math.abs(dg / 3.0));
+
+            aDifPix[i] = toInt(v, v, v);
+
+            localCtx.add(v);
+            globalCtx.add(v);
+        }
+
+        if (!localCtx.isDiff()) {
+            return null;
+        }
+
+        return diffImage;
+    }
+
+    static void saveImage(final BufferedImage image, final File resDirectory, final String imageFileName) throws IOException {
+        final Iterator<ImageWriter> itWriters = ImageIO.getImageWritersByFormatName("PNG");
+        if (itWriters.hasNext()) {
+            final ImageWriter writer = itWriters.next();
+
+            final ImageWriteParam writerParams = writer.getDefaultWriteParam();
+            writerParams.setProgressiveMode(ImageWriteParam.MODE_DISABLED);
+
+            final File imgFile = new File(resDirectory, imageFileName);
+
+            if (!imgFile.exists() || imgFile.canWrite()) {
+                System.out.println("saveImage: saving image as PNG [" + imgFile + "]...");
+                imgFile.delete();
+
+                // disable cache in temporary files:
+                ImageIO.setUseCache(false);
+
+                final long start = System.nanoTime();
+
+                // PNG uses already buffering:
+                final ImageOutputStream imgOutStream = ImageIO.createImageOutputStream(new FileOutputStream(imgFile));
+
+                writer.setOutput(imgOutStream);
+                try {
+                    writer.write(null, new IIOImage(image, null, null), writerParams);
+                } finally {
+                    imgOutStream.close();
+
+                    final long time = System.nanoTime() - start;
+                    System.out.println("saveImage: duration= " + (time / 1000000l) + " ms.");
+                }
+            }
+        }
+    }
+
+    static int r(final int v) {
+        return (v >> 16 & 0xff);
+    }
+
+    static int g(final int v) {
+        return (v >> 8 & 0xff);
+    }
+
+    static int b(final int v) {
+        return (v & 0xff);
+    }
+
+    static int clamp127(final int v) {
+        return (v < 128) ? (v > -127 ? (v + 127) : 0) : 255;
+    }
+
+    static int toInt(final int r, final int g, final int b) {
+        return DCM_ALPHA_MASK | (r << 16) | (g << 8) | b;
+    }
+
+    /* stats */
+    static class StatInteger {
+
+        public final String name;
+        public long count = 0l;
+        public long sum = 0l;
+        public long min = Integer.MAX_VALUE;
+        public long max = Integer.MIN_VALUE;
+
+        StatInteger(String name) {
+            this.name = name;
+        }
+
+        void reset() {
+            count = 0l;
+            sum = 0l;
+            min = Integer.MAX_VALUE;
+            max = Integer.MIN_VALUE;
+        }
+
+        void add(int val) {
+            count++;
+            sum += val;
+            if (val < min) {
+                min = val;
+            }
+            if (val > max) {
+                max = val;
+            }
+        }
+
+        void add(long val) {
+            count++;
+            sum += val;
+            if (val < min) {
+                min = val;
+            }
+            if (val > max) {
+                max = val;
+            }
+        }
+
+        public final double average() {
+            return ((double) sum) / count;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder(128);
+            toString(sb);
+            return sb.toString();
+        }
+
+        public final StringBuilder toString(final StringBuilder sb) {
+            sb.append(name).append("[n: ").append(count);
+            sb.append("] sum: ").append(sum).append(" avg: ").append(trimTo3Digits(average()));
+            sb.append(" [").append(min).append(" | ").append(max).append("]");
+            return sb;
+        }
+
+    }
+
+    final static class Histogram extends StatInteger {
+
+        static final int BUCKET = 2;
+        static final int MAX = 20;
+        static final int LAST = MAX - 1;
+        static final int[] STEPS = new int[MAX];
+
+        static {
+            STEPS[0] = 0;
+            STEPS[1] = 1;
+
+            for (int i = 2; i < MAX; i++) {
+                STEPS[i] = STEPS[i - 1] * BUCKET;
+            }
+//            System.out.println("Histogram.STEPS = " + Arrays.toString(STEPS));
+        }
+
+        static int bucket(int val) {
+            for (int i = 1; i < MAX; i++) {
+                if (val < STEPS[i]) {
+                    return i - 1;
+                }
+            }
+            return LAST;
+        }
+
+        private final StatInteger[] stats = new StatInteger[MAX];
+
+        public Histogram(String name) {
+            super(name);
+            for (int i = 0; i < MAX; i++) {
+                stats[i] = new StatInteger(String.format("%5s .. %5s", STEPS[i], ((i + 1 < MAX) ? STEPS[i + 1] : "~")));
+            }
+        }
+
+        @Override
+        final void reset() {
+            super.reset();
+            for (int i = 0; i < MAX; i++) {
+                stats[i].reset();
+            }
+        }
+
+        @Override
+        final void add(int val) {
+            super.add(val);
+            stats[bucket(val)].add(val);
+        }
+
+        @Override
+        final void add(long val) {
+            add((int) val);
+        }
+
+        @Override
+        public final String toString() {
+            final StringBuilder sb = new StringBuilder(2048);
+            super.toString(sb).append(" { ");
+
+            for (int i = 0; i < MAX; i++) {
+                if (stats[i].count != 0l) {
+                    sb.append("\n        ").append(stats[i].toString());
+                }
+            }
+
+            return sb.append(" }").toString();
+        }
+    }
+
+    /**
+     * Adjust the given double value to keep only 3 decimal digits
+     * @param value value to adjust
+     * @return double value with only 3 decimal digits
+     */
+    static double trimTo3Digits(final double value) {
+        return ((long) (1e3d * value)) / 1e3d;
+    }
+
+    static final class DiffContext {
+
+        public final Histogram histAll;
+        public final Histogram histPix;
+
+        DiffContext(String name) {
+            histAll = new Histogram("All  Pixels [" + name + "]");
+            histPix = new Histogram("Diff Pixels [" + name + "]");
+        }
+
+        void reset() {
+            histAll.reset();
+            histPix.reset();
+        }
+
+        void dump() {
+            if (isDiff()) {
+                System.out.println("Differences [" + histAll.name + "]:");
+                System.out.println("Total [all pixels]:\n" + histAll.toString());
+                System.out.println("Total [different pixels]:\n" + histPix.toString());
+            } else {
+                System.out.println("No difference for [" + histAll.name + "].");
+            }
+        }
+
+        void add(int val) {
+            histAll.add(val);
+            if (val != 0) {
+                histPix.add(val);
+            }
+        }
+
+        boolean isDiff() {
+            return histAll.sum != 0l;
+        }
+    }
+}