Respect max index buffer sizes in GrFillRectOp::MakeSet

This is required before we can lower the max AA quad count (again).

Bug: b/143572065 skia:9601
Change-Id: Iea5884ffff9bd62f62527fcb597dac97d33ecd76
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/254438
Commit-Queue: Robert Phillips <robertphillips@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
diff --git a/gn/tests.gni b/gn/tests.gni
index b893768..c823ac2 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -24,6 +24,7 @@
   "$_tests/BlendTest.cpp",
   "$_tests/BlitMaskClip.cpp",
   "$_tests/BlurTest.cpp",
+  "$_tests/BulkFillRectTest.cpp",
   "$_tests/CTest.cpp",
   "$_tests/CachedDataTest.cpp",
   "$_tests/CachedDecodingPixelRefTest.cpp",
diff --git a/src/gpu/GrOpsTask.h b/src/gpu/GrOpsTask.h
index 1a4c517..fd69509 100644
--- a/src/gpu/GrOpsTask.h
+++ b/src/gpu/GrOpsTask.h
@@ -106,6 +106,11 @@
     SkDEBUGCODE(int numClips() const override { return fNumClips; })
     SkDEBUGCODE(void visitProxies_debugOnly(const VisitSurfaceProxyFunc&) const override;)
 
+#if GR_TEST_UTILS
+    int numOpChains() const { return fOpChains.count(); }
+    const GrOp* getChain(int index) const { return fOpChains[index].head(); }
+#endif
+
 private:
     bool isNoOp() const {
         // TODO: GrLoadOp::kDiscard (i.e., storing a discard) should also be grounds for skipping
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index 1e0cc19..06147d9 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -733,8 +733,9 @@
                                         const SkMatrix& viewMatrix, const QuadSetEntry quads[],
                                         int cnt) {
     GrAAType aaType = this->chooseAAType(aa);
-    this->addDrawOp(clip, GrFillRectOp::MakeSet(fContext, std::move(paint), aaType, viewMatrix,
-                                                quads, cnt));
+
+    GrFillRectOp::AddFillRectOps(this, clip, fContext, std::move(paint), aaType, viewMatrix,
+                                 quads, cnt);
 }
 
 int GrRenderTargetContextPriv::maxWindowRectangles() const {
diff --git a/src/gpu/GrRenderTargetContext.h b/src/gpu/GrRenderTargetContext.h
index eacff51..89fd20c 100644
--- a/src/gpu/GrRenderTargetContext.h
+++ b/src/gpu/GrRenderTargetContext.h
@@ -549,12 +549,16 @@
     friend class GrTessellatingPathRenderer;         // for access to add[Mesh]DrawOp
     friend class GrCCPerFlushResources;              // for access to addDrawOp
     friend class GrCoverageCountingPathRenderer;     // for access to addDrawOp
+    friend class GrFillRectOp;                       // for access to addDrawOp
+
+#if GR_TEST_UTILS
     // for a unit test
     friend void test_draw_op(GrContext*,
                              GrRenderTargetContext*,
                              std::unique_ptr<GrFragmentProcessor>,
                              sk_sp<GrTextureProxy>,
                              GrColorType);
+#endif
 
     GrRenderTargetContext(GrRecordingContext*, sk_sp<GrRenderTargetProxy>, GrColorType,
                           GrSurfaceOrigin, GrSwizzle texSwizzle, GrSwizzle outSwizzle,
diff --git a/src/gpu/ops/GrDrawOp.h b/src/gpu/ops/GrDrawOp.h
index a9e9752..3385b64 100644
--- a/src/gpu/ops/GrDrawOp.h
+++ b/src/gpu/ops/GrDrawOp.h
@@ -54,6 +54,11 @@
     }
 #endif
 
+#if GR_TEST_UTILS
+    // This is really only intended for GrTextureOp and GrFillRectOp to override
+    virtual int numQuads() const { return -1; }
+#endif
+
 private:
     typedef GrOp INHERITED;
 };
diff --git a/src/gpu/ops/GrFillRectOp.cpp b/src/gpu/ops/GrFillRectOp.cpp
index ec6fa0b..2ee183a 100644
--- a/src/gpu/ops/GrFillRectOp.cpp
+++ b/src/gpu/ops/GrFillRectOp.cpp
@@ -178,13 +178,11 @@
     DEFINE_OP_CLASS_ID
 
 private:
-    // For GrFillRectOp::MakeSet's use of addQuad
-    friend std::unique_ptr<GrDrawOp> GrFillRectOp::MakeSet(
-            GrRecordingContext*,
-            GrPaint&&,
-            GrAAType, const SkMatrix& viewMatrix,
-            const GrRenderTargetContext::QuadSetEntry quads[], int quadCount,
-            const GrUserStencilSettings*);
+    friend class ::GrFillRectOp; // for access to addQuad
+
+#if GR_TEST_UTILS
+    int numQuads() const final { return fQuads.count(); }
+#endif
 
     void onPrepareDraws(Target* target) override {
         TRACE_EVENT0("skia.gpu", TRACE_FUNC);
@@ -297,19 +295,29 @@
     // But since it's avoiding the op list management, it must update the op's bounds. This is only
     // used with quad sets, which uses the same view matrix for each quad so this assumes that the
     // device quad type of the new quad is the same as the op's.
-    void addQuad(const GrQuad& deviceQuad, const GrQuad& localQuad,
+    bool addQuad(const GrQuad& deviceQuad, const GrQuad& localQuad,
                  const SkPMColor4f& color, GrQuadAAFlags edgeAA, GrAAType aaType) {
         // The new quad's aa type should be the same as the first quad's or none, except when the
         // first quad's aa type was already downgraded to none, in which case the stored type must
         // be lifted to back to the requested type.
-        if (aaType != fHelper.aaType()) {
-            if (aaType != GrAAType::kNone) {
-                // Original quad was downgraded to non-aa, lift back up to this quad's required type
-                SkASSERT(fHelper.aaType() == GrAAType::kNone);
-                fHelper.setAAType(aaType);
+        if (aaType != fHelper.aaType() && aaType != GrAAType::kNone) {
+            auto indexBufferOption = GrQuadPerEdgeAA::CalcIndexBufferOption(aaType,
+                                                                            fQuads.count()+1);
+            if (fQuads.count()+1 > GrQuadPerEdgeAA::QuadLimit(indexBufferOption)) {
+                // Promoting to the new aaType would've caused an overflow of the indexBuffer
+                // limit
+                return false;
             }
-            // else the new quad could have been downgraded but the other quads can't be, so don't
-            // reset the op's accumulated aa type.
+
+            // Original quad was downgraded to non-aa, lift back up to this quad's required type
+            SkASSERT(fHelper.aaType() == GrAAType::kNone);
+            fHelper.setAAType(aaType);
+        } else {
+            auto indexBufferOption = GrQuadPerEdgeAA::CalcIndexBufferOption(fHelper.aaType(),
+                                                                            fQuads.count()+1);
+            if (fQuads.count()+1 > GrQuadPerEdgeAA::QuadLimit(indexBufferOption)) {
+                return false; // This op can't grow any more
+            }
         }
 
         // Update the bounds and add the quad to this op's storage
@@ -318,6 +326,7 @@
         this->setBounds(newBounds, HasAABloat(fHelper.aaType() == GrAAType::kCoverage),
                         IsHairline::kNo);
         fQuads.append(deviceQuad, { color, edgeAA }, fHelper.isTrivial() ? nullptr : &localQuad);
+        return true;
     }
 
     struct ColorAndAA {
@@ -335,45 +344,46 @@
 
 } // anonymous namespace
 
-namespace GrFillRectOp {
-
-std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
-                               GrPaint&& paint,
-                               GrAAType aaType,
-                               GrQuadAAFlags aaFlags,
-                               const GrQuad& deviceQuad,
-                               const GrQuad& localQuad,
-                               const GrUserStencilSettings* stencil) {
+std::unique_ptr<GrDrawOp> GrFillRectOp::Make(GrRecordingContext* context,
+                                             GrPaint&& paint,
+                                             GrAAType aaType,
+                                             GrQuadAAFlags aaFlags,
+                                             const GrQuad& deviceQuad,
+                                             const GrQuad& localQuad,
+                                             const GrUserStencilSettings* stencil) {
     return FillRectOp::Make(context, std::move(paint), aaType, aaFlags, stencil,
                             deviceQuad, localQuad);
 }
 
-std::unique_ptr<GrDrawOp> MakeNonAARect(GrRecordingContext* context,
-                                        GrPaint&& paint,
-                                        const SkMatrix& view,
-                                        const SkRect& rect,
-                                        const GrUserStencilSettings* stencil) {
+std::unique_ptr<GrDrawOp> GrFillRectOp::MakeNonAARect(GrRecordingContext* context,
+                                                      GrPaint&& paint,
+                                                      const SkMatrix& view,
+                                                      const SkRect& rect,
+                                                      const GrUserStencilSettings* stencil) {
     return FillRectOp::Make(context, std::move(paint), GrAAType::kNone, GrQuadAAFlags::kNone,
                             stencil, GrQuad::MakeFromRect(rect, view), GrQuad(rect));
 }
 
-std::unique_ptr<GrDrawOp> MakeSet(GrRecordingContext* context,
-                                  GrPaint&& paint,
-                                  GrAAType aaType,
-                                  const SkMatrix& viewMatrix,
-                                  const GrRenderTargetContext::QuadSetEntry quads[],
-                                  int cnt,
-                                  const GrUserStencilSettings* stencilSettings) {
+std::unique_ptr<GrDrawOp> GrFillRectOp::MakeOp(GrRecordingContext* context,
+                                               GrPaint&& paint,
+                                               GrAAType aaType,
+                                               const SkMatrix& viewMatrix,
+                                               const GrRenderTargetContext::QuadSetEntry quads[],
+                                               int cnt,
+                                               const GrUserStencilSettings* stencilSettings,
+                                               int* numConsumed) {
     // First make a draw op for the first quad in the set
     SkASSERT(cnt > 0);
 
     paint.setColor4f(quads[0].fColor);
-    std::unique_ptr<GrDrawOp> op = FillRectOp::Make(context, std::move(paint), aaType,
+    std::unique_ptr<GrDrawOp> op = FillRectOp::Make(
+            context, std::move(paint), aaType,
             quads[0].fAAFlags, stencilSettings,
             GrQuad::MakeFromRect(quads[0].fRect, viewMatrix),
             GrQuad::MakeFromRect(quads[0].fRect, quads[0].fLocalMatrix));
-    auto* fillRects = op->cast<FillRectOp>();
+    FillRectOp* fillRects = op->cast<FillRectOp>();
 
+    *numConsumed = 1;
     // Accumulate remaining quads similar to onCombineIfPossible() without creating an op
     for (int i = 1; i < cnt; ++i) {
         GrQuad deviceQuad = GrQuad::MakeFromRect(quads[i].fRect, viewMatrix);
@@ -383,18 +393,52 @@
         GrQuadUtils::ResolveAAType(aaType, quads[i].fAAFlags, deviceQuad,
                                    &resolvedAA, &resolvedEdgeFlags);
 
-        fillRects->addQuad(deviceQuad,
-                           GrQuad::MakeFromRect(quads[i].fRect, quads[i].fLocalMatrix),
-                           quads[i].fColor, resolvedEdgeFlags,resolvedAA);
+        if (!fillRects->addQuad(deviceQuad,
+                                GrQuad::MakeFromRect(quads[i].fRect, quads[i].fLocalMatrix),
+                                quads[i].fColor, resolvedEdgeFlags, resolvedAA)) {
+            break;
+        }
+
+        (*numConsumed)++;
     }
 
     return op;
 }
 
-} // namespace GrFillRectOp
+void GrFillRectOp::AddFillRectOps(GrRenderTargetContext* rtc,
+                                  const GrClip& clip,
+                                  GrRecordingContext* context,
+                                  GrPaint&& paint,
+                                  GrAAType aaType,
+                                  const SkMatrix& viewMatrix,
+                                  const GrRenderTargetContext::QuadSetEntry quads[],
+                                  int cnt,
+                                  const GrUserStencilSettings* stencilSettings) {
+
+    int offset = 0;
+    int numLeft = cnt;
+    while (numLeft) {
+        int numConsumed = 0;
+
+        std::unique_ptr<GrDrawOp> op = MakeOp(context, GrPaint::Clone(paint), aaType, viewMatrix,
+                                              &quads[offset], numLeft, stencilSettings,
+                                              &numConsumed);
+
+        offset += numConsumed;
+        numLeft -= numConsumed;
+
+        rtc->addDrawOp(clip, std::move(op));
+    }
+
+    SkASSERT(offset == cnt);
+}
 
 #if GR_TEST_UTILS
 
+uint32_t GrFillRectOp::ClassID() {
+    return FillRectOp::ClassID();
+}
+
 #include "src/gpu/GrDrawOpTest.h"
 #include "src/gpu/SkGr.h"
 
@@ -417,35 +461,11 @@
 
     if (random->nextBool()) {
         if (random->nextBool()) {
-            if (random->nextBool()) {
-                // Local matrix with a set op
-                uint32_t extraQuadCt = random->nextRangeU(1, 4);
-                SkTArray<GrRenderTargetContext::QuadSetEntry> quads(extraQuadCt + 1);
-                quads.push_back(
-                        {rect, SkPMColor4f::FromBytes_RGBA(SkColorToPremulGrColor(random->nextU())),
-                         GrTest::TestMatrixInvertible(random), aaFlags});
-                for (uint32_t i = 0; i < extraQuadCt; ++i) {
-                    GrQuadAAFlags aaFlags = GrQuadAAFlags::kNone;
-                    aaFlags |= random->nextBool() ? GrQuadAAFlags::kLeft : GrQuadAAFlags::kNone;
-                    aaFlags |= random->nextBool() ? GrQuadAAFlags::kTop : GrQuadAAFlags::kNone;
-                    aaFlags |= random->nextBool() ? GrQuadAAFlags::kRight : GrQuadAAFlags::kNone;
-                    aaFlags |= random->nextBool() ? GrQuadAAFlags::kBottom : GrQuadAAFlags::kNone;
-
-                    quads.push_back(
-                        {GrTest::TestRect(random),
-                         SkPMColor4f::FromBytes_RGBA(SkColorToPremulGrColor(random->nextU())),
-                         GrTest::TestMatrixInvertible(random), aaFlags});
-                }
-
-                return GrFillRectOp::MakeSet(context, std::move(paint), aaType, viewMatrix,
-                                             quads.begin(), quads.count(), stencil);
-            } else {
-                // Single local matrix
-                SkMatrix localMatrix = GrTest::TestMatrixInvertible(random);
-                return GrFillRectOp::Make(context, std::move(paint), aaType, aaFlags,
-                                          GrQuad::MakeFromRect(rect, viewMatrix),
-                                          GrQuad::MakeFromRect(rect, localMatrix), stencil);
-            }
+            // Single local matrix
+            SkMatrix localMatrix = GrTest::TestMatrixInvertible(random);
+            return GrFillRectOp::Make(context, std::move(paint), aaType, aaFlags,
+                                      GrQuad::MakeFromRect(rect, viewMatrix),
+                                      GrQuad::MakeFromRect(rect, localMatrix), stencil);
         } else {
             // Pass local rect directly
             SkRect localRect = GrTest::TestRect(random);
diff --git a/src/gpu/ops/GrFillRectOp.h b/src/gpu/ops/GrFillRectOp.h
index 768ed8b..600e87b 100644
--- a/src/gpu/ops/GrFillRectOp.h
+++ b/src/gpu/ops/GrFillRectOp.h
@@ -25,34 +25,53 @@
  * the GrPaint is only consumed by these methods if a valid op is returned. If null is returned then
  * the paint is unmodified and may still be used.
  */
-namespace GrFillRectOp {
+class GrFillRectOp {
+public:
 
-std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
-                               GrPaint&& paint,
-                               GrAAType aaType,
-                               GrQuadAAFlags aaFlags,
-                               const GrQuad& deviceQuad,
-                               const GrQuad& localQuad,
-                               const GrUserStencilSettings* stencil = nullptr);
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
+                                          GrPaint&& paint,
+                                          GrAAType aaType,
+                                          GrQuadAAFlags aaFlags,
+                                          const GrQuad& deviceQuad,
+                                          const GrQuad& localQuad,
+                                          const GrUserStencilSettings* stencil = nullptr);
 
-// Utility function to create a non-AA rect transformed by view. This is used commonly enough in
-// testing and GMs that manage ops without going through GrRTC that it's worth the convenience.
-std::unique_ptr<GrDrawOp> MakeNonAARect(GrRecordingContext* context,
-                                        GrPaint&& paint,
-                                        const SkMatrix& view,
-                                        const SkRect& rect,
-                                        const GrUserStencilSettings* stencil = nullptr);
+    // Utility function to create a non-AA rect transformed by view. This is used commonly enough
+    // in testing and GMs that manage ops without going through GrRTC that it's worth the
+    // convenience.
+    static std::unique_ptr<GrDrawOp> MakeNonAARect(GrRecordingContext* context,
+                                                   GrPaint&& paint,
+                                                   const SkMatrix& view,
+                                                   const SkRect& rect,
+                                                   const GrUserStencilSettings* stencil = nullptr);
 
-// Bulk API for drawing quads with a single op
-// TODO(michaelludwig) - remove if the bulk API is not useful for SkiaRenderer
-std::unique_ptr<GrDrawOp> MakeSet(GrRecordingContext* context,
-                                  GrPaint&& paint,
-                                  GrAAType aaType,
-                                  const SkMatrix& viewMatrix,
-                                  const GrRenderTargetContext::QuadSetEntry quads[],
-                                  int quadCount,
-                                  const GrUserStencilSettings* stencil = nullptr);
+    // Bulk API for drawing quads with a single op
+    // TODO(michaelludwig) - remove if the bulk API is not useful for SkiaRenderer
+    static void AddFillRectOps(GrRenderTargetContext*,
+                               const GrClip& clip,
+                               GrRecordingContext*,
+                               GrPaint&&,
+                               GrAAType,
+                               const SkMatrix& viewMatrix,
+                               const GrRenderTargetContext::QuadSetEntry quads[],
+                               int quadCount,
+                               const GrUserStencilSettings* = nullptr);
 
-} // namespace GrFillRectOp
+#if GR_TEST_UTILS
+    static uint32_t ClassID();
+#endif
+
+private:
+    // Create a GrFillRectOp that uses as many quads as possible from 'quads' w/o exceeding
+    // any index buffer size limits.
+    static std::unique_ptr<GrDrawOp> MakeOp(GrRecordingContext*,
+                                            GrPaint&&,
+                                            GrAAType,
+                                            const SkMatrix& viewMatrix,
+                                            const GrRenderTargetContext::QuadSetEntry quads[],
+                                            int quadCount,
+                                            const GrUserStencilSettings*,
+                                            int* numConsumed);
+};
 
 #endif // GrFillRectOp_DEFINED
diff --git a/src/gpu/ops/GrQuadPerEdgeAA.cpp b/src/gpu/ops/GrQuadPerEdgeAA.cpp
index 86fe00f..687d3a5 100644
--- a/src/gpu/ops/GrQuadPerEdgeAA.cpp
+++ b/src/gpu/ops/GrQuadPerEdgeAA.cpp
@@ -166,6 +166,15 @@
     }
 }
 
+int QuadLimit(IndexBufferOption option) {
+    switch (option) {
+        case IndexBufferOption::kPictureFramed: return GrResourceProvider::MaxNumAAQuads();
+        case IndexBufferOption::kIndexedRects:  return GrResourceProvider::MaxNumNonAAQuads();
+        case IndexBufferOption::kTriStrips:     return SK_MaxS32; // not limited by an indexBuffer
+    }
+
+    SkUNREACHABLE;
+}
 
 void ConfigureMesh(GrMesh* mesh, const VertexSpec& spec,
                    int runningQuadCount, int quadsInDraw, int maxVerts,
diff --git a/src/gpu/ops/GrQuadPerEdgeAA.h b/src/gpu/ops/GrQuadPerEdgeAA.h
index ae245ea..1e5675b 100644
--- a/src/gpu/ops/GrQuadPerEdgeAA.h
+++ b/src/gpu/ops/GrQuadPerEdgeAA.h
@@ -151,6 +151,9 @@
     // It will, correctly, return nullptr if the indexBufferOption is kTriStrips.
     sk_sp<const GrBuffer> GetIndexBuffer(GrMeshDrawOp::Target*, IndexBufferOption);
 
+    // What is the maximum number of quads allowed for the specified indexBuffer option?
+    int QuadLimit(IndexBufferOption);
+
     // This method will configure the vertex and index data of the provided 'mesh' to comply
     // with the indexing method specified in the vertexSpec. It is up to the calling code
     // to allocate and fill in the vertex data and acquire the correct indexBuffer if it is needed.
diff --git a/src/gpu/ops/GrSimpleMeshDrawOpHelper.h b/src/gpu/ops/GrSimpleMeshDrawOpHelper.h
index acf28b4..f184769 100644
--- a/src/gpu/ops/GrSimpleMeshDrawOpHelper.h
+++ b/src/gpu/ops/GrSimpleMeshDrawOpHelper.h
@@ -119,7 +119,7 @@
     GrAAType aaType() const { return static_cast<GrAAType>(fAAType); }
 
     void setAAType(GrAAType aaType) {
-      fAAType = static_cast<unsigned>(aaType);
+        fAAType = static_cast<unsigned>(aaType);
     }
 
     void executeDrawsAndUploads(const GrOp*, GrOpFlushState*, const SkRect& chainBounds);
diff --git a/tests/BulkFillRectTest.cpp b/tests/BulkFillRectTest.cpp
new file mode 100644
index 0000000..f80a360
--- /dev/null
+++ b/tests/BulkFillRectTest.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/gpu/GrClip.h"
+#include "src/gpu/GrContextPriv.h"
+#include "src/gpu/GrRenderTargetContext.h"
+#include "src/gpu/ops/GrFillRectOp.h"
+#include "tests/Test.h"
+
+static std::unique_ptr<GrRenderTargetContext> new_RTC(GrContext* context) {
+    return context->priv().makeDeferredRenderTargetContext(SkBackingFit::kExact, 128, 128,
+                                                           GrColorType::kRGBA_8888, nullptr);
+}
+
+typedef GrQuadAAFlags (*PerQuadAAFunc)(int i);
+
+static void bulk_fill_rect_create_test(skiatest::Reporter* reporter, GrContext* context,
+                                       PerQuadAAFunc perQuadAA, GrAAType overallAA,
+                                       int requestedTotNumQuads, int expectedNumOps) {
+
+    std::unique_ptr<GrRenderTargetContext> rtc = new_RTC(context);
+
+    auto quads = new GrRenderTargetContext::QuadSetEntry[requestedTotNumQuads];
+
+    for (int i = 0; i < requestedTotNumQuads; ++i) {
+        quads[i].fRect = SkRect::MakeWH(100.5f, 100.5f); // prevent the int non-AA optimization
+        quads[i].fColor = SK_PMColor4fWHITE;
+        quads[i].fLocalMatrix = SkMatrix::I();
+        quads[i].fAAFlags = perQuadAA(i);
+    }
+
+    GrPaint paint;
+
+    GrFillRectOp::AddFillRectOps(rtc.get(), GrNoClip(), context, std::move(paint), overallAA,
+                                 SkMatrix::I(), quads, requestedTotNumQuads);
+
+    GrOpsTask* opsTask = rtc->testingOnly_PeekLastOpsTask();
+    int actualNumOps = opsTask->numOpChains();
+
+    int actualTotNumQuads = 0;
+
+    for (int i = 0; i < actualNumOps; ++i) {
+        const GrOp* tmp = opsTask->getChain(i);
+        REPORTER_ASSERT(reporter, tmp->classID() == GrFillRectOp::ClassID());
+        REPORTER_ASSERT(reporter, tmp->isChainTail());
+        actualTotNumQuads += ((GrDrawOp*) tmp)->numQuads();
+    }
+
+    REPORTER_ASSERT(reporter, expectedNumOps == actualNumOps);
+    REPORTER_ASSERT(reporter, requestedTotNumQuads == actualTotNumQuads);
+
+    context->flush();
+
+    delete[] quads;
+}
+
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(BulkFillRectTest, reporter, ctxInfo) {
+    GrContext* context = ctxInfo.grContext();
+
+    if (!context->priv().caps()->dynamicStateArrayGeometryProcessorTextureSupport()) {
+        return;
+    }
+
+    // This is the simple case where there is no AA at all. We expect 2 non-AA clumps of quads.
+    {
+        auto noAA = [](int i) -> GrQuadAAFlags {
+            return GrQuadAAFlags::kNone;
+        };
+
+        static const int kNumExpectedOps = 2;
+
+        bulk_fill_rect_create_test(reporter, context, noAA, GrAAType::kNone,
+                                   2*GrResourceProvider::MaxNumNonAAQuads(), kNumExpectedOps);
+    }
+
+    // This is the same as the above case except the overall AA is kCoverage. However, since
+    // the per-quad AA is still none, all the quads should be downgraded to non-AA.
+    {
+        auto noAA = [](int i) -> GrQuadAAFlags {
+            return GrQuadAAFlags::kNone;
+        };
+
+        static const int kNumExpectedOps = 2;
+
+        bulk_fill_rect_create_test(reporter, context, noAA, GrAAType::kCoverage,
+                                   2*GrResourceProvider::MaxNumNonAAQuads(), kNumExpectedOps);
+    }
+
+    // This case has an overall AA of kCoverage but the per-quad AA alternates.
+    // We should end up with several aa-sized clumps
+    {
+        auto alternateAA = [](int i) -> GrQuadAAFlags {
+            return (i % 2) ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone;
+        };
+
+        int numExpectedOps = 2*GrResourceProvider::MaxNumNonAAQuads() /
+                                                 GrResourceProvider::MaxNumAAQuads();
+
+        bulk_fill_rect_create_test(reporter, context, alternateAA, GrAAType::kCoverage,
+                                   2*GrResourceProvider::MaxNumNonAAQuads(), numExpectedOps);
+    }
+
+    // In this case we have a run of MaxNumAAQuads non-AA quads and then AA quads. This
+    // exercises the case where we have a clump of quads that can't be upgraded to AA bc of
+    // its size. We expect one clump of non-AA quads followed by one clump of AA quads.
+    {
+        auto runOfNonAA = [](int i) -> GrQuadAAFlags {
+            return (i < GrResourceProvider::MaxNumAAQuads()) ? GrQuadAAFlags::kNone
+                                                             : GrQuadAAFlags::kAll;
+        };
+
+        static const int kNumExpectedOps = 2;
+
+        bulk_fill_rect_create_test(reporter, context, runOfNonAA, GrAAType::kCoverage,
+                                   2*GrResourceProvider::MaxNumAAQuads(), kNumExpectedOps);
+    }
+}