Add a GrPathRange class

Adds a GrPathRange object that represents a range of paths on the gpu.
Updates GrDrawTarget::drawPaths and supporting code to use GrPathRange
instead of an array of GrPath objects.

Change-Id: I67845f3893cd4d955db947d699aa3733cbb081e0

BUG=skia:
R=bsalomon@google.com, jvanverth@google.com, kkinnunen@nvidia.com

Author: cdalton@nvidia.com

Review URL: https://codereview.chromium.org/400713003
diff --git a/gyp/gpu.gypi b/gyp/gpu.gypi
index ca2dc1d..603a2fd 100644
--- a/gyp/gpu.gypi
+++ b/gyp/gpu.gypi
@@ -94,6 +94,7 @@
       '<(skia_src_path)/gpu/GrPaint.cpp',
       '<(skia_src_path)/gpu/GrPath.cpp',
       '<(skia_src_path)/gpu/GrPath.h',
+      '<(skia_src_path)/gpu/GrPathRange.h',
       '<(skia_src_path)/gpu/GrPathRendererChain.cpp',
       '<(skia_src_path)/gpu/GrPathRenderer.cpp',
       '<(skia_src_path)/gpu/GrPathRenderer.h',
@@ -200,6 +201,8 @@
       '<(skia_src_path)/gpu/gl/GrGLNoOpInterface.h',
       '<(skia_src_path)/gpu/gl/GrGLPath.cpp',
       '<(skia_src_path)/gpu/gl/GrGLPath.h',
+      '<(skia_src_path)/gpu/gl/GrGLPathRange.cpp',
+      '<(skia_src_path)/gpu/gl/GrGLPathRange.h',
       '<(skia_src_path)/gpu/gl/GrGLProgram.cpp',
       '<(skia_src_path)/gpu/gl/GrGLProgram.h',
       '<(skia_src_path)/gpu/gl/GrGLProgramDesc.cpp',
diff --git a/include/core/SkStrokeRec.h b/include/core/SkStrokeRec.h
index 42bed8c..0c5892f 100644
--- a/include/core/SkStrokeRec.h
+++ b/include/core/SkStrokeRec.h
@@ -30,6 +30,9 @@
         kStroke_Style,
         kStrokeAndFill_Style
     };
+    enum {
+        kStyleCount = kStrokeAndFill_Style + 1
+    };
 
     Style getStyle() const;
     SkScalar getWidth() const { return fWidth; }
diff --git a/src/gpu/GrDrawTarget.cpp b/src/gpu/GrDrawTarget.cpp
index 483a9a5..72204fa 100644
--- a/src/gpu/GrDrawTarget.cpp
+++ b/src/gpu/GrDrawTarget.cpp
@@ -548,34 +548,25 @@
     this->onDrawPath(path, fill, dstCopy.texture() ? &dstCopy : NULL);
 }
 
-void GrDrawTarget::drawPaths(int pathCount, const GrPath** paths,
-                             const SkMatrix* transforms,
-                             SkPath::FillType fill, SkStrokeRec::Style stroke) {
-    SkASSERT(pathCount > 0);
-    SkASSERT(NULL != paths);
-    SkASSERT(NULL != paths[0]);
+void GrDrawTarget::drawPaths(const GrPathRange* pathRange,
+                             const uint32_t indices[], int count,
+                             const float transforms[], PathTransformType transformsType,
+                             SkPath::FillType fill) {
     SkASSERT(this->caps()->pathRenderingSupport());
-    SkASSERT(!SkPath::IsInverseFillType(fill));
+    SkASSERT(NULL != pathRange);
+    SkASSERT(NULL != indices);
+    SkASSERT(NULL != transforms);
 
-    const GrDrawState* drawState = &getDrawState();
-
-    SkRect devBounds;
-    transforms[0].mapRect(&devBounds, paths[0]->getBounds());
-    for (int i = 1; i < pathCount; ++i) {
-        SkRect mappedPathBounds;
-        transforms[i].mapRect(&mappedPathBounds, paths[i]->getBounds());
-        devBounds.join(mappedPathBounds);
-    }
-
-    SkMatrix viewM = drawState->getViewMatrix();
-    viewM.mapRect(&devBounds);
-
+    // Don't compute a bounding box for setupDstReadIfNecessary(), we'll opt
+    // instead for it to just copy the entire dst. Realistically this is a moot
+    // point, because any context that supports NV_path_rendering will also
+    // support NV_blend_equation_advanced.
     GrDeviceCoordTexture dstCopy;
-    if (!this->setupDstReadIfNecessary(&dstCopy, &devBounds)) {
+    if (!this->setupDstReadIfNecessary(&dstCopy, NULL)) {
         return;
     }
 
-    this->onDrawPaths(pathCount, paths, transforms, fill, stroke,
+    this->onDrawPaths(pathRange, indices, count, transforms, transformsType, fill,
                       dstCopy.texture() ? &dstCopy : NULL);
 }
 
diff --git a/src/gpu/GrDrawTarget.h b/src/gpu/GrDrawTarget.h
index 7898dfd..c868e56 100644
--- a/src/gpu/GrDrawTarget.h
+++ b/src/gpu/GrDrawTarget.h
@@ -26,6 +26,7 @@
 class GrClipData;
 class GrDrawTargetCaps;
 class GrPath;
+class GrPathRange;
 class GrVertexBuffer;
 
 class GrDrawTarget : public SkRefCnt {
@@ -349,14 +350,45 @@
      * Draws many paths. It will respect the HW
      * antialias flag on the draw state (if possible in the 3D API).
      *
-     * @param transforms array of 2d affine transformations, one for each path.
-     * @param fill the fill type for drawing all the paths. Fill must not be a
-     *             hairline.
-     * @param stroke the stroke for drawing all the paths.
+     * @param pathRange       Source of paths to draw from
+     * @param indices         Array of indices into the the pathRange
+     * @param count           Number of paths to draw (length of indices array)
+     * @param transforms      Array of individual transforms, one for each path
+     * @param transformsType  Type of transformations in the array. Array contains
+                              PathTransformSize(transformsType) × count elements
+     * @param fill            Fill type for drawing all the paths
      */
-    void drawPaths(int pathCount, const GrPath** paths,
-                   const SkMatrix* transforms, SkPath::FillType fill,
-                   SkStrokeRec::Style stroke);
+    enum PathTransformType {
+        kNone_PathTransformType,        //!< []
+        kTranslateX_PathTransformType,  //!< [kMTransX]
+        kTranslateY_PathTransformType,  //!< [kMTransY]
+        kTranslate_PathTransformType,   //!< [kMTransX, kMTransY]
+        kAffine_PathTransformType,      //!< [kMScaleX, kMSkewX, kMTransX, kMSkewY, kMScaleY, kMTransY]
+
+        kLast_PathTransformType = kAffine_PathTransformType
+    };
+    void drawPaths(const GrPathRange* pathRange,
+                   const uint32_t indices[], int count,
+                   const float transforms[], PathTransformType transformsType,
+                   SkPath::FillType fill);
+
+    static inline int PathTransformSize(PathTransformType type) {
+        switch (type) {
+            case kNone_PathTransformType:
+                return 0;
+            case kTranslateX_PathTransformType:
+            case kTranslateY_PathTransformType:
+                return 1;
+            case kTranslate_PathTransformType:
+                return 2;
+            case kAffine_PathTransformType:
+                return 6;
+
+            default:
+                SkFAIL("Unknown path transform type");
+                return 0;
+        }
+    }
 
     /**
      * Helper function for drawing rects. It performs a geometry src push and pop
@@ -516,11 +548,12 @@
     /**
      * For subclass internal use to invoke a call to onDrawPaths().
      */
-    void executeDrawPaths(int pathCount, const GrPath** paths,
-                          const SkMatrix* transforms, SkPath::FillType fill,
-                          SkStrokeRec::Style stroke,
+    void executeDrawPaths(const GrPathRange* pathRange,
+                          const uint32_t indices[], int count,
+                          const float transforms[], PathTransformType transformsType,
+                          SkPath::FillType fill,
                           const GrDeviceCoordTexture* dstCopy) {
-        this->onDrawPaths(pathCount, paths, transforms, fill, stroke, dstCopy);
+        this->onDrawPaths(pathRange, indices, count, transforms, transformsType, fill, dstCopy);
     }
 
     inline bool isGpuTracingEnabled() const {
@@ -909,9 +942,10 @@
     virtual void onStencilPath(const GrPath*, SkPath::FillType) = 0;
     virtual void onDrawPath(const GrPath*, SkPath::FillType,
                             const GrDeviceCoordTexture* dstCopy) = 0;
-    virtual void onDrawPaths(int, const GrPath**, const SkMatrix*,
-                             SkPath::FillType, SkStrokeRec::Style,
-                             const GrDeviceCoordTexture* dstCopy) = 0;
+    virtual void onDrawPaths(const GrPathRange*,
+                             const uint32_t indices[], int count,
+                             const float transforms[], PathTransformType,
+                             SkPath::FillType, const GrDeviceCoordTexture*) = 0;
 
     virtual void didAddGpuTraceMarker() = 0;
     virtual void didRemoveGpuTraceMarker() = 0;
diff --git a/src/gpu/GrGpu.cpp b/src/gpu/GrGpu.cpp
index fd249de..840f8ee 100644
--- a/src/gpu/GrGpu.cpp
+++ b/src/gpu/GrGpu.cpp
@@ -218,6 +218,12 @@
     return this->onCreatePath(path, stroke);
 }
 
+GrPathRange* GrGpu::createPathRange(size_t size, const SkStrokeRec& stroke) {
+    SkASSERT(this->caps()->pathRenderingSupport());
+    this->handleDirtyContext();
+    return this->onCreatePathRange(size, stroke);
+}
+
 void GrGpu::clear(const SkIRect* rect,
                   GrColor color,
                   bool canIgnoreRect,
@@ -419,10 +425,10 @@
     this->onGpuDrawPath(path, fill);
 }
 
-void GrGpu::onDrawPaths(int pathCount, const GrPath** paths,
-                        const SkMatrix* transforms, SkPath::FillType fill,
-                        SkStrokeRec::Style style,
-                        const GrDeviceCoordTexture* dstCopy) {
+void GrGpu::onDrawPaths(const GrPathRange* pathRange,
+                        const uint32_t indices[], int count,
+                        const float transforms[], PathTransformType transformsType,
+                        SkPath::FillType fill, const GrDeviceCoordTexture* dstCopy) {
     this->handleDirtyContext();
 
     drawState()->setDefaultVertexAttribs();
@@ -432,7 +438,7 @@
         return;
     }
 
-    this->onGpuDrawPaths(pathCount, paths, transforms, fill, style);
+    this->onGpuDrawPaths(pathRange, indices, count, transforms, transformsType, fill);
 }
 
 void GrGpu::finalizeReservedVertices() {
diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h
index cd7502e..17b7b69 100644
--- a/src/gpu/GrGpu.h
+++ b/src/gpu/GrGpu.h
@@ -16,6 +16,7 @@
 class GrGpuObject;
 class GrIndexBufferAllocPool;
 class GrPath;
+class GrPathRange;
 class GrPathRenderer;
 class GrPathRendererChain;
 class GrStencilBuffer;
@@ -138,6 +139,13 @@
     GrPath* createPath(const SkPath& path, const SkStrokeRec& stroke);
 
     /**
+     * Creates a path range object that can be used to draw multiple paths via
+     * drawPaths(). It is only legal to call this if the caps report support for
+     * path rendering.
+     */
+    GrPathRange* createPathRange(size_t size, const SkStrokeRec&);
+
+    /**
      * Returns an index buffer that can be used to render quads.
      * Six indices per quad: 0, 1, 2, 0, 2, 3, etc.
      * The max number of quads can be queried using GrIndexBuffer::maxQuads().
@@ -432,6 +440,7 @@
     virtual GrVertexBuffer* onCreateVertexBuffer(size_t size, bool dynamic) = 0;
     virtual GrIndexBuffer* onCreateIndexBuffer(size_t size, bool dynamic) = 0;
     virtual GrPath* onCreatePath(const SkPath& path, const SkStrokeRec&) = 0;
+    virtual GrPathRange* onCreatePathRange(size_t size, const SkStrokeRec&) = 0;
 
     // overridden by backend-specific derived class to perform the clear and
     // clearRect. NULL rect means clear whole target. If canIgnoreRect is
@@ -444,8 +453,10 @@
     // overridden by backend-specific derived class to perform the path stenciling.
     virtual void onGpuStencilPath(const GrPath*, SkPath::FillType) = 0;
     virtual void onGpuDrawPath(const GrPath*, SkPath::FillType) = 0;
-    virtual void onGpuDrawPaths(int, const GrPath**, const SkMatrix*,
-                                SkPath::FillType, SkStrokeRec::Style) = 0;
+    virtual void onGpuDrawPaths(const GrPathRange*,
+                                const uint32_t indices[], int count,
+                                const float transforms[], PathTransformType,
+                                SkPath::FillType) = 0;
 
     // overridden by backend-specific derived class to perform the read pixels.
     virtual bool onReadPixels(GrRenderTarget* target,
@@ -488,9 +499,10 @@
     virtual void onStencilPath(const GrPath*, SkPath::FillType) SK_OVERRIDE;
     virtual void onDrawPath(const GrPath*, SkPath::FillType,
                             const GrDeviceCoordTexture* dstCopy) SK_OVERRIDE;
-    virtual void onDrawPaths(int, const GrPath**, const SkMatrix*,
-                             SkPath::FillType, SkStrokeRec::Style,
-                             const GrDeviceCoordTexture* dstCopy) SK_OVERRIDE;
+    virtual void onDrawPaths(const GrPathRange*,
+                             const uint32_t indices[], int count,
+                             const float transforms[], PathTransformType,
+                             SkPath::FillType, const GrDeviceCoordTexture*) SK_OVERRIDE;
 
     // readies the pools to provide vertex/index data.
     void prepareVertexPool();
diff --git a/src/gpu/GrInOrderDrawBuffer.cpp b/src/gpu/GrInOrderDrawBuffer.cpp
index dfff247..26b5452 100644
--- a/src/gpu/GrInOrderDrawBuffer.cpp
+++ b/src/gpu/GrInOrderDrawBuffer.cpp
@@ -13,6 +13,7 @@
 #include "GrGpu.h"
 #include "GrIndexBuffer.h"
 #include "GrPath.h"
+#include "GrPathRange.h"
 #include "GrRenderTarget.h"
 #include "GrTemplates.h"
 #include "GrTexture.h"
@@ -419,10 +420,9 @@
     if (fTransforms) {
         SkDELETE_ARRAY(fTransforms);
     }
-    for (int i = 0; i < fPathCount; ++i) {
-        fPaths[i]->unref();
+    if (fIndices) {
+        SkDELETE_ARRAY(fIndices);
     }
-    SkDELETE_ARRAY(fPaths);
 }
 
 void GrInOrderDrawBuffer::onStencilPath(const GrPath* path, SkPath::FillType fill) {
@@ -457,12 +457,13 @@
     }
 }
 
-void GrInOrderDrawBuffer::onDrawPaths(int pathCount, const GrPath** paths,
-                                      const SkMatrix* transforms,
-                                      SkPath::FillType fill,
-                                      SkStrokeRec::Style stroke,
-                                      const GrDeviceCoordTexture* dstCopy) {
-    SkASSERT(pathCount);
+void GrInOrderDrawBuffer::onDrawPaths(const GrPathRange* pathRange,
+                                      const uint32_t indices[], int count,
+                                      const float transforms[], PathTransformType transformsType,
+                                      SkPath::FillType fill, const GrDeviceCoordTexture* dstCopy) {
+    SkASSERT(NULL != pathRange);
+    SkASSERT(NULL != indices);
+    SkASSERT(NULL != transforms);
 
     if (this->needsNewClip()) {
         this->recordClip();
@@ -471,18 +472,17 @@
         this->recordState();
     }
     DrawPaths* dp = this->recordDrawPaths();
-    dp->fPathCount = pathCount;
-    dp->fPaths = SkNEW_ARRAY(const GrPath*, pathCount);
-    memcpy(dp->fPaths, paths, sizeof(GrPath*) * pathCount);
-    for (int i = 0; i < pathCount; ++i) {
-        dp->fPaths[i]->ref();
-    }
+    dp->fPathRange.reset(SkRef(pathRange));
+    dp->fIndices = SkNEW_ARRAY(uint32_t, count); // TODO: Accomplish this without a malloc
+    memcpy(dp->fIndices, indices, sizeof(uint32_t) * count);
+    dp->fCount = count;
 
-    dp->fTransforms = SkNEW_ARRAY(SkMatrix, pathCount);
-    memcpy(dp->fTransforms, transforms, sizeof(SkMatrix) * pathCount);
+    const int transformsLength = PathTransformSize(transformsType) * count;
+    dp->fTransforms = SkNEW_ARRAY(float, transformsLength);
+    memcpy(dp->fTransforms, transforms, sizeof(float) * transformsLength);
+    dp->fTransformsType = transformsType;
 
     dp->fFill = fill;
-    dp->fStroke = stroke;
 
     if (NULL != dstCopy) {
         dp->fDstCopy = *dstCopy;
@@ -633,9 +633,13 @@
                 SkAssertResult(drawPathsIter.next());
                 const GrDeviceCoordTexture* dstCopy =
                     NULL !=drawPathsIter->fDstCopy.texture() ? &drawPathsIter->fDstCopy : NULL;
-                fDstGpu->executeDrawPaths(drawPathsIter->fPathCount, drawPathsIter->fPaths,
-                                          drawPathsIter->fTransforms, drawPathsIter->fFill,
-                                          drawPathsIter->fStroke, dstCopy);
+                fDstGpu->executeDrawPaths(drawPathsIter->fPathRange.get(),
+                                          drawPathsIter->fIndices,
+                                          drawPathsIter->fCount,
+                                          drawPathsIter->fTransforms,
+                                          drawPathsIter->fTransformsType,
+                                          drawPathsIter->fFill,
+                                          dstCopy);
                 break;
             }
             case kSetState_Cmd:
diff --git a/src/gpu/GrInOrderDrawBuffer.h b/src/gpu/GrInOrderDrawBuffer.h
index 3025c06..b18112e 100644
--- a/src/gpu/GrInOrderDrawBuffer.h
+++ b/src/gpu/GrInOrderDrawBuffer.h
@@ -19,6 +19,7 @@
 
 class GrGpu;
 class GrIndexBufferAllocPool;
+class GrPathRange;
 class GrVertexBufferAllocPool;
 
 /**
@@ -119,11 +120,12 @@
         DrawPaths();
         ~DrawPaths();
 
-        int fPathCount;
-        const GrPath** fPaths;
-        SkMatrix* fTransforms;
+        SkAutoTUnref<const GrPathRange> fPathRange;
+        uint32_t* fIndices;
+        size_t fCount;
+        float* fTransforms;
+        PathTransformType fTransformsType;
         SkPath::FillType fFill;
-        SkStrokeRec::Style fStroke;
         GrDeviceCoordTexture fDstCopy;
     };
 
@@ -160,9 +162,10 @@
     virtual void onStencilPath(const GrPath*, SkPath::FillType) SK_OVERRIDE;
     virtual void onDrawPath(const GrPath*, SkPath::FillType,
                             const GrDeviceCoordTexture* dstCopy) SK_OVERRIDE;
-    virtual void onDrawPaths(int, const GrPath**, const SkMatrix*,
-                             SkPath::FillType, SkStrokeRec::Style,
-                             const GrDeviceCoordTexture* dstCopy) SK_OVERRIDE;
+    virtual void onDrawPaths(const GrPathRange*,
+                             const uint32_t indices[], int count,
+                             const float transforms[], PathTransformType,
+                             SkPath::FillType, const GrDeviceCoordTexture*) SK_OVERRIDE;
 
     virtual bool onReserveVertexSpace(size_t vertexSize,
                                       int vertexCount,
diff --git a/src/gpu/GrPath.cpp b/src/gpu/GrPath.cpp
index adb3fe6..5426e49 100644
--- a/src/gpu/GrPath.cpp
+++ b/src/gpu/GrPath.cpp
@@ -7,25 +7,54 @@
 
 #include "GrPath.h"
 
+template<int NumBits> static uint64_t get_top_n_float_bits(float f) {
+    char* floatData = reinterpret_cast<char*>(&f);
+    uint32_t floatBits = *reinterpret_cast<uint32_t*>(floatData);
+    return floatBits >> (32 - NumBits);
+}
+
 GrResourceKey GrPath::ComputeKey(const SkPath& path, const SkStrokeRec& stroke) {
     static const GrResourceKey::ResourceType gPathResourceType = GrResourceKey::GenerateResourceType();
     static const GrCacheID::Domain gPathDomain = GrCacheID::GenerateDomain();
 
     GrCacheID::Key key;
-    uint32_t* keyData = key.fData32;
+    uint64_t* keyData = key.fData64;
     keyData[0] = path.getGenerationID();
-
-    SK_COMPILE_ASSERT(SkPaint::kJoinCount <= 3, cap_shift_will_be_wrong);
-    keyData[1] = stroke.needToApply();
-    if (0 != keyData[1]) {
-        keyData[1] |= stroke.getJoin() << 1;
-        keyData[1] |= stroke.getCap() << 3;
-        keyData[2] = static_cast<uint32_t>(stroke.getMiter());
-        keyData[3] = static_cast<uint32_t>(stroke.getWidth());
-    } else {
-        keyData[2] = 0;
-        keyData[3] = 0;
-    }
+    keyData[1] = ComputeStrokeKey(stroke);
 
     return GrResourceKey(GrCacheID(gPathDomain, key), gPathResourceType, 0);
 }
+
+uint64_t GrPath::ComputeStrokeKey(const SkStrokeRec& stroke) {
+    enum {
+        kStyleBits = 2,
+        kJoinBits = 2,
+        kCapBits = 2,
+        kWidthBits = 29,
+        kMiterBits = 29,
+
+        kJoinShift = kStyleBits,
+        kCapShift = kJoinShift + kJoinBits,
+        kWidthShift = kCapShift + kCapBits,
+        kMiterShift = kWidthShift + kWidthBits,
+
+        kBitCount = kMiterShift + kMiterBits
+    };
+
+    SK_COMPILE_ASSERT(SkStrokeRec::kStyleCount <= (1 << kStyleBits), style_shift_will_be_wrong);
+    SK_COMPILE_ASSERT(SkPaint::kJoinCount <= (1 << kJoinBits), cap_shift_will_be_wrong);
+    SK_COMPILE_ASSERT(SkPaint::kCapCount <= (1 << kCapBits), miter_shift_will_be_wrong);
+    SK_COMPILE_ASSERT(kBitCount == 64, wrong_stroke_key_size);
+
+    if (!stroke.needToApply()) {
+        return SkStrokeRec::kFill_Style;
+    }
+
+    uint64_t key = stroke.getStyle();
+    key |= stroke.getJoin() << kJoinShift;
+    key |= stroke.getCap() << kCapShift;
+    key |= get_top_n_float_bits<kWidthBits>(stroke.getWidth()) << kWidthShift;
+    key |= get_top_n_float_bits<kMiterBits>(stroke.getMiter()) << kMiterShift;
+
+    return key;
+}
diff --git a/src/gpu/GrPath.h b/src/gpu/GrPath.h
index d324e6a..2c0040c 100644
--- a/src/gpu/GrPath.h
+++ b/src/gpu/GrPath.h
@@ -18,6 +18,9 @@
 public:
     SK_DECLARE_INST_COUNT(GrPath);
 
+    /**
+     * Initialize to a path with a fixed stroke. Stroke must not be hairline.
+     */
     GrPath(GrGpu* gpu, bool isWrapped, const SkPath& skPath, const SkStrokeRec& stroke)
         : INHERITED(gpu, isWrapped),
           fSkPath(skPath),
@@ -26,6 +29,7 @@
     }
 
     static GrResourceKey ComputeKey(const SkPath& path, const SkStrokeRec& stroke);
+    static uint64_t ComputeStrokeKey(const SkStrokeRec&);
 
     bool isEqualTo(const SkPath& path, const SkStrokeRec& stroke) {
         return fSkPath == path && fStroke == stroke;
diff --git a/src/gpu/GrPathRange.h b/src/gpu/GrPathRange.h
new file mode 100644
index 0000000..02342c4
--- /dev/null
+++ b/src/gpu/GrPathRange.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrPathRange_DEFINED
+#define GrPathRange_DEFINED
+
+#include "GrGpuObject.h"
+#include "GrResourceCache.h"
+#include "SkStrokeRec.h"
+
+class SkPath;
+
+/**
+ * Represents a contiguous range of GPU path objects with a common stroke. The
+ * path range is immutable with the exception that individual paths can be
+ * initialized lazily. Unititialized paths are silently ignored by drawing
+ * functions.
+ */
+class GrPathRange : public GrGpuObject {
+public:
+    SK_DECLARE_INST_COUNT(GrPathRange);
+
+    static const bool kIsWrapped = false;
+
+    static GrResourceKey::ResourceType resourceType() {
+        static const GrResourceKey::ResourceType type = GrResourceKey::GenerateResourceType();
+        return type;
+    }
+
+    /**
+     * Initialize to a range with a fixed size and stroke. Stroke must not be hairline.
+     */
+    GrPathRange(GrGpu* gpu, size_t size, const SkStrokeRec& stroke)
+        : INHERITED(gpu, kIsWrapped),
+          fSize(size),
+          fStroke(stroke) {
+    }
+
+    size_t getSize() const { return fSize; }
+    const SkStrokeRec& getStroke() const { return fStroke; }
+
+    /**
+     * Initialize a path in the range. It is invalid to call this method for a
+     * path that has already been initialized.
+     */
+    virtual void initAt(size_t index, const SkPath&) = 0;
+
+protected:
+    size_t fSize;
+    SkStrokeRec fStroke;
+
+private:
+    typedef GrGpuObject INHERITED;
+};
+
+#endif
diff --git a/src/gpu/GrStencilAndCoverTextContext.cpp b/src/gpu/GrStencilAndCoverTextContext.cpp
index 637c85a..65c2642 100644
--- a/src/gpu/GrStencilAndCoverTextContext.cpp
+++ b/src/gpu/GrStencilAndCoverTextContext.cpp
@@ -7,11 +7,9 @@
 
 #include "GrStencilAndCoverTextContext.h"
 #include "GrDrawTarget.h"
-#include "GrFontScaler.h"
 #include "GrGpu.h"
 #include "GrPath.h"
-#include "GrTextStrike.h"
-#include "GrTextStrike_impl.h"
+#include "GrPathRange.h"
 #include "SkAutoKern.h"
 #include "SkDraw.h"
 #include "SkDrawProcs.h"
@@ -20,12 +18,95 @@
 #include "SkPath.h"
 #include "SkTextMapStateProc.h"
 
-static const int kMaxReservedGlyphs = 64;
+class GrStencilAndCoverTextContext::GlyphPathRange : public GrCacheable {
+    static const int kMaxGlyphCount = 1 << 16; // Glyph IDs are uint16_t's
+    static const int kGlyphGroupSize = 16; // Glyphs get tracked in groups of 16
+
+public:
+    static GlyphPathRange* Create(GrContext* context,
+                                  SkGlyphCache* cache,
+                                  const SkStrokeRec& stroke) {
+        static const GrCacheID::Domain gGlyphPathRangeDomain = GrCacheID::GenerateDomain();
+
+        GrCacheID::Key key;
+        key.fData32[0] = cache->getDescriptor().getChecksum();
+        key.fData32[1] = cache->getScalerContext()->getTypeface()->uniqueID();
+        key.fData64[1] = GrPath::ComputeStrokeKey(stroke);
+
+        GrResourceKey resourceKey(GrCacheID(gGlyphPathRangeDomain, key),
+                                  GrPathRange::resourceType(), 0);
+        SkAutoTUnref<GlyphPathRange> glyphs(
+            static_cast<GlyphPathRange*>(context->findAndRefCachedResource(resourceKey)));
+
+        if (NULL == glyphs ||
+            !glyphs->fDesc->equals(cache->getDescriptor() /*checksum collision*/)) {
+            glyphs.reset(SkNEW_ARGS(GlyphPathRange, (context, cache->getDescriptor(), stroke)));
+            context->addResourceToCache(resourceKey, glyphs);
+        }
+
+        return glyphs.detach();
+    }
+
+    const GrPathRange* pathRange() const { return fPathRange.get(); }
+
+    void preloadGlyph(uint16_t glyphID, SkGlyphCache* cache) {
+        const uint16_t groupIndex = glyphID / kGlyphGroupSize;
+        const uint16_t groupByte = groupIndex >> 3;
+        const uint8_t groupBit = 1 << (groupIndex & 7);
+
+        const bool hasGlyph = 0 != (fLoadedGlyphs[groupByte] & groupBit);
+        if (hasGlyph) {
+            return;
+        }
+
+        // We track which glyphs are loaded in groups of kGlyphGroupSize. To
+        // mark a glyph loaded we need to load the entire group.
+        const uint16_t groupFirstID = groupIndex * kGlyphGroupSize;
+        const uint16_t groupLastID = groupFirstID + kGlyphGroupSize - 1;
+        SkPath skPath;
+        for (int id = groupFirstID; id <= groupLastID; ++id) {
+            const SkGlyph& skGlyph = cache->getGlyphIDMetrics(id);
+            if (const SkPath* skPath = cache->findPath(skGlyph)) {
+                fPathRange->initAt(id, *skPath);
+            } // GrGpu::drawPaths will silently ignore undefined paths.
+        }
+
+        fLoadedGlyphs[groupByte] |= groupBit;
+        this->didChangeGpuMemorySize();
+    }
+
+    // GrCacheable overrides
+    virtual size_t gpuMemorySize() const SK_OVERRIDE { return fPathRange->gpuMemorySize(); }
+    virtual bool isValidOnGpu() const SK_OVERRIDE { return fPathRange->isValidOnGpu(); }
+
+private:
+    GlyphPathRange(GrContext* context, const SkDescriptor& desc, const SkStrokeRec& stroke)
+        : fDesc(desc.copy())
+        // We reserve a range of kMaxGlyphCount paths because of fallbacks fonts. We
+        // can't know exactly how many glyphs we might need without preloading every
+        // fallback, which we don't want to do at this point.
+        , fPathRange(context->getGpu()->createPathRange(kMaxGlyphCount, stroke)) {
+        memset(fLoadedGlyphs, 0, sizeof(fLoadedGlyphs));
+    }
+
+    ~GlyphPathRange() {
+        SkDescriptor::Free(fDesc);
+    }
+
+    static const int kMaxGroupCount = (kMaxGlyphCount + (kGlyphGroupSize - 1)) / kGlyphGroupSize;
+    SkDescriptor* const fDesc;
+    uint8_t fLoadedGlyphs[(kMaxGroupCount + 7) >> 3]; // One bit per glyph group
+    SkAutoTUnref<GrPathRange> fPathRange;
+
+    typedef GrCacheable INHERITED;
+};
+
 
 GrStencilAndCoverTextContext::GrStencilAndCoverTextContext(
     GrContext* context, const SkDeviceProperties& properties)
     : GrTextContext(context, properties)
-    , fStroke(SkStrokeRec::kFill_InitStyle) {
+    , fStroke(SkStrokeRec::kFill_InitStyle)
+    , fPendingGlyphCount(0) {
 }
 
 GrStencilAndCoverTextContext::~GrStencilAndCoverTextContext() {
@@ -73,10 +154,8 @@
 
     SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
     SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, glyphCacheTransform);
-    SkGlyphCache* cache = autoCache.getCache();
-    GrFontScaler* scaler = GetGrFontScaler(cache);
-    GrTextStrike* strike =
-        fContext->getFontCache()->getStrike(scaler, true);
+    fGlyphCache = autoCache.getCache();
+    fGlyphs = GlyphPathRange::Create(fContext, fGlyphCache, fStroke);
 
     const char* stop = text + byteLength;
 
@@ -89,7 +168,7 @@
         while (textPtr < stop) {
             // We don't need x, y here, since all subpixel variants will have the
             // same advance.
-            const SkGlyph& glyph = glyphCacheProc(cache, &textPtr, 0, 0);
+            const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &textPtr, 0, 0);
 
             stopX += glyph.fAdvanceX;
             stopY += glyph.fAdvanceY;
@@ -115,17 +194,10 @@
     SkFixed fx = SkScalarToFixed(x);
     SkFixed fy = SkScalarToFixed(y);
     while (text < stop) {
-        const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
+        const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
         fx += SkFixedMul_portable(autokern.adjust(glyph), fixedSizeRatio);
         if (glyph.fWidth) {
-            this->appendGlyph(GrGlyph::Pack(glyph.getGlyphID(),
-                                            glyph.getSubXFixed(),
-                                            glyph.getSubYFixed()),
-                              SkPoint::Make(
-                                  SkFixedToScalar(fx),
-                                  SkFixedToScalar(fy)),
-                              strike,
-                              scaler);
+            this->appendGlyph(glyph.getGlyphID(), SkFixedToScalar(fx), SkFixedToScalar(fy));
         }
 
         fx += SkFixedMul_portable(glyph.fAdvanceX, fixedSizeRatio);
@@ -164,10 +236,8 @@
     SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
 
     SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, NULL);
-    SkGlyphCache* cache = autoCache.getCache();
-    GrFontScaler* scaler = GetGrFontScaler(cache);
-    GrTextStrike* strike =
-        fContext->getFontCache()->getStrike(scaler, true);
+    fGlyphCache = autoCache.getCache();
+    fGlyphs = GlyphPathRange::Create(fContext, fGlyphCache, fStroke);
 
     const char* stop = text + byteLength;
     SkTextAlignProcScalar alignProc(fSkPaint.getTextAlign());
@@ -177,33 +247,22 @@
         while (text < stop) {
             SkPoint loc;
             tmsProc(pos, &loc);
-            const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
+            const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
             if (glyph.fWidth) {
-                this->appendGlyph(GrGlyph::Pack(glyph.getGlyphID(),
-                                                glyph.getSubXFixed(),
-                                                glyph.getSubYFixed()),
-                                  loc,
-                                  strike,
-                                  scaler);
+                this->appendGlyph(glyph.getGlyphID(), loc.x(), loc.y());
             }
             pos += scalarsPerPosition;
         }
     } else {
         while (text < stop) {
-            const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
+            const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
             if (glyph.fWidth) {
                 SkPoint tmsLoc;
                 tmsProc(pos, &tmsLoc);
                 SkPoint loc;
                 alignProc(tmsLoc, glyph, &loc);
 
-                this->appendGlyph(GrGlyph::Pack(glyph.getGlyphID(),
-                                                glyph.getSubXFixed(),
-                                                glyph.getSubYFixed()),
-                                  loc,
-                                  strike,
-                                  scaler);
-
+                this->appendGlyph(glyph.getGlyphID(), loc.x(), loc.y());
             }
             pos += scalarsPerPosition;
         }
@@ -305,62 +364,45 @@
 
     *fDrawTarget->drawState()->stencil() = kStencilPass;
 
-    size_t reserveAmount;
-    switch (skPaint.getTextEncoding()) {
-        default:
-            SkASSERT(false);
-        case SkPaint::kUTF8_TextEncoding:
-            reserveAmount = textByteLength;
-            break;
-        case SkPaint::kUTF16_TextEncoding:
-            reserveAmount = textByteLength / 2;
-            break;
-        case SkPaint::kUTF32_TextEncoding:
-        case SkPaint::kGlyphID_TextEncoding:
-            reserveAmount = textByteLength / 4;
-            break;
-    }
-    fPaths.setReserve(reserveAmount);
-    fTransforms.setReserve(reserveAmount);
+    SkASSERT(0 == fPendingGlyphCount);
 }
 
-inline void GrStencilAndCoverTextContext::appendGlyph(GrGlyph::PackedID glyphID,
-                                                      const SkPoint& pos,
-                                                      GrTextStrike* strike,
-                                                      GrFontScaler* scaler) {
-    GrGlyph* glyph = strike->getGlyph(glyphID, scaler);
-    if (NULL == glyph || glyph->fBounds.isEmpty()) {
+inline void GrStencilAndCoverTextContext::appendGlyph(uint16_t glyphID, float x, float y) {
+    if (fPendingGlyphCount >= kGlyphBufferSize) {
+        this->flush();
+    }
+
+    fGlyphs->preloadGlyph(glyphID, fGlyphCache);
+
+    fIndexBuffer[fPendingGlyphCount] = glyphID;
+    fTransformBuffer[6 * fPendingGlyphCount + 0] = fTextRatio;
+    fTransformBuffer[6 * fPendingGlyphCount + 1] = 0;
+    fTransformBuffer[6 * fPendingGlyphCount + 2] = x;
+    fTransformBuffer[6 * fPendingGlyphCount + 3] = 0;
+    fTransformBuffer[6 * fPendingGlyphCount + 4] = fTextRatio;
+    fTransformBuffer[6 * fPendingGlyphCount + 5] = y;
+
+    ++fPendingGlyphCount;
+}
+
+void GrStencilAndCoverTextContext::flush() {
+    if (0 == fPendingGlyphCount) {
         return;
     }
 
-    if (scaler->getGlyphPath(glyph->glyphID(), &fTmpPath)) {
-        if (!fTmpPath.isEmpty()) {
-            *fPaths.append() = fContext->createPath(fTmpPath, fStroke);
-            SkMatrix* t = fTransforms.append();
-            t->setTranslate(pos.fX, pos.fY);
-            t->preScale(fTextRatio, fTextRatio);
-        }
-    }
+    fDrawTarget->drawPaths(fGlyphs->pathRange(), fIndexBuffer, fPendingGlyphCount,
+                           fTransformBuffer, GrDrawTarget::kAffine_PathTransformType,
+                           SkPath::kWinding_FillType);
+
+    fPendingGlyphCount = 0;
 }
 
 void GrStencilAndCoverTextContext::finish() {
-    if (fPaths.count() > 0) {
-        fDrawTarget->drawPaths(static_cast<size_t>(fPaths.count()),
-                               fPaths.begin(), fTransforms.begin(),
-                               SkPath::kWinding_FillType, fStroke.getStyle());
+    this->flush();
 
-        for (int i = 0; i < fPaths.count(); ++i) {
-            fPaths[i]->unref();
-        }
-        if (fPaths.count() > kMaxReservedGlyphs) {
-            fPaths.reset();
-            fTransforms.reset();
-        } else {
-            fPaths.rewind();
-            fTransforms.rewind();
-        }
-    }
-    fTmpPath.reset();
+    SkSafeUnref(fGlyphs);
+    fGlyphs = NULL;
+    fGlyphCache = NULL;
 
     fDrawTarget->drawState()->stencil()->setDisabled();
     fStateRestore.set(NULL);
diff --git a/src/gpu/GrStencilAndCoverTextContext.h b/src/gpu/GrStencilAndCoverTextContext.h
index 2e79f5c..021a84d 100644
--- a/src/gpu/GrStencilAndCoverTextContext.h
+++ b/src/gpu/GrStencilAndCoverTextContext.h
@@ -36,17 +36,23 @@
     virtual bool canDraw(const SkPaint& paint) SK_OVERRIDE;
 
 private:
+    class GlyphPathRange;
+    static const int kGlyphBufferSize = 1024;
+
     void init(const GrPaint&, const SkPaint&, size_t textByteLength);
-    void appendGlyph(GrGlyph::PackedID, const SkPoint&,
-                     GrTextStrike*, GrFontScaler*);
+    void initGlyphs(SkGlyphCache* cache);
+    void appendGlyph(uint16_t glyphID, float x, float y);
+    void flush();
     void finish();
 
     GrDrawState::AutoRestoreEffects fStateRestore;
     SkScalar fTextRatio;
     SkStrokeRec fStroke;
-    SkTDArray<const GrPath*> fPaths;
-    SkTDArray<SkMatrix> fTransforms;
-    SkPath fTmpPath;
+    SkGlyphCache* fGlyphCache;
+    GlyphPathRange* fGlyphs;
+    uint32_t fIndexBuffer[kGlyphBufferSize];
+    float fTransformBuffer[6 * kGlyphBufferSize];
+    int fPendingGlyphCount;
     SkMatrix fGlyphTransform;
     bool fNeedsDeviceSpaceGlyphs;
 };
diff --git a/src/gpu/gl/GrGLPath.cpp b/src/gpu/gl/GrGLPath.cpp
index bb26b12..1c74580 100644
--- a/src/gpu/gl/GrGLPath.cpp
+++ b/src/gpu/gl/GrGLPath.cpp
@@ -85,23 +85,21 @@
 
 static const bool kIsWrapped = false; // The constructor creates the GL path object.
 
-GrGLPath::GrGLPath(GrGpuGL* gpu, const SkPath& path, const SkStrokeRec& stroke)
-    : INHERITED(gpu, kIsWrapped, path, stroke) {
-    SkASSERT(!path.isEmpty());
-
-    fPathID = gpu->createGLPathObject();
-
+void GrGLPath::InitPathObject(const GrGLInterface* gl,
+                              GrGLuint pathID,
+                              const SkPath& skPath,
+                              const SkStrokeRec& stroke) {
     SkSTArray<16, GrGLubyte, true> pathCommands;
     SkSTArray<16, SkPoint, true> pathPoints;
 
-    int verbCnt = fSkPath.countVerbs();
-    int pointCnt = fSkPath.countPoints();
+    int verbCnt = skPath.countVerbs();
+    int pointCnt = skPath.countPoints();
     pathCommands.resize_back(verbCnt);
     pathPoints.resize_back(pointCnt);
 
     // TODO: Direct access to path points since we could pass them on directly.
-    fSkPath.getPoints(&pathPoints[0], pointCnt);
-    fSkPath.getVerbs(&pathCommands[0], verbCnt);
+    skPath.getPoints(&pathPoints[0], pointCnt);
+    skPath.getVerbs(&pathCommands[0], verbCnt);
 
     SkDEBUGCODE(int numPts = 0);
     for (int i = 0; i < verbCnt; ++i) {
@@ -111,21 +109,34 @@
     }
     SkASSERT(pathPoints.count() == numPts);
 
-    GL_CALL(PathCommands(fPathID,
-                         verbCnt, &pathCommands[0],
-                         2 * pointCnt, GR_GL_FLOAT, &pathPoints[0]));
+    GR_GL_CALL(gl, PathCommands(pathID,
+                                verbCnt, &pathCommands[0],
+                                2 * pointCnt, GR_GL_FLOAT, &pathPoints[0]));
 
     if (stroke.needToApply()) {
-        GL_CALL(PathParameterf(fPathID, GR_GL_PATH_STROKE_WIDTH, SkScalarToFloat(stroke.getWidth())));
-        GL_CALL(PathParameterf(fPathID, GR_GL_PATH_MITER_LIMIT, SkScalarToFloat(stroke.getMiter())));
+        SkASSERT(!stroke.isHairlineStyle());
+        GR_GL_CALL(gl, PathParameterf(pathID, GR_GL_PATH_STROKE_WIDTH, SkScalarToFloat(stroke.getWidth())));
+        GR_GL_CALL(gl, PathParameterf(pathID, GR_GL_PATH_MITER_LIMIT, SkScalarToFloat(stroke.getMiter())));
         GrGLenum join = join_to_gl_join(stroke.getJoin());
-        GL_CALL(PathParameteri(fPathID, GR_GL_PATH_JOIN_STYLE, join));
+        GR_GL_CALL(gl, PathParameteri(pathID, GR_GL_PATH_JOIN_STYLE, join));
         GrGLenum cap = cap_to_gl_cap(stroke.getCap());
-        GL_CALL(PathParameteri(fPathID, GR_GL_PATH_INITIAL_END_CAP, cap));
-        GL_CALL(PathParameteri(fPathID, GR_GL_PATH_TERMINAL_END_CAP, cap));
+        GR_GL_CALL(gl, PathParameteri(pathID, GR_GL_PATH_INITIAL_END_CAP, cap));
+        GR_GL_CALL(gl, PathParameteri(pathID, GR_GL_PATH_TERMINAL_END_CAP, cap));
+    }
+}
 
+GrGLPath::GrGLPath(GrGpuGL* gpu, const SkPath& path, const SkStrokeRec& stroke)
+    : INHERITED(gpu, kIsWrapped, path, stroke) {
+    SkASSERT(!path.isEmpty());
+
+    fPathID = gpu->createGLPathObject();
+
+    InitPathObject(static_cast<GrGpuGL*>(this->getGpu())->glInterface(),
+                   fPathID, fSkPath, stroke);
+
+    if (stroke.needToApply()) {
         // FIXME: try to account for stroking, without rasterizing the stroke.
-        fBounds.outset(SkScalarToFloat(stroke.getWidth()), SkScalarToFloat(stroke.getWidth()));
+        fBounds.outset(stroke.getWidth(), stroke.getWidth());
     }
 }
 
diff --git a/src/gpu/gl/GrGLPath.h b/src/gpu/gl/GrGLPath.h
index 3409547..4831b72 100644
--- a/src/gpu/gl/GrGLPath.h
+++ b/src/gpu/gl/GrGLPath.h
@@ -13,6 +13,7 @@
 #include "gl/GrGLFunctions.h"
 
 class GrGpuGL;
+struct GrGLInterface;
 
 /**
  * Currently this represents a path built using GL_NV_path_rendering. If we
@@ -22,6 +23,11 @@
 
 class GrGLPath : public GrPath {
 public:
+    static void InitPathObject(const GrGLInterface*,
+                               GrGLuint pathID,
+                               const SkPath&,
+                               const SkStrokeRec&);
+
     GrGLPath(GrGpuGL* gpu, const SkPath& path, const SkStrokeRec& stroke);
     virtual ~GrGLPath();
     GrGLuint pathID() const { return fPathID; }
diff --git a/src/gpu/gl/GrGLPathRange.cpp b/src/gpu/gl/GrGLPathRange.cpp
new file mode 100644
index 0000000..2df04e3
--- /dev/null
+++ b/src/gpu/gl/GrGLPathRange.cpp
@@ -0,0 +1,62 @@
+
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrGLPathRange.h"
+#include "GrGLPath.h"
+#include "GrGpuGL.h"
+
+#define GPUGL static_cast<GrGpuGL*>(this->getGpu())
+
+#define GL_CALL(X) GR_GL_CALL(GPUGL->glInterface(), X)
+#define GL_CALL_RET(R, X) GR_GL_CALL_RET(GPUGL->glInterface(), R, X)
+
+GrGLPathRange::GrGLPathRange(GrGpu* gpu, size_t size, const SkStrokeRec& stroke)
+    : INHERITED(gpu, size, stroke),
+      fNumDefinedPaths(0) {
+    GL_CALL_RET(fBasePathID, GenPaths(fSize));
+}
+
+GrGLPathRange::~GrGLPathRange() {
+    this->release();
+}
+
+void GrGLPathRange::initAt(size_t index, const SkPath& skPath) {
+    GrGpuGL* gpu = static_cast<GrGpuGL*>(this->getGpu());
+    if (NULL == gpu) {
+        return;
+    }
+
+#ifdef SK_DEBUG
+    // Make sure the path at this index hasn't been initted already.
+    GrGLboolean hasPathAtIndex;
+    GL_CALL_RET(hasPathAtIndex, IsPath(fBasePathID + index));
+    SkASSERT(GR_GL_FALSE == hasPathAtIndex);
+#endif
+
+    GrGLPath::InitPathObject(gpu->glInterface(), fBasePathID + index, skPath, fStroke);
+
+    ++fNumDefinedPaths;
+    this->didChangeGpuMemorySize();
+}
+
+void GrGLPathRange::onRelease() {
+    SkASSERT(NULL != this->getGpu());
+
+    if (0 != fBasePathID && !this->isWrapped()) {
+        GL_CALL(DeletePaths(fBasePathID, fSize));
+        fBasePathID = 0;
+    }
+
+    INHERITED::onRelease();
+}
+
+void GrGLPathRange::onAbandon() {
+    fBasePathID = 0;
+
+    INHERITED::onAbandon();
+}
diff --git a/src/gpu/gl/GrGLPathRange.h b/src/gpu/gl/GrGLPathRange.h
new file mode 100644
index 0000000..6c6b78e
--- /dev/null
+++ b/src/gpu/gl/GrGLPathRange.h
@@ -0,0 +1,48 @@
+
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrGLPathRange_DEFINED
+#define GrGLPathRange_DEFINED
+
+#include "../GrPathRange.h"
+#include "gl/GrGLFunctions.h"
+
+class GrGpuGL;
+
+/**
+ * Currently this represents a range of GL_NV_path_rendering Path IDs. If we
+ * support other GL path extensions then this would have to have a type enum
+ * and/or be subclassed.
+ */
+
+class GrGLPathRange : public GrPathRange {
+public:
+    GrGLPathRange(GrGpu*, size_t size, const SkStrokeRec&);
+    virtual ~GrGLPathRange();
+
+    GrGLuint basePathID() const { return fBasePathID; }
+
+    virtual void initAt(size_t index, const SkPath&);
+
+    // TODO: Use a better approximation for the individual path sizes.
+    virtual size_t gpuMemorySize() const SK_OVERRIDE {
+        return 100 * fNumDefinedPaths;
+    }
+
+protected:
+    virtual void onRelease() SK_OVERRIDE;
+    virtual void onAbandon() SK_OVERRIDE;
+
+private:
+    GrGLuint fBasePathID;
+    size_t fNumDefinedPaths;
+
+    typedef GrPathRange INHERITED;
+};
+
+#endif
diff --git a/src/gpu/gl/GrGpuGL.cpp b/src/gpu/gl/GrGpuGL.cpp
index 6d47627..d29c141 100644
--- a/src/gpu/gl/GrGpuGL.cpp
+++ b/src/gpu/gl/GrGpuGL.cpp
@@ -10,6 +10,7 @@
 #include "GrGLNameAllocator.h"
 #include "GrGLStencilBuffer.h"
 #include "GrGLPath.h"
+#include "GrGLPathRange.h"
 #include "GrGLShaderBuilder.h"
 #include "GrTemplates.h"
 #include "GrTypes.h"
@@ -34,6 +35,21 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
+static const GrGLenum gXformType2GLType[] = {
+    GR_GL_NONE,
+    GR_GL_TRANSLATE_X,
+    GR_GL_TRANSLATE_Y,
+    GR_GL_TRANSLATE_2D,
+    GR_GL_TRANSPOSE_AFFINE_2D
+};
+
+GR_STATIC_ASSERT(0 == GrDrawTarget::kNone_PathTransformType);
+GR_STATIC_ASSERT(1 == GrDrawTarget::kTranslateX_PathTransformType);
+GR_STATIC_ASSERT(2 == GrDrawTarget::kTranslateY_PathTransformType);
+GR_STATIC_ASSERT(3 == GrDrawTarget::kTranslate_PathTransformType);
+GR_STATIC_ASSERT(4 == GrDrawTarget::kAffine_PathTransformType);
+GR_STATIC_ASSERT(GrDrawTarget::kAffine_PathTransformType == GrDrawTarget::kLast_PathTransformType);
+
 static const GrGLenum gXfermodeCoeff2Blend[] = {
     GR_GL_ZERO,
     GR_GL_ONE,
@@ -1369,6 +1385,11 @@
     return SkNEW_ARGS(GrGLPath, (this, inPath, stroke));
 }
 
+GrPathRange* GrGpuGL::onCreatePathRange(size_t size, const SkStrokeRec& stroke) {
+    SkASSERT(this->caps()->pathRenderingSupport());
+    return SkNEW_ARGS(GrGLPathRange, (this, size, stroke));
+}
+
 void GrGpuGL::flushScissor() {
     if (fScissorState.fEnabled) {
         // Only access the RT if scissoring is being enabled. We can call this before performing
@@ -1907,35 +1928,19 @@
     }
 }
 
-void GrGpuGL::onGpuDrawPaths(int pathCount, const GrPath** paths,
-                             const SkMatrix* transforms,
-                             SkPath::FillType fill,
-                             SkStrokeRec::Style stroke) {
+void GrGpuGL::onGpuDrawPaths(const GrPathRange* pathRange,
+                             const uint32_t indices[], int count,
+                             const float transforms[], PathTransformType transformsType,
+                             SkPath::FillType fill) {
     SkASSERT(this->caps()->pathRenderingSupport());
     SkASSERT(NULL != this->drawState()->getRenderTarget());
     SkASSERT(NULL != this->drawState()->getRenderTarget()->getStencilBuffer());
     SkASSERT(!fCurrentProgram->hasVertexShader());
-    SkASSERT(stroke != SkStrokeRec::kHairline_Style);
 
-    SkAutoMalloc pathData(pathCount * sizeof(GrGLuint));
-    SkAutoMalloc transformData(pathCount * sizeof(GrGLfloat) * 6);
-    GrGLfloat* transformValues =
-        reinterpret_cast<GrGLfloat*>(transformData.get());
-    GrGLuint* pathIDs = reinterpret_cast<GrGLuint*>(pathData.get());
-
-    for (int i = 0; i < pathCount; ++i) {
-        SkASSERT(transforms[i].asAffine(NULL));
-        const SkMatrix& m = transforms[i];
-        transformValues[i * 6] = m.getScaleX();
-        transformValues[i * 6 + 1] = m.getSkewY();
-        transformValues[i * 6 + 2] = m.getSkewX();
-        transformValues[i * 6 + 3] = m.getScaleY();
-        transformValues[i * 6 + 4] = m.getTranslateX();
-        transformValues[i * 6 + 5] = m.getTranslateY();
-        pathIDs[i] = static_cast<const GrGLPath*>(paths[i])->pathID();
-    }
+    GrGLuint baseID = static_cast<const GrGLPathRange*>(pathRange)->basePathID();
 
     flushPathStencilSettings(fill);
+    const SkStrokeRec& stroke = pathRange->getStroke();
 
     SkPath::FillType nonInvertedFill =
         SkPath::ConvertToNonInverseFillType(fill);
@@ -1947,36 +1952,28 @@
     GrGLint writeMask =
         fHWPathStencilSettings.writeMask(GrStencilSettings::kFront_Face);
 
-    bool doFill = stroke == SkStrokeRec::kFill_Style
-        || stroke == SkStrokeRec::kStrokeAndFill_Style;
-    bool doStroke = stroke == SkStrokeRec::kStroke_Style
-        || stroke == SkStrokeRec::kStrokeAndFill_Style;
-
-    if (doFill) {
-        GL_CALL(StencilFillPathInstanced(pathCount, GR_GL_UNSIGNED_INT,
-                                         pathIDs, 0,
-                                         fillMode, writeMask,
-                                         GR_GL_AFFINE_2D, transformValues));
+    if (stroke.isFillStyle() || SkStrokeRec::kStrokeAndFill_Style == stroke.getStyle()) {
+        GL_CALL(StencilFillPathInstanced(count, GR_GL_UNSIGNED_INT, indices, baseID, fillMode,
+                                         writeMask, gXformType2GLType[transformsType],
+                                         transforms));
     }
-    if (doStroke) {
-        GL_CALL(StencilStrokePathInstanced(pathCount, GR_GL_UNSIGNED_INT,
-                                           pathIDs, 0,
-                                           0xffff, writeMask,
-                                           GR_GL_AFFINE_2D, transformValues));
+    if (stroke.needToApply()) {
+        GL_CALL(StencilStrokePathInstanced(count, GR_GL_UNSIGNED_INT, indices, baseID, 0xffff,
+                                           writeMask, gXformType2GLType[transformsType],
+                                           transforms));
     }
 
     if (nonInvertedFill == fill) {
-        if (doStroke) {
+        if (stroke.needToApply()) {
             GL_CALL(CoverStrokePathInstanced(
-                        pathCount, GR_GL_UNSIGNED_INT, pathIDs, 0,
+                        count, GR_GL_UNSIGNED_INT, indices, baseID,
                         GR_GL_BOUNDING_BOX_OF_BOUNDING_BOXES,
-                        GR_GL_AFFINE_2D, transformValues));
+                        gXformType2GLType[transformsType], transforms));
         } else {
             GL_CALL(CoverFillPathInstanced(
-                        pathCount, GR_GL_UNSIGNED_INT, pathIDs, 0,
+                        count, GR_GL_UNSIGNED_INT, indices, baseID,
                         GR_GL_BOUNDING_BOX_OF_BOUNDING_BOXES,
-                        GR_GL_AFFINE_2D, transformValues));
-
+                        gXformType2GLType[transformsType], transforms));
         }
     } else {
         GrDrawState* drawState = this->drawState();
diff --git a/src/gpu/gl/GrGpuGL.h b/src/gpu/gl/GrGpuGL.h
index 52e7b86..526e1ca 100644
--- a/src/gpu/gl/GrGpuGL.h
+++ b/src/gpu/gl/GrGpuGL.h
@@ -135,6 +135,7 @@
     virtual GrVertexBuffer* onCreateVertexBuffer(size_t size, bool dynamic) SK_OVERRIDE;
     virtual GrIndexBuffer* onCreateIndexBuffer(size_t size, bool dynamic) SK_OVERRIDE;
     virtual GrPath* onCreatePath(const SkPath&, const SkStrokeRec&) SK_OVERRIDE;
+    virtual GrPathRange* onCreatePathRange(size_t size, const SkStrokeRec&) SK_OVERRIDE;
     virtual GrTexture* onWrapBackendTexture(const GrBackendTextureDesc&) SK_OVERRIDE;
     virtual GrRenderTarget* onWrapBackendRenderTarget(const GrBackendRenderTargetDesc&) SK_OVERRIDE;
     virtual bool createStencilBufferForRenderTarget(GrRenderTarget* rt,
@@ -164,9 +165,10 @@
 
     virtual void onGpuStencilPath(const GrPath*, SkPath::FillType) SK_OVERRIDE;
     virtual void onGpuDrawPath(const GrPath*, SkPath::FillType) SK_OVERRIDE;
-    virtual void onGpuDrawPaths(int, const GrPath**, const SkMatrix*,
-                                SkPath::FillType,
-                                SkStrokeRec::Style) SK_OVERRIDE;
+    virtual void onGpuDrawPaths(const GrPathRange*,
+                                const uint32_t indices[], int count,
+                                const float transforms[], PathTransformType,
+                                SkPath::FillType) SK_OVERRIDE;
 
     virtual void clearStencil() SK_OVERRIDE;
     virtual void clearStencilClip(const SkIRect& rect,