changeset 10701:70758983aa19

8188062: Use Marlin renderer in JavaFX BasicStroke Reviewed-by: prr, kcr
author lbourges
date Thu, 09 Nov 2017 07:56:48 -0800
parents ea9829a7fe02
children bad75af55855
files modules/javafx.graphics/src/main/java/com/sun/marlin/DRendererContext.java modules/javafx.graphics/src/main/java/com/sun/marlin/DTransformingPathConsumer2D.java modules/javafx.graphics/src/main/java/com/sun/marlin/MaskMarlinAlphaConsumer.java modules/javafx.graphics/src/main/java/com/sun/marlin/RendererContext.java modules/javafx.graphics/src/main/java/com/sun/marlin/TransformingPathConsumer2D.java modules/javafx.graphics/src/main/java/com/sun/prism/BasicStroke.java modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/DMarlinPrismUtils.java modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/DMarlinRasterizer.java modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/MarlinPrismUtils.java modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/MarlinRasterizer.java modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/ShapeUtil.java
diffstat 11 files changed, 369 insertions(+), 126 deletions(-) [+]
line wrap: on
line diff
--- a/modules/javafx.graphics/src/main/java/com/sun/marlin/DRendererContext.java	Thu Nov 09 07:38:23 2017 -0800
+++ b/modules/javafx.graphics/src/main/java/com/sun/marlin/DRendererContext.java	Thu Nov 09 07:56:48 2017 -0800
@@ -25,10 +25,12 @@
 
 package com.sun.marlin;
 
+import com.sun.javafx.geom.Path2D;
 import java.util.concurrent.atomic.AtomicInteger;
 import com.sun.util.reentrant.ReentrantContext;
 import com.sun.javafx.geom.Rectangle;
 import com.sun.marlin.ArrayCacheConst.CacheStats;
+import java.lang.ref.WeakReference;
 
 /**
  * This class is a renderer context dedicated to a single thread
@@ -58,6 +60,8 @@
     final DCurve curve = new DCurve();
     // MarlinRenderingEngine.TransformingPathConsumer2D
     public final DTransformingPathConsumer2D transformerPC2D;
+    // recycled Path2D instance (weak)
+    private WeakReference<Path2D> refPath2D = null;
     // shared memory between renderer instances:
     final DRendererSharedMemory rdrMem;
     public final DRenderer renderer;
@@ -150,6 +154,22 @@
         }
     }
 
+    public Path2D getPath2D() {
+        // resolve reference:
+        Path2D p2d = (refPath2D != null) ? refPath2D.get() : null;
+
+        // create a new Path2D ?
+        if (p2d == null) {
+            p2d = new Path2D(Path2D.WIND_NON_ZERO, INITIAL_EDGES_COUNT); // 32K
+
+            // update weak reference:
+            refPath2D = new WeakReference<Path2D>(p2d);
+        }
+        // reset the path anyway:
+        p2d.reset();
+        return p2d;
+    }
+
     public DRendererNoAA getRendererNoAA() {
         if (rendererNoAA == null) {
             rendererNoAA = new DRendererNoAA(this);
--- a/modules/javafx.graphics/src/main/java/com/sun/marlin/DTransformingPathConsumer2D.java	Thu Nov 09 07:38:23 2017 -0800
+++ b/modules/javafx.graphics/src/main/java/com/sun/marlin/DTransformingPathConsumer2D.java	Thu Nov 09 07:56:48 2017 -0800
@@ -25,7 +25,7 @@
 
 package com.sun.marlin;
 
-
+import com.sun.javafx.geom.Path2D;
 import com.sun.javafx.geom.transform.BaseTransform;
 
 public final class DTransformingPathConsumer2D {
@@ -34,10 +34,17 @@
         // used by DRendererContext
     }
 
+    // recycled DPathConsumer2D instance from wrapPath2d()
+    private final Path2DWrapper        wp_Path2DWrapper        = new Path2DWrapper();
+
     // recycled DPathConsumer2D instances from deltaTransformConsumer()
     private final DeltaScaleFilter     dt_DeltaScaleFilter     = new DeltaScaleFilter();
     private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
 
+    public DPathConsumer2D wrapPath2D(Path2D p2d) {
+        return wp_Path2DWrapper.init(p2d);
+    }
+
     public DPathConsumer2D deltaTransformConsumer(DPathConsumer2D out,
                                                  BaseTransform at)
     {
@@ -91,7 +98,6 @@
         }
     }
 
-
     static final class DeltaScaleFilter implements DPathConsumer2D {
         private DPathConsumer2D out;
         private double sx, sy;
@@ -209,4 +215,47 @@
             out.pathDone();
         }
     }
+
+    static final class Path2DWrapper implements DPathConsumer2D {
+        private Path2D p2d;
+
+        Path2DWrapper() {}
+
+        Path2DWrapper init(Path2D p2d) {
+            this.p2d = p2d;
+            return this;
+        }
+
+        @Override
+        public void moveTo(double x0, double y0) {
+            p2d.moveTo((float)x0, (float)y0);
+        }
+
+        @Override
+        public void lineTo(double x1, double y1) {
+            p2d.lineTo((float)x1, (float)y1);
+        }
+
+        @Override
+        public void closePath() {
+            p2d.closePath();
+        }
+
+        @Override
+        public void pathDone() {}
+
+        @Override
+        public void curveTo(double x1, double y1,
+                            double x2, double y2,
+                            double x3, double y3)
+        {
+            p2d.curveTo((float)x1, (float)y1, (float)x2, (float)y2,
+                    (float)x3, (float)y3);
+        }
+
+        @Override
+        public void quadTo(double x1, double y1, double x2, double y2) {
+            p2d.quadTo((float)x1, (float)y1, (float)x2, (float)y2);
+        }
+    }
 }
--- a/modules/javafx.graphics/src/main/java/com/sun/marlin/MaskMarlinAlphaConsumer.java	Thu Nov 09 07:38:23 2017 -0800
+++ b/modules/javafx.graphics/src/main/java/com/sun/marlin/MaskMarlinAlphaConsumer.java	Thu Nov 09 07:56:48 2017 -0800
@@ -159,7 +159,7 @@
 //            System.out.println("setting row "+(pix_y - y)+
 //                               " out of "+width+" x "+height);
 
-        final byte out[] = this.alphas;
+        final byte[] out = this.alphas;
         final int w = width;
         final int off = (pix_y - y) * w;
 
@@ -223,7 +223,7 @@
 //            System.out.println("setting row "+(pix_y - y)+
 //                               " out of "+width+" x "+height);
 
-        final byte out[] = this.alphas;
+        final byte[] out = this.alphas;
         final int w = width;
         final int off = (pix_y - y) * w;
 
--- a/modules/javafx.graphics/src/main/java/com/sun/marlin/RendererContext.java	Thu Nov 09 07:38:23 2017 -0800
+++ b/modules/javafx.graphics/src/main/java/com/sun/marlin/RendererContext.java	Thu Nov 09 07:56:48 2017 -0800
@@ -25,10 +25,12 @@
 
 package com.sun.marlin;
 
+import com.sun.javafx.geom.Path2D;
 import java.util.concurrent.atomic.AtomicInteger;
 import com.sun.util.reentrant.ReentrantContext;
 import com.sun.javafx.geom.Rectangle;
 import com.sun.marlin.ArrayCacheConst.CacheStats;
+import java.lang.ref.WeakReference;
 
 /**
  * This class is a renderer context dedicated to a single thread
@@ -58,6 +60,8 @@
     final Curve curve = new Curve();
     // MarlinRenderingEngine.TransformingPathConsumer2D
     public final TransformingPathConsumer2D transformerPC2D;
+    // recycled Path2D instance (weak)
+    private WeakReference<Path2D> refPath2D = null;
     // shared memory between renderer instances:
     final RendererSharedMemory rdrMem;
     public final Renderer renderer;
@@ -150,6 +154,22 @@
         }
     }
 
+    public Path2D getPath2D() {
+        // resolve reference:
+        Path2D p2d = (refPath2D != null) ? refPath2D.get() : null;
+
+        // create a new Path2D ?
+        if (p2d == null) {
+            p2d = new Path2D(Path2D.WIND_NON_ZERO, INITIAL_EDGES_COUNT); // 32K
+
+            // update weak reference:
+            refPath2D = new WeakReference<Path2D>(p2d);
+        }
+        // reset the path anyway:
+        p2d.reset();
+        return p2d;
+    }
+
     public RendererNoAA getRendererNoAA() {
         if (rendererNoAA == null) {
             rendererNoAA = new RendererNoAA(this);
--- a/modules/javafx.graphics/src/main/java/com/sun/marlin/TransformingPathConsumer2D.java	Thu Nov 09 07:38:23 2017 -0800
+++ b/modules/javafx.graphics/src/main/java/com/sun/marlin/TransformingPathConsumer2D.java	Thu Nov 09 07:56:48 2017 -0800
@@ -25,6 +25,7 @@
 
 package com.sun.marlin;
 
+import com.sun.javafx.geom.Path2D;
 import com.sun.javafx.geom.PathConsumer2D;
 import com.sun.javafx.geom.transform.BaseTransform;
 
@@ -34,10 +35,17 @@
         // used by RendererContext
     }
 
+    // recycled PathConsumer2D instance from wrapPath2d()
+    private final Path2DWrapper        wp_Path2DWrapper        = new Path2DWrapper();
+
     // recycled PathConsumer2D instances from deltaTransformConsumer()
     private final DeltaScaleFilter     dt_DeltaScaleFilter     = new DeltaScaleFilter();
     private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
 
+    public PathConsumer2D wrapPath2D(Path2D p2d) {
+        return wp_Path2DWrapper.init(p2d);
+    }
+
     public PathConsumer2D deltaTransformConsumer(PathConsumer2D out,
                                                  BaseTransform at)
     {
@@ -91,7 +99,6 @@
         }
     }
 
-
     static final class DeltaScaleFilter implements PathConsumer2D {
         private PathConsumer2D out;
         private float sx, sy;
@@ -209,4 +216,46 @@
             out.pathDone();
         }
     }
+
+    static final class Path2DWrapper implements PathConsumer2D {
+        private Path2D p2d;
+
+        Path2DWrapper() {}
+
+        Path2DWrapper init(Path2D p2d) {
+            this.p2d = p2d;
+            return this;
+        }
+
+        @Override
+        public void moveTo(float x0, float y0) {
+            p2d.moveTo(x0, y0);
+        }
+
+        @Override
+        public void lineTo(float x1, float y1) {
+            p2d.lineTo(x1, y1);
+        }
+
+        @Override
+        public void closePath() {
+            p2d.closePath();
+        }
+
+        @Override
+        public void pathDone() {}
+
+        @Override
+        public void curveTo(float x1, float y1,
+                            float x2, float y2,
+                            float x3, float y3)
+        {
+            p2d.curveTo(x1, y1, x2, y2, x3, y3);
+        }
+
+        @Override
+        public void quadTo(float x1, float y1, float x2, float y2) {
+            p2d.quadTo(x1, y1, x2, y2);
+        }
+    }
 }
--- a/modules/javafx.graphics/src/main/java/com/sun/prism/BasicStroke.java	Thu Nov 09 07:38:23 2017 -0800
+++ b/modules/javafx.graphics/src/main/java/com/sun/prism/BasicStroke.java	Thu Nov 09 07:56:48 2017 -0800
@@ -28,24 +28,28 @@
 import com.sun.javafx.geom.Area;
 import com.sun.javafx.geom.GeneralShapePair;
 import com.sun.javafx.geom.Path2D;
-import com.sun.javafx.geom.PathConsumer2D;
 import com.sun.javafx.geom.PathIterator;
 import com.sun.javafx.geom.RoundRectangle2D;
 import com.sun.javafx.geom.Shape;
 import com.sun.javafx.geom.ShapePair;
 import com.sun.javafx.geom.transform.BaseTransform;
-import com.sun.openpisces.Dasher;
-import com.sun.openpisces.Stroker;
-import com.sun.prism.impl.shape.OpenPiscesPrismUtils;
+import com.sun.prism.impl.shape.ShapeUtil;
 
 public final class BasicStroke {
-    public static final int CAP_BUTT = Stroker.CAP_BUTT;
-    public static final int CAP_ROUND = Stroker.CAP_ROUND;
-    public static final int CAP_SQUARE = Stroker.CAP_SQUARE;
 
-    public static final int JOIN_BEVEL = Stroker.JOIN_BEVEL;
-    public static final int JOIN_MITER = Stroker.JOIN_MITER;
-    public static final int JOIN_ROUND = Stroker.JOIN_ROUND;
+    /** 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;
+
+    /** 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;
 
     public static final int TYPE_CENTERED = 0;
     public static final int TYPE_INNER = 1;
@@ -679,16 +683,8 @@
         accumulate(x0 + ox, y0 + oy, x0 - ox, y0 - oy, bbox);
     }
 
-    public Shape createCenteredStrokedShape(Shape s) {
-        Path2D p2d = new Path2D(Path2D.WIND_NON_ZERO);
-        float lw = (type == TYPE_CENTERED) ? width : width * 2.0f;
-        PathConsumer2D pc2d =
-            new Stroker(p2d, lw, cap, join, miterLimit);
-        if (dash != null) {
-            pc2d = new Dasher(pc2d, dash, dashPhase);
-        }
-        OpenPiscesPrismUtils.feedConsumer(s.getPathIterator(null), pc2d);
-        return p2d;
+    public Shape createCenteredStrokedShape(final Shape s) {
+        return ShapeUtil.createCenteredStrokedShape(s, this);
     }
 
     static final float SQRT_2 = (float) Math.sqrt(2);
--- a/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/DMarlinPrismUtils.java	Thu Nov 09 07:38:23 2017 -0800
+++ b/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/DMarlinPrismUtils.java	Thu Nov 09 07:56:48 2017 -0800
@@ -51,17 +51,17 @@
     private DMarlinPrismUtils() {
     }
 
-    private static DPathConsumer2D initRenderer(
+    private static boolean nearZero(final double num) {
+        return Math.abs(num) < 2.0d * Math.ulp(num);
+    }
+
+    private static DPathConsumer2D initPipeline(
             final DRendererContext rdrCtx,
             final BasicStroke stroke,
+            final float lineWidth,
             final BaseTransform tx,
-            final Rectangle clip,
-            final int pirule,
-            final DMarlinRenderer renderer)
+            final DPathConsumer2D out)
     {
-        final int oprule = (stroke == null && pirule == PathIterator.WIND_EVEN_ODD) ?
-            DMarlinRenderer.WIND_EVEN_ODD : DMarlinRenderer.WIND_NON_ZERO;
-
         // We use strokerat so that in Stroker and Dasher we can work only
         // with the pre-transformation coordinates. This will repeat a lot of
         // computations done in the path iterator, but the alternative is to
@@ -79,12 +79,12 @@
 
         int dashLen = -1;
         boolean recycleDashes = false;
-
+        double scale = 1.0d;
         double width = 0.0f, dashphase = 0.0f;
         double[] dashesD = null;
 
         if (stroke != null) {
-            width = stroke.getLineWidth();
+            width = lineWidth;
             final float[] dashes = stroke.getDashArray();
             dashphase = stroke.getDashPhase();
 
@@ -95,7 +95,7 @@
                 dashesD = rdrCtx.dasher.copyDashArray(dashes);
             }
 
-            if (tx != null && !tx.isIdentity()) {
+            if ((tx != null) && !tx.isIdentity()) {
                 final double a = tx.getMxx();
                 final double b = tx.getMxy();
                 final double c = tx.getMyx();
@@ -108,7 +108,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++) {
@@ -140,7 +140,8 @@
             }
         }
 
-        DPathConsumer2D pc = renderer.init(clip.x, clip.y, clip.width, clip.height, oprule);
+        // Prepare the pipeline:
+        DPathConsumer2D pc = out;
 
         if (MarlinConst.USE_SIMPLIFIER) {
             // Use simplifier after stroker before Renderer
@@ -149,19 +150,19 @@
         }
 
         final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
-        pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx);
 
         if (stroke != null) {
+            pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx);
+
             pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(),
                     stroke.getLineJoin(), stroke.getMiterLimit());
 
             if (dashesD != null) {
                 pc = rdrCtx.dasher.init(pc, dashesD, dashLen, dashphase, recycleDashes);
             }
+            pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx);
         }
 
-        pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx);
-
         /*
          * Pipeline seems to be:
          * shape.getPathIterator(tx)
@@ -177,8 +178,26 @@
         return pc;
     }
 
-    private static boolean nearZero(final double num) {
-        return Math.abs(num) < 2.0d * Math.ulp(num);
+    private static DPathConsumer2D initRenderer(
+            final DRendererContext rdrCtx,
+            final BasicStroke stroke,
+            final BaseTransform tx,
+            final Rectangle clip,
+            final int piRule,
+            final DMarlinRenderer renderer)
+    {
+        final int oprule = ((stroke == null) && (piRule == PathIterator.WIND_EVEN_ODD)) ?
+            DMarlinRenderer.WIND_EVEN_ODD : DMarlinRenderer.WIND_NON_ZERO;
+
+        renderer.init(clip.x, clip.y, clip.width, clip.height, oprule);
+
+        float lw = 0.0f;
+
+        if (stroke != null) {
+            lw = stroke.getLineWidth();
+        }
+
+        return initPipeline(rdrCtx, stroke, lw, tx, renderer);
     }
 
     public static DMarlinRenderer setupRenderer(
@@ -190,39 +209,37 @@
             final boolean antialiasedShape)
     {
         // Test if transform is identity:
-        final BaseTransform tf = (xform != null && !xform.isIdentity()) ? xform : null;
-
-        final PathIterator pi = shape.getPathIterator(tf);
+        final BaseTransform tf = ((xform != null) && !xform.isIdentity()) ? xform : null;
 
         final DMarlinRenderer r =  (!FORCE_NO_AA && antialiasedShape) ?
                 rdrCtx.renderer : rdrCtx.getRendererNoAA();
 
-        final DPathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, pi.getWindingRule(), r);
-
-        feedConsumer(rdrCtx, pi, pc2d);
-
+        if (shape instanceof Path2D) {
+            final Path2D p2d = (Path2D)shape;
+            final DPathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, p2d.getWindingRule(), r);
+            feedConsumer(rdrCtx, p2d, tf, pc2d);
+        } else {
+            final PathIterator pi = shape.getPathIterator(tf);
+            final DPathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, pi.getWindingRule(), r);
+            feedConsumer(rdrCtx, pi, pc2d);
+        }
         return r;
     }
 
-    public static DMarlinRenderer setupRenderer(
+    public static void strokeTo(
             final DRendererContext rdrCtx,
-            final Path2D p2d,
+            final Shape shape,
             final BasicStroke stroke,
-            final BaseTransform xform,
-            final Rectangle rclip,
-            final boolean antialiasedShape)
+            final float lineWidth,
+            final DPathConsumer2D out)
     {
-        // Test if transform is identity:
-        final BaseTransform tf = (xform != null && !xform.isIdentity()) ? xform : null;
+        final DPathConsumer2D pc2d = initPipeline(rdrCtx, stroke, lineWidth, null, out);
 
-        final DMarlinRenderer r =  (!FORCE_NO_AA && antialiasedShape) ?
-                rdrCtx.renderer : rdrCtx.getRendererNoAA();
-
-        final DPathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, p2d.getWindingRule(), r);
-
-        feedConsumer(rdrCtx, p2d, tf, pc2d);
-
-        return r;
+        if (shape instanceof Path2D) {
+            feedConsumer(rdrCtx, (Path2D)shape, null, pc2d);
+        } else {
+            feedConsumer(rdrCtx, shape.getPathIterator(null), pc2d);
+        }
     }
 
     private static void feedConsumer(final DRendererContext rdrCtx, final PathIterator pi,
@@ -359,8 +376,8 @@
         // - removed pathClosed (ie subpathStarted not set to false)
         boolean subpathStarted = false;
 
-        final float pCoords[] = p2d.getFloatCoordsNoClone();
-        final byte pTypes[] = p2d.getCommandsNoClone();
+        final float[] pCoords = p2d.getFloatCoordsNoClone();
+        final byte[] pTypes = p2d.getCommandsNoClone();
         final int nsegs = p2d.getNumCommands();
 
         for (int i = 0, coff = 0; i < nsegs; i++) {
--- a/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/DMarlinRasterizer.java	Thu Nov 09 07:38:23 2017 -0800
+++ b/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/DMarlinRasterizer.java	Thu Nov 09 07:56:48 2017 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 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
@@ -83,14 +83,9 @@
             final Rectangle rclip = rdrCtx.clip;
             rclip.setBounds(xformBounds);
 
-            if (shape instanceof Path2D) {
-                renderer = DMarlinPrismUtils.setupRenderer(rdrCtx, (Path2D) shape, stroke, xform, rclip,
-                        antialiasedShape);
-            }
-            if (renderer == null) {
-                renderer = DMarlinPrismUtils.setupRenderer(rdrCtx, shape, stroke, xform, rclip,
-                        antialiasedShape);
-            }
+            renderer = DMarlinPrismUtils.setupRenderer(rdrCtx, shape, stroke, xform, rclip,
+                    antialiasedShape);
+
             final int outpix_xmin = renderer.getOutpixMinX();
             final int outpix_xmax = renderer.getOutpixMaxX();
             final int outpix_ymin = renderer.getOutpixMinY();
@@ -111,12 +106,36 @@
             }
             consumer.setBoundsNoClone(outpix_xmin, outpix_ymin, w, h);
             renderer.produceAlphas(consumer);
+
             return consumer.getMaskData();
         } finally {
             if (renderer != null) {
                 renderer.dispose();
             }
-            // recycle the RendererContext instance
+            // recycle the DRendererContext instance
+            DMarlinRenderingEngine.returnRendererContext(rdrCtx);
+        }
+    }
+
+    static Shape createCenteredStrokedShape(Shape s, BasicStroke stroke)
+    {
+        final float lw = (stroke.getType() == BasicStroke.TYPE_CENTERED) ?
+                             stroke.getLineWidth() : stroke.getLineWidth() * 2.0f;
+
+        final DRendererContext rdrCtx = DMarlinRenderingEngine.getRendererContext();
+        try {
+            // initialize a large copyable Path2D to avoid a lot of array growing:
+            final Path2D p2d = rdrCtx.getPath2D();
+
+            DMarlinPrismUtils.strokeTo(rdrCtx, s, stroke, lw,
+                     rdrCtx.transformerPC2D.wrapPath2D(p2d)
+            );
+
+            // Use Path2D copy constructor (trim)
+            return new Path2D(p2d);
+
+        } finally {
+            // recycle the DRendererContext instance
             DMarlinRenderingEngine.returnRendererContext(rdrCtx);
         }
     }
--- a/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/MarlinPrismUtils.java	Thu Nov 09 07:38:23 2017 -0800
+++ b/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/MarlinPrismUtils.java	Thu Nov 09 07:56:48 2017 -0800
@@ -51,17 +51,17 @@
     private MarlinPrismUtils() {
     }
 
-    private static PathConsumer2D initRenderer(
+    private static boolean nearZero(final double num) {
+        return Math.abs(num) < 2.0d * Math.ulp(num);
+    }
+
+    private static PathConsumer2D initPipeline(
             final RendererContext rdrCtx,
             final BasicStroke stroke,
+            final float lineWidth,
             final BaseTransform tx,
-            final Rectangle clip,
-            final int pirule,
-            final MarlinRenderer renderer)
+            final PathConsumer2D out)
     {
-        final int oprule = (stroke == null && pirule == PathIterator.WIND_EVEN_ODD) ?
-            MarlinRenderer.WIND_EVEN_ODD : MarlinRenderer.WIND_NON_ZERO;
-
         // We use strokerat so that in Stroker and Dasher we can work only
         // with the pre-transformation coordinates. This will repeat a lot of
         // computations done in the path iterator, but the alternative is to
@@ -79,16 +79,16 @@
 
         int dashLen = -1;
         boolean recycleDashes = false;
-
+        float scale = 1.0f;
         float width = 0.0f, dashphase = 0.0f;
         float[] dashes = null;
 
         if (stroke != null) {
-            width = stroke.getLineWidth();
+            width = lineWidth;
             dashes = stroke.getDashArray();
             dashphase = stroke.getDashPhase();
 
-            if (tx != null && !tx.isIdentity()) {
+            if ((tx != null) && !tx.isIdentity()) {
                 final double a = tx.getMxx();
                 final double b = tx.getMxy();
                 final double c = tx.getMyx();
@@ -101,7 +101,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;
@@ -136,7 +136,8 @@
             }
         }
 
-        PathConsumer2D pc = renderer.init(clip.x, clip.y, clip.width, clip.height, oprule);
+        // Prepare the pipeline:
+        PathConsumer2D pc = out;
 
         if (MarlinConst.USE_SIMPLIFIER) {
             // Use simplifier after stroker before Renderer
@@ -145,9 +146,10 @@
         }
 
         final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
-        pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx);
 
         if (stroke != null) {
+            pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx);
+
             pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(),
                     stroke.getLineJoin(), stroke.getMiterLimit());
 
@@ -157,10 +159,9 @@
                 }
                 pc = rdrCtx.dasher.init(pc, dashes, dashLen, dashphase, recycleDashes);
             }
+            pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx);
         }
 
-        pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx);
-
         /*
          * Pipeline seems to be:
          * shape.getPathIterator(tx)
@@ -176,8 +177,26 @@
         return pc;
     }
 
-    private static boolean nearZero(final double num) {
-        return Math.abs(num) < 2.0d * Math.ulp(num);
+    private static PathConsumer2D initRenderer(
+            final RendererContext rdrCtx,
+            final BasicStroke stroke,
+            final BaseTransform tx,
+            final Rectangle clip,
+            final int piRule,
+            final MarlinRenderer renderer)
+    {
+        final int oprule = ((stroke == null) && (piRule == PathIterator.WIND_EVEN_ODD)) ?
+            MarlinRenderer.WIND_EVEN_ODD : MarlinRenderer.WIND_NON_ZERO;
+
+        renderer.init(clip.x, clip.y, clip.width, clip.height, oprule);
+
+        float lw = 0.0f;
+
+        if (stroke != null) {
+            lw = stroke.getLineWidth();
+        }
+
+        return initPipeline(rdrCtx, stroke, lw, tx, renderer);
     }
 
     public static MarlinRenderer setupRenderer(
@@ -189,39 +208,37 @@
             final boolean antialiasedShape)
     {
         // Test if transform is identity:
-        final BaseTransform tf = (xform != null && !xform.isIdentity()) ? xform : null;
-
-        final PathIterator pi = shape.getPathIterator(tf);
+        final BaseTransform tf = ((xform != null) && !xform.isIdentity()) ? xform : null;
 
         final MarlinRenderer r =  (!FORCE_NO_AA && antialiasedShape) ?
                 rdrCtx.renderer : rdrCtx.getRendererNoAA();
 
-        final PathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, pi.getWindingRule(), r);
-
-        feedConsumer(rdrCtx, pi, pc2d);
-
+        if (shape instanceof Path2D) {
+            final Path2D p2d = (Path2D)shape;
+            final PathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, p2d.getWindingRule(), r);
+            feedConsumer(rdrCtx, p2d, tf, pc2d);
+        } else {
+            final PathIterator pi = shape.getPathIterator(tf);
+            final PathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, pi.getWindingRule(), r);
+            feedConsumer(rdrCtx, pi, pc2d);
+        }
         return r;
     }
 
-    public static MarlinRenderer setupRenderer(
+    public static void strokeTo(
             final RendererContext rdrCtx,
-            final Path2D p2d,
+            final Shape shape,
             final BasicStroke stroke,
-            final BaseTransform xform,
-            final Rectangle rclip,
-            final boolean antialiasedShape)
+            final float lineWidth,
+            final PathConsumer2D out)
     {
-        // Test if transform is identity:
-        final BaseTransform tf = (xform != null && !xform.isIdentity()) ? xform : null;
+        final PathConsumer2D pc2d = initPipeline(rdrCtx, stroke, lineWidth, null, out);
 
-        final MarlinRenderer r =  (!FORCE_NO_AA && antialiasedShape) ?
-                rdrCtx.renderer : rdrCtx.getRendererNoAA();
-
-        final PathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, p2d.getWindingRule(), r);
-
-        feedConsumer(rdrCtx, p2d, tf, pc2d);
-
-        return r;
+        if (shape instanceof Path2D) {
+            feedConsumer(rdrCtx, (Path2D)shape, null, pc2d);
+        } else {
+            feedConsumer(rdrCtx, shape.getPathIterator(null), pc2d);
+        }
     }
 
     private static void feedConsumer(final RendererContext rdrCtx, final PathIterator pi,
@@ -358,8 +375,8 @@
         // - removed pathClosed (ie subpathStarted not set to false)
         boolean subpathStarted = false;
 
-        final float pCoords[] = p2d.getFloatCoordsNoClone();
-        final byte pTypes[] = p2d.getCommandsNoClone();
+        final float[] pCoords = p2d.getFloatCoordsNoClone();
+        final byte[] pTypes = p2d.getCommandsNoClone();
         final int nsegs = p2d.getNumCommands();
 
         for (int i = 0, coff = 0; i < nsegs; i++) {
--- a/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/MarlinRasterizer.java	Thu Nov 09 07:38:23 2017 -0800
+++ b/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/MarlinRasterizer.java	Thu Nov 09 07:56:48 2017 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 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
@@ -83,14 +83,9 @@
             final Rectangle rclip = rdrCtx.clip;
             rclip.setBounds(xformBounds);
 
-            if (shape instanceof Path2D) {
-                renderer = MarlinPrismUtils.setupRenderer(rdrCtx, (Path2D) shape, stroke, xform, rclip,
-                        antialiasedShape);
-            }
-            if (renderer == null) {
-                renderer = MarlinPrismUtils.setupRenderer(rdrCtx, shape, stroke, xform, rclip,
-                        antialiasedShape);
-            }
+            renderer = MarlinPrismUtils.setupRenderer(rdrCtx, shape, stroke, xform, rclip,
+                    antialiasedShape);
+
             final int outpix_xmin = renderer.getOutpixMinX();
             final int outpix_xmax = renderer.getOutpixMaxX();
             final int outpix_ymin = renderer.getOutpixMinY();
@@ -111,6 +106,7 @@
             }
             consumer.setBoundsNoClone(outpix_xmin, outpix_ymin, w, h);
             renderer.produceAlphas(consumer);
+
             return consumer.getMaskData();
         } finally {
             if (renderer != null) {
@@ -120,4 +116,27 @@
             MarlinRenderingEngine.returnRendererContext(rdrCtx);
         }
     }
+
+    static Shape createCenteredStrokedShape(Shape s, BasicStroke stroke)
+    {
+        final float lw = (stroke.getType() == BasicStroke.TYPE_CENTERED) ?
+                             stroke.getLineWidth() : stroke.getLineWidth() * 2.0f;
+
+        final RendererContext rdrCtx = MarlinRenderingEngine.getRendererContext();
+        try {
+            // initialize a large copyable Path2D to avoid a lot of array growing:
+            final Path2D p2d = rdrCtx.getPath2D();
+
+            MarlinPrismUtils.strokeTo(rdrCtx, s, stroke, lw,
+                     rdrCtx.transformerPC2D.wrapPath2D(p2d)
+            );
+
+            // Use Path2D copy constructor (trim)
+            return new Path2D(p2d);
+
+        } finally {
+            // recycle the RendererContext instance
+            MarlinRenderingEngine.returnRendererContext(rdrCtx);
+        }
+    }
 }
--- a/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/ShapeUtil.java	Thu Nov 09 07:38:23 2017 -0800
+++ b/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/ShapeUtil.java	Thu Nov 09 07:56:48 2017 -0800
@@ -25,11 +25,14 @@
 
 package com.sun.prism.impl.shape;
 
+import com.sun.javafx.geom.Path2D;
+import com.sun.javafx.geom.PathConsumer2D;
 import com.sun.javafx.geom.RectBounds;
 import com.sun.javafx.geom.Shape;
 import com.sun.javafx.geom.transform.BaseTransform;
 import com.sun.prism.BasicStroke;
 import com.sun.prism.impl.PrismSettings;
+import com.sun.prism.impl.PrismSettings.RasterizerType;
 
 public class ShapeUtil {
 
@@ -61,6 +64,40 @@
         return shapeRasterizer.getMaskData(shape, stroke, xformBounds, xform, close, antialiasedShape);
     }
 
+    public static Shape createCenteredStrokedShape(Shape s, BasicStroke stroke)
+    {
+        if (PrismSettings.rasterizerSpec == RasterizerType.DoubleMarlin) {
+            return DMarlinRasterizer.createCenteredStrokedShape(s, stroke);
+        }
+        if (PrismSettings.rasterizerSpec == RasterizerType.FloatMarlin) {
+            return MarlinRasterizer.createCenteredStrokedShape(s, stroke);
+        }
+        // JavaPisces fallback:
+        return createCenteredStrokedShapeOpenPisces(s, stroke);
+    }
+
+    private static Shape createCenteredStrokedShapeOpenPisces(Shape s, BasicStroke stroke)
+    {
+        final float lw = (stroke.getType() == BasicStroke.TYPE_CENTERED) ?
+                             stroke.getLineWidth() : stroke.getLineWidth() * 2.0f;
+
+        final Path2D p2d = new Path2D(Path2D.WIND_NON_ZERO);
+
+        PathConsumer2D pc2d =
+            new com.sun.openpisces.Stroker(p2d, lw, stroke.getEndCap(),
+                                                    stroke.getLineJoin(),
+                                                    stroke.getMiterLimit());
+
+        if (stroke.isDashed()) {
+            pc2d = new com.sun.openpisces.Dasher(pc2d, stroke.getDashArray(),
+                                                       stroke.getDashPhase());
+        }
+        com.sun.prism.impl.shape.OpenPiscesPrismUtils.feedConsumer(
+                s.getPathIterator(null), pc2d);
+
+        return p2d;
+    }
+
     /**
      * Private constructor to prevent instantiation.
      */