Generate path names on the client side

Pre-allocates a range of path names and manages allocations within
that range on the client side. This allows us to generate new path
objects in a feed-forward manner that doesn't require round trips to
the GL server.

BUG=skia:
R=bsalomon@google.com, markkilgard@gmail.com

Author: cdalton@nvidia.com

Review URL: https://codereview.chromium.org/304403003
diff --git a/gyp/gpu.gypi b/gyp/gpu.gypi
index 568bc0e..d336fa9 100644
--- a/gyp/gpu.gypi
+++ b/gyp/gpu.gypi
@@ -186,6 +186,8 @@
       '<(skia_src_path)/gpu/gl/GrGLIndexBuffer.h',
       '<(skia_src_path)/gpu/gl/GrGLInterface.cpp',
       '<(skia_src_path)/gpu/gl/GrGLIRect.h',
+      '<(skia_src_path)/gpu/gl/GrGLNameAllocator.cpp',
+      '<(skia_src_path)/gpu/gl/GrGLNameAllocator.h',
       '<(skia_src_path)/gpu/gl/GrGLNoOpInterface.cpp',
       '<(skia_src_path)/gpu/gl/GrGLNoOpInterface.h',
       '<(skia_src_path)/gpu/gl/GrGLPath.cpp',
diff --git a/gyp/tests.gypi b/gyp/tests.gypi
index 7a32da2..6af8e91 100644
--- a/gyp/tests.gypi
+++ b/gyp/tests.gypi
@@ -119,6 +119,7 @@
     '../tests/MessageBusTest.cpp',
     '../tests/MetaDataTest.cpp',
     '../tests/MipMapTest.cpp',
+    '../tests/NameAllocatorTest.cpp',
     '../tests/ObjectPoolTest.cpp',
     '../tests/OSPathTest.cpp',
     '../tests/OnceTest.cpp',
diff --git a/src/gpu/gl/GrGLNameAllocator.cpp b/src/gpu/gl/GrGLNameAllocator.cpp
new file mode 100644
index 0000000..f2c37ed
--- /dev/null
+++ b/src/gpu/gl/GrGLNameAllocator.cpp
@@ -0,0 +1,370 @@
+
+/*
+ * 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 "GrGLNameAllocator.h"
+
+/**
+ * This is the abstract base class for a nonempty AVL tree that tracks allocated
+ * names within the half-open range [fFirst, fEnd). The inner nodes can be
+ * sparse (meaning not every name within the range is necessarily allocated),
+ * but the bounds are tight, so fFirst *is* guaranteed to be allocated, and so
+ * is fEnd - 1.
+ */
+class GrGLNameAllocator::SparseNameRange : public SkRefCnt {
+public:
+    virtual ~SparseNameRange() {}
+
+    /**
+     * Return the beginning of the range. first() is guaranteed to be allocated.
+     *
+     * @return The first name in the range.
+     */
+    GrGLuint first() const { return fFirst; }
+
+    /**
+     * Return the end of the range. end() - 1 is guaranteed to be allocated.
+     *
+     * @return One plus the final name in the range.
+     */
+    GrGLuint end() const { return fEnd; }
+
+    /**
+     * Return the height of the tree. This can only be nonzero at an inner node.
+     *
+     * @return 0 if the implementation is a leaf node,
+     *         The nonzero height of the tree otherwise.
+     */
+    GrGLuint height() const { return fHeight; }
+
+    /**
+     * Allocate a name from strictly inside this range. The call will fail if
+     * there is not a free name within.
+     *
+     * @param outName A pointer that receives the allocated name. outName will
+     *                be set to zero if there were no free names within the
+     *                range [fFirst, fEnd).
+     * @return The resulting SparseNameRange after the allocation. Note that
+     *         this call is destructive, so the original SparseNameRange will no
+     *         longer be valid afterward. The caller must always update its
+     *         pointer with the new SparseNameRange.
+     */
+    virtual SparseNameRange* SK_WARN_UNUSED_RESULT internalAllocate(GrGLuint* outName) = 0;
+
+    /**
+     * Remove the leftmost leaf node from this range (or the entire thing if it
+     * *is* a leaf node). This is an internal helper method that is used after
+     * an allocation if one contiguous range became adjacent to another. (The
+     * range gets removed so the one immediately before can be extended,
+     * collapsing the two into one.)
+     *
+     * @param removedCount A pointer that receives the size of the contiguous
+                           range that was removed.
+     * @return The resulting SparseNameRange after the removal (or NULL if it
+     *         became empty). Note that this call is destructive, so the
+     *         original SparseNameRange will no longer be valid afterward. The
+     *         caller must always update its pointer with the new
+     *         SparseNameRange.
+     */
+    virtual SparseNameRange* SK_WARN_UNUSED_RESULT removeLeftmostContiguousRange(GrGLuint* removedCount) = 0;
+
+    /**
+     * Append adjacent allocated names to the end of this range. This operation
+     * does not affect the structure of the tree. The caller is responsible for
+     * ensuring the new names won't overlap sibling ranges, if any.
+     *
+     * @param count The number of adjacent names to append.
+     * @return The first name appended.
+     */
+    virtual GrGLuint appendNames(GrGLuint count) = 0;
+
+    /**
+     * Prepend adjacent allocated names behind the beginning of this range. This
+     * operation does not affect the structure of the tree. The caller is
+     * responsible for ensuring the new names won't overlap sibling ranges, if
+     * any.
+     *
+     * @param count The number of adjacent names to prepend.
+     * @return The final name prepended (the one with the lowest value).
+     */
+    virtual GrGLuint prependNames(GrGLuint count) = 0;
+
+    /**
+     * Free a name so it is no longer tracked as allocated. If the name is at
+     * the very beginning or very end of the range, the boundaries [fFirst, fEnd)
+     * will be tightened.
+     *
+     * @param name The name to free. Not-allocated names are silently ignored
+     *             the same way they are in the OpenGL spec.
+     * @return The resulting SparseNameRange after the free (or NULL if it
+     *         became empty). Note that this call is destructive, so the
+     *         original SparseNameRange will no longer be valid afterward. The
+     *         caller must always update its pointer with the new
+     *         SparseNameRange.
+     */
+    virtual SparseNameRange* SK_WARN_UNUSED_RESULT free(GrGLuint name) = 0;
+
+protected:
+    SparseNameRange* takeRef() {
+      this->ref();
+      return this;
+    }
+
+    GrGLuint fFirst;
+    GrGLuint fEnd;
+    GrGLuint fHeight;
+};
+
+/**
+ * This class is the SparseNameRange implementation for an inner node. It is an
+ * AVL tree with non-null, non-adjacent left and right children.
+ */
+class GrGLNameAllocator::SparseNameTree : public SparseNameRange {
+public:
+    SparseNameTree(SparseNameRange* left, SparseNameRange* right)
+        : fLeft(left),
+          fRight(right) {
+        SkASSERT(fLeft.get());
+        SkASSERT(fRight.get());
+        this->updateStats();
+    }
+
+    virtual SparseNameRange* SK_WARN_UNUSED_RESULT internalAllocate(GrGLuint* outName) SK_OVERRIDE {
+        // Try allocating the range inside fLeft's internal gaps.
+        fLeft.reset(fLeft->internalAllocate(outName));
+        if (0 != *outName) {
+            this->updateStats();
+            return this->rebalance();
+        }
+
+        if (fLeft->end() + 1 == fRight->first()) {
+            // It closed the gap between fLeft and fRight; merge.
+            GrGLuint removedCount;
+            fRight.reset(fRight->removeLeftmostContiguousRange(&removedCount));
+            *outName = fLeft->appendNames(1 + removedCount);
+            if (NULL == fRight.get()) {
+                return fLeft.detach();
+            }
+            this->updateStats();
+            return this->rebalance();
+        }
+
+        // There is guaranteed to be a gap between fLeft and fRight, and the
+        // "size 1" case has already been covered.
+        SkASSERT(fLeft->end() + 1 < fRight->first());
+        *outName = fLeft->appendNames(1);
+        return this->takeRef();
+    }
+
+    virtual SparseNameRange* SK_WARN_UNUSED_RESULT removeLeftmostContiguousRange(GrGLuint* removedCount) SK_OVERRIDE {
+        fLeft.reset(fLeft->removeLeftmostContiguousRange(removedCount));
+        if (NULL == fLeft) {
+            return fRight.detach();
+        }
+        this->updateStats();
+        return this->rebalance();
+    }
+
+    virtual GrGLuint appendNames(GrGLuint count) SK_OVERRIDE {
+        SkASSERT(fEnd + count > fEnd); // Check for integer wrap.
+        GrGLuint name = fRight->appendNames(count);
+        SkASSERT(fRight->end() == fEnd + count);
+        this->updateStats();
+        return name;
+    }
+
+    virtual GrGLuint prependNames(GrGLuint count) SK_OVERRIDE {
+        SkASSERT(fFirst > count); // We can't allocate at or below 0.
+        GrGLuint name = fLeft->prependNames(count);
+        SkASSERT(fLeft->first() == fFirst - count);
+        this->updateStats();
+        return name;
+    }
+
+    virtual SparseNameRange* SK_WARN_UNUSED_RESULT free(GrGLuint name) SK_OVERRIDE {
+        if (name < fLeft->end()) {
+            fLeft.reset(fLeft->free(name));
+            if (NULL == fLeft) {
+                // fLeft became empty after the free.
+                return fRight.detach();
+            }
+            this->updateStats();
+            return this->rebalance();
+        } else {
+            fRight.reset(fRight->free(name));
+            if (NULL == fRight) {
+                // fRight became empty after the free.
+                return fLeft.detach();
+            }
+            this->updateStats();
+            return this->rebalance();
+        }
+    }
+
+private:
+    typedef SkAutoTUnref<SparseNameRange> SparseNameTree::* ChildRange;
+
+    SparseNameRange* SK_WARN_UNUSED_RESULT rebalance() {
+        if (fLeft->height() > fRight->height() + 1) {
+            return this->rebalanceImpl<&SparseNameTree::fLeft, &SparseNameTree::fRight>();
+        }
+        if (fRight->height() > fLeft->height() + 1) {
+            return this->rebalanceImpl<&SparseNameTree::fRight, &SparseNameTree::fLeft>();
+        }
+        return this->takeRef();
+    }
+
+    /**
+     * Rebalance the tree using rotations, as described in the AVL algorithm:
+     * http://en.wikipedia.org/wiki/AVL_tree#Insertion
+     */
+    template<ChildRange Tall, ChildRange Short>
+    SparseNameRange* SK_WARN_UNUSED_RESULT rebalanceImpl() {
+        // We should be calling rebalance() enough that the tree never gets more
+        // than one rotation off balance.
+        SkASSERT(2 == (this->*Tall)->height() - (this->*Short)->height());
+
+        // Ensure we are in the 'Left Left' or 'Right Right' case:
+        // http://en.wikipedia.org/wiki/AVL_tree#Insertion
+        SparseNameTree* tallChild = static_cast<SparseNameTree*>((this->*Tall).get());
+        if ((tallChild->*Tall)->height() < (tallChild->*Short)->height()) {
+            (this->*Tall).reset(tallChild->rotate<Short, Tall>());
+        }
+
+        // Perform a rotation to balance the tree.
+        return this->rotate<Tall, Short>();
+    }
+
+    /**
+     * Perform a node rotation, as described in the AVL algorithm:
+     * http://en.wikipedia.org/wiki/AVL_tree#Insertion
+     */
+    template<ChildRange Tall, ChildRange Short>
+    SparseNameRange* SK_WARN_UNUSED_RESULT rotate() {
+        SparseNameTree* newRoot = static_cast<SparseNameTree*>((this->*Tall).detach());
+
+        (this->*Tall).reset((newRoot->*Short).detach());
+        this->updateStats();
+
+        (newRoot->*Short).reset(this->takeRef());
+        newRoot->updateStats();
+
+        return newRoot;
+    }
+
+    void updateStats() {
+        SkASSERT(fLeft->end() < fRight->first()); // There must be a gap between left and right.
+        fFirst = fLeft->first();
+        fEnd = fRight->end();
+        fHeight = 1 + SkMax32(fLeft->height(), fRight->height());
+    }
+
+    SkAutoTUnref<SparseNameRange> fLeft;
+    SkAutoTUnref<SparseNameRange> fRight;
+};
+
+/**
+ * This class is the SparseNameRange implementation for a leaf node. It just a
+ * contiguous range of allocated names.
+ */
+class GrGLNameAllocator::ContiguousNameRange : public SparseNameRange {
+public:
+    ContiguousNameRange(GrGLuint first, GrGLuint end) {
+        SkASSERT(first < end);
+        fFirst = first;
+        fEnd = end;
+        fHeight = 0;
+    }
+
+    virtual SparseNameRange* SK_WARN_UNUSED_RESULT internalAllocate(GrGLuint* outName) SK_OVERRIDE {
+        *outName = 0; // No internal gaps, we are contiguous.
+        return this->takeRef();
+    }
+
+    virtual SparseNameRange* SK_WARN_UNUSED_RESULT removeLeftmostContiguousRange(GrGLuint* removedCount) SK_OVERRIDE {
+        *removedCount = fEnd - fFirst;
+        return NULL;
+    }
+
+    virtual GrGLuint appendNames(GrGLuint count) SK_OVERRIDE {
+        SkASSERT(fEnd + count > fEnd); // Check for integer wrap.
+        GrGLuint name = fEnd;
+        fEnd += count;
+        return name;
+    }
+
+    virtual GrGLuint prependNames(GrGLuint count) SK_OVERRIDE {
+        SkASSERT(fFirst > count); // We can't allocate at or below 0.
+        fFirst -= count;
+        return fFirst;
+    }
+
+    virtual SparseNameRange* SK_WARN_UNUSED_RESULT free(GrGLuint name) SK_OVERRIDE {
+        if (name < fFirst || name >= fEnd) {
+          // Not-allocated names are silently ignored.
+          return this->takeRef();
+        }
+
+        if (fFirst == name) {
+            ++fFirst;
+            return (fEnd == fFirst) ? NULL : this->takeRef();
+        }
+
+        if (fEnd == name + 1) {
+            --fEnd;
+            return this->takeRef();
+        }
+
+        SparseNameRange* left = SkNEW_ARGS(ContiguousNameRange, (fFirst, name));
+        SparseNameRange* right = this->takeRef();
+        fFirst = name + 1;
+        return SkNEW_ARGS(SparseNameTree, (left, right));
+    }
+};
+
+GrGLNameAllocator::GrGLNameAllocator(GrGLuint firstName, GrGLuint endName)
+    : fFirstName(firstName),
+      fEndName(endName) {
+    SkASSERT(firstName > 0);
+    SkASSERT(endName > firstName);
+}
+
+GrGLNameAllocator::~GrGLNameAllocator() {
+}
+
+GrGLuint GrGLNameAllocator::allocateName() {
+    if (NULL == fAllocatedNames.get()) {
+        fAllocatedNames.reset(SkNEW_ARGS(ContiguousNameRange, (fFirstName, fFirstName + 1)));
+        return fFirstName;
+    }
+
+    if (fAllocatedNames->first() > fFirstName) {
+        return fAllocatedNames->prependNames(1);
+    }
+
+    GrGLuint name;
+    fAllocatedNames.reset(fAllocatedNames->internalAllocate(&name));
+    if (0 != name) {
+        return name;
+    }
+
+    if (fAllocatedNames->end() < fEndName) {
+        return fAllocatedNames->appendNames(1);
+    }
+
+    // Out of names.
+    return 0;
+}
+
+void GrGLNameAllocator::free(GrGLuint name) {
+    if (!fAllocatedNames.get()) {
+      // Not-allocated names are silently ignored.
+      return;
+    }
+
+    fAllocatedNames.reset(fAllocatedNames->free(name));
+}
diff --git a/src/gpu/gl/GrGLNameAllocator.h b/src/gpu/gl/GrGLNameAllocator.h
new file mode 100644
index 0000000..1c2c26e
--- /dev/null
+++ b/src/gpu/gl/GrGLNameAllocator.h
@@ -0,0 +1,86 @@
+
+/*
+ * 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 GrGLNameAllocator_DEFINED
+#define GrGLNameAllocator_DEFINED
+
+#include "SkRefCnt.h"
+#include "gl/GrGLFunctions.h"
+
+/**
+ * This class assumes ownership of an explicit range of OpenGL object names and
+ * manages allocations within that range. This allows the app to generate new
+ * objects on the client side without making round trips to the GL server.
+ */
+class GrGLNameAllocator {
+public:
+    /**
+     * Constructs a name allocator that produces names within the explicit
+     * half-open range [firstName, end). Note that the caller will most likely
+     * need to call glGen* beforehand to reserve a range within the GL driver,
+     * and then invoke this constructor with that range.
+     *
+     * @param firstName The first name in the range owned by this class. Must be
+                        greater than zero.
+     * @param endName   The first past-the-end name beyond the range owned by
+                        this class. Must be >= firstName.
+     */
+    GrGLNameAllocator(GrGLuint firstName, GrGLuint endName);
+
+    /**
+     * Destructs the name allocator. The caller is responsible for calling the
+     * appropriate glDelete* on the range if necessary.
+     */
+    ~GrGLNameAllocator();
+
+    /**
+     * Return the beginning of this class's range.
+     *
+     * @return The first name in the range owned by this class.
+     */
+    GrGLuint firstName() const { return fFirstName; }
+
+    /**
+     * Return the end of this class's range. Note that endName() is not owned by
+     * this class.
+     *
+     * @return One plus the final name in the range owned by this class.
+     */
+    GrGLuint endName() const { return fEndName; }
+
+    /**
+     * Allocate an OpenGL object name from within this class's range.
+     *
+     * @return The name if one was available,
+               0 if all the names in the range were already in use.
+     */
+    GrGLuint allocateName();
+
+    /**
+     * Free an OpenGL object name, allowing it to be returned by a future call
+     * to allocateName(). Note that the caller should most likely redefine the
+     * object as empty to deallocate any underlying GPU memory before calling
+     * this method (but not call glDelete*, since that would free up the name
+     * within the driver itself).
+     *
+     * @param name The object name to free. Not-allocated names are silently
+     *             ignored the same way they are in the OpenGL spec.
+     */
+    void free(GrGLuint name);
+
+private:
+    class SparseNameRange;
+    class SparseNameTree;
+    class ContiguousNameRange;
+
+    const GrGLuint fFirstName;
+    const GrGLuint fEndName;
+    SkAutoTUnref<SparseNameRange> fAllocatedNames;
+};
+
+#endif
diff --git a/src/gpu/gl/GrGLPath.cpp b/src/gpu/gl/GrGLPath.cpp
index 879edd5..bb26b12 100644
--- a/src/gpu/gl/GrGLPath.cpp
+++ b/src/gpu/gl/GrGLPath.cpp
@@ -89,7 +89,7 @@
     : INHERITED(gpu, kIsWrapped, path, stroke) {
     SkASSERT(!path.isEmpty());
 
-    GL_CALL_RET(fPathID, GenPaths(1));
+    fPathID = gpu->createGLPathObject();
 
     SkSTArray<16, GrGLubyte, true> pathCommands;
     SkSTArray<16, SkPoint, true> pathPoints;
@@ -135,7 +135,7 @@
 
 void GrGLPath::onRelease() {
     if (0 != fPathID && !this->isWrapped()) {
-        GL_CALL(DeletePaths(fPathID, 1));
+        static_cast<GrGpuGL*>(this->getGpu())->deleteGLPathObject(fPathID);
         fPathID = 0;
     }
 
diff --git a/src/gpu/gl/GrGpuGL.cpp b/src/gpu/gl/GrGpuGL.cpp
index 7568ba3..12938f1 100644
--- a/src/gpu/gl/GrGpuGL.cpp
+++ b/src/gpu/gl/GrGpuGL.cpp
@@ -7,6 +7,7 @@
 
 
 #include "GrGpuGL.h"
+#include "GrGLNameAllocator.h"
 #include "GrGLStencilBuffer.h"
 #include "GrGLPath.h"
 #include "GrGLShaderBuilder.h"
@@ -2497,6 +2498,39 @@
     }
 }
 
+
+GrGLuint GrGpuGL::createGLPathObject() {
+    if (NULL == fPathNameAllocator.get()) {
+        static const int range = 65536;
+        GrGLuint firstName;
+        GL_CALL_RET(firstName, GenPaths(range));
+        fPathNameAllocator.reset(SkNEW_ARGS(GrGLNameAllocator, (firstName, firstName + range)));
+    }
+
+    GrGLuint name = fPathNameAllocator->allocateName();
+    if (0 == name) {
+        // Our reserved path names are all in use. Fall back on GenPaths.
+        GL_CALL_RET(name, GenPaths(1));
+    }
+
+    return name;
+}
+
+void GrGpuGL::deleteGLPathObject(GrGLuint name) {
+    if (NULL == fPathNameAllocator.get() ||
+        name < fPathNameAllocator->firstName() ||
+        name >= fPathNameAllocator->endName()) {
+        // If we aren't inside fPathNameAllocator's range then this name was
+        // generated by the GenPaths fallback (or else the name is unallocated).
+        GL_CALL(DeletePaths(name, 1));
+        return;
+    }
+
+    // Make the path empty to save memory, but don't free the name in the driver.
+    GL_CALL(PathCommands(name, 0, NULL, 0, GR_GL_FLOAT, NULL));
+    fPathNameAllocator->free(name);
+}
+
 bool GrGpuGL::configToGLFormats(GrPixelConfig config,
                                 bool getSizedInternalFormat,
                                 GrGLenum* internalFormat,
diff --git a/src/gpu/gl/GrGpuGL.h b/src/gpu/gl/GrGpuGL.h
index cfb8b52..e50ff9b 100644
--- a/src/gpu/gl/GrGpuGL.h
+++ b/src/gpu/gl/GrGpuGL.h
@@ -26,6 +26,8 @@
 #define PROGRAM_CACHE_STATS
 #endif
 
+class GrGLNameAllocator;
+
 class GrGpuGL : public GrGpu {
 public:
     GrGpuGL(const GrGLContext& ctx, GrContext* context);
@@ -106,6 +108,11 @@
     void notifyTextureDelete(GrGLTexture* texture);
     void notifyRenderTargetDelete(GrRenderTarget* renderTarget);
 
+    // These functions should be used to generate and delete GL path names. They have their own
+    // allocator that runs on the client side, so they are much faster than going through GenPaths.
+    GrGLuint createGLPathObject();
+    void deleteGLPathObject(GrGLuint);
+
 protected:
     virtual bool onCopySurface(GrSurface* dst,
                                GrSurface* src,
@@ -465,6 +472,8 @@
     // from our loop that tries stencil formats and calls check fb status.
     int fLastSuccessfulStencilFmtIdx;
 
+    SkAutoTDelete<GrGLNameAllocator> fPathNameAllocator;
+
     typedef GrGpu INHERITED;
 };
 
diff --git a/src/gpu/gl/GrGpuGL_program.cpp b/src/gpu/gl/GrGpuGL_program.cpp
index bc3a5b2..bd4758c 100644
--- a/src/gpu/gl/GrGpuGL_program.cpp
+++ b/src/gpu/gl/GrGpuGL_program.cpp
@@ -10,6 +10,7 @@
 #include "GrEffect.h"
 #include "GrGLEffect.h"
 #include "SkRTConf.h"
+#include "GrGLNameAllocator.h"
 #include "SkTSearch.h"
 
 #ifdef PROGRAM_CACHE_STATS
@@ -202,6 +203,7 @@
     INHERITED::abandonResources();
     fProgramCache->abandon();
     fHWProgramID = 0;
+    fPathNameAllocator.reset(NULL);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/tests/NameAllocatorTest.cpp b/tests/NameAllocatorTest.cpp
new file mode 100644
index 0000000..86efdb2
--- /dev/null
+++ b/tests/NameAllocatorTest.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#if SK_SUPPORT_GPU
+
+#include "gl/GrGLNameAllocator.h"
+#include "Test.h"
+
+////////////////////////////////////////////////////////////////////////////////
+
+class NameLeakTest {
+    static const GrGLuint kFirstName = 101;
+    static const GrGLuint kRange = 1013;
+
+public:
+    NameLeakTest(skiatest::Reporter* reporter)
+        : fReporter(reporter),
+          fAllocator(kFirstName, kFirstName + kRange),
+          fAllocatedCount(0),
+          fRandomName(kFirstName + 4 * kRange / 7) {
+        memset(fAllocatedNames, 0, sizeof(fAllocatedNames));
+    }
+
+    bool run() {
+        if (!this->allocateAllRemaining()) {
+            return false;
+        }
+
+        for (GrGLuint freeCount = 1; freeCount <= kRange; ++freeCount) {
+            if (!this->freeRandomNames(freeCount)) {
+                return false;
+            }
+            if (!this->allocateAllRemaining()) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+private:
+    bool isAllocated(GrGLuint name) const {
+        return fAllocatedNames[name - kFirstName];
+    }
+
+    void setAllocated(GrGLuint name, bool allocated) {
+        fAllocatedNames[name - kFirstName] = allocated;
+    }
+
+    bool allocateAllRemaining() {
+        for (; fAllocatedCount < kRange; ++fAllocatedCount) {
+            GrGLuint name = fAllocator.allocateName();
+            if (0 == name) {
+                ERRORF(fReporter,
+                       "Name allocate failed, but there should still be %u free names",
+                       kRange - fAllocatedCount);
+                return false;
+            }
+            if (name < kFirstName || name >= kFirstName + kRange) {
+                ERRORF(fReporter,
+                       "Name allocate returned name %u outside its bounds [%u, %u)",
+                       name, kFirstName, kFirstName + kRange);
+                return false;
+            }
+            if (this->isAllocated(name)) {
+                ERRORF(fReporter, "Name allocate returned name that is already allocated");
+                return false;
+            }
+
+            this->setAllocated(name, true);
+        }
+
+        // Ensure it returns 0 once all the names are allocated.
+        GrGLuint name = fAllocator.allocateName();
+        if (0 != name) {
+            ERRORF(fReporter,
+                   "Name allocate did not fail when all names were already in use");
+            return false;
+        }
+
+        // Ensure every unique name is allocated.
+        for (GrGLuint i = 0; i < kRange; ++i) {
+            if (!this->isAllocated(kFirstName + i)) {
+                ERRORF(fReporter, "Not all unique names are allocated after allocateAllRemaining()");
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    bool freeRandomNames(GrGLuint count) {
+        // The values a and c make up an LCG (pseudo-random generator). These
+        // values must satisfy the Hull-Dobell Theorem (with m=kRange):
+        // http://en.wikipedia.org/wiki/Linear_congruential_generator
+        // We use our own generator to guarantee it hits each unique value
+        // within kRange exactly once before repeating.
+        const GrGLuint seed = (count + fRandomName) / 2;
+        const GrGLuint a = seed * kRange + 1;
+        const GrGLuint c = (seed * 743) % kRange;
+
+        for (GrGLuint i = 0; i < count; ++i) {
+            fRandomName = (a * fRandomName + c) % kRange;
+            const GrGLuint name = kFirstName + fRandomName;
+            if (!this->isAllocated(name)) {
+                ERRORF(fReporter, "Test bug: Should not free a not-allocated name at this point (%u)", i);
+                return false;
+            }
+
+            fAllocator.free(name);
+            this->setAllocated(name, false);
+            --fAllocatedCount;
+        }
+
+        return true;
+    }
+
+    skiatest::Reporter* fReporter;
+    GrGLNameAllocator fAllocator;
+    bool fAllocatedNames[kRange];
+    GrGLuint fAllocatedCount;
+    GrGLuint fRandomName;
+};
+
+DEF_GPUTEST(NameAllocator, reporter, factory) {
+    // Ensure no names are leaked or double-allocated during heavy usage.
+    {
+        NameLeakTest nameLeakTest(reporter);
+        nameLeakTest.run();
+    }
+
+    static const GrGLuint range = 32;
+    GrGLNameAllocator allocator(1, 1 + range);
+    for (GrGLuint i = 1; i <= range; ++i) {
+        allocator.allocateName();
+    }
+    REPORTER_ASSERT(reporter, 0 == allocator.allocateName());
+
+    // Test freeing names out of range.
+    allocator.free(allocator.firstName() - 1);
+    allocator.free(allocator.endName());
+    REPORTER_ASSERT(reporter, 0 == allocator.allocateName());
+
+    // Test freeing not-allocated names.
+    for (GrGLuint i = 1; i <= range/2; i += 2) {
+        allocator.free(i);
+    }
+    for (GrGLuint i = 1; i <= range/2; i += 2) {
+        // None of these names will be allocated.
+        allocator.free(i);
+    }
+    for (GrGLuint i = 1; i <= range/2; ++i) {
+        // Every other name will not be be allocated.
+        allocator.free(i);
+    }
+    for (GrGLuint i = 1; i <= range/2; ++i) {
+        if (0 == allocator.allocateName()) {
+            ERRORF(reporter, "Name allocate failed when there should be free names");
+            break;
+        }
+    }
+    REPORTER_ASSERT(reporter, 0 == allocator.allocateName());
+}
+
+#endif