CCPR: Add workaround for PowerVR crash

Bug: skia:
Change-Id: Icd00f81fda5366813f9c959fdc91b0415894cbfc
Reviewed-on: https://skia-review.googlesource.com/55360
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/gn/tests.gni b/gn/tests.gni
index 34c10b5..a6135ed 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -87,6 +87,7 @@
   "$_tests/GpuSampleLocationsTest.cpp",
   "$_tests/GradientTest.cpp",
   "$_tests/GrAllocatorTest.cpp",
+  "$_tests/GrCCPRTest.cpp",
   "$_tests/GrContextAbandonTest.cpp",
   "$_tests/GrContextFactoryTest.cpp",
   "$_tests/GrMemoryPoolTest.cpp",
diff --git a/src/gpu/GrCaps.cpp b/src/gpu/GrCaps.cpp
index a0e435a..f4c7de7 100644
--- a/src/gpu/GrCaps.cpp
+++ b/src/gpu/GrCaps.cpp
@@ -149,6 +149,8 @@
     writer->appendBool("Cross context texture support", fCrossContextTextureSupport);
 
     writer->appendBool("Draw Instead of Clear [workaround]", fUseDrawInsteadOfClear);
+    writer->appendBool("Blacklist Coverage Counting Path Renderer [workaround]",
+                       fBlacklistCoverageCounting);
     writer->appendBool("Prefer VRAM Use over flushes [workaround]", fPreferVRAMUseOverFlushes);
 
     if (this->advancedBlendEquationSupport()) {
diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp
index 3b401e6..088e5cb 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -62,6 +62,7 @@
     fRequiresCullFaceEnableDisableWhenDrawingLinesAfterNonLines = false;
 
     fBlitFramebufferFlags = kNoSupport_BlitFramebufferFlag;
+    fMaxInstancesPerDrawArraysWithoutCrashing = 0;
 
     fShaderCaps.reset(new GrShaderCaps(contextOptions));
 
@@ -595,6 +596,12 @@
         fRequiresCullFaceEnableDisableWhenDrawingLinesAfterNonLines = true;
     }
 
+    // Our Chromebook with kPowerVRRogue_GrGLRenderer seems to crash when glDrawArraysInstanced is
+    // given 1 << 15 or more instances.
+    if (kPowerVRRogue_GrGLRenderer == ctxInfo.renderer()) {
+        fMaxInstancesPerDrawArraysWithoutCrashing = 0x7fff;
+    }
+
     // Texture uploads sometimes seem to be ignored to textures bound to FBOS on Tegra3.
     if (kTegra3_GrGLRenderer == ctxInfo.renderer()) {
         fDisallowTexSubImageForUnormConfigTexturesEverBoundToFBO = true;
@@ -1374,6 +1381,8 @@
                        fDisallowTexSubImageForUnormConfigTexturesEverBoundToFBO);
     writer->appendBool("Intermediate texture for all updates of textures bound to FBOs",
                        fUseDrawInsteadOfAllRenderTargetWrites);
+    writer->appendBool("Max instances per glDrawArraysInstanced without crashing (or zero)",
+                       fMaxInstancesPerDrawArraysWithoutCrashing);
 
     writer->beginArray("configs");
 
diff --git a/src/gpu/gl/GrGLCaps.h b/src/gpu/gl/GrGLCaps.h
index 68cbc9b..5140aee 100644
--- a/src/gpu/gl/GrGLCaps.h
+++ b/src/gpu/gl/GrGLCaps.h
@@ -399,6 +399,15 @@
         return fRequiresCullFaceEnableDisableWhenDrawingLinesAfterNonLines;
     }
 
+    // Returns the observed maximum number of instances the driver can handle in a single call to
+    // glDrawArraysInstanced without crashing, or 'pendingInstanceCount' if this
+    // workaround is not necessary.
+    // NOTE: the return value may be larger than pendingInstanceCount.
+    int maxInstancesPerDrawArraysWithoutCrashing(int pendingInstanceCount) const {
+        return fMaxInstancesPerDrawArraysWithoutCrashing ? fMaxInstancesPerDrawArraysWithoutCrashing
+                                                         : pendingInstanceCount;
+    }
+
     bool initDescForDstCopy(const GrRenderTargetProxy* src, GrSurfaceDesc* desc,
                             bool* rectsMustMatch, bool* disallowSubrect) const override;
 
@@ -479,6 +488,7 @@
     bool fRequiresCullFaceEnableDisableWhenDrawingLinesAfterNonLines : 1;
 
     uint32_t fBlitFramebufferFlags;
+    int fMaxInstancesPerDrawArraysWithoutCrashing;
 
     /** Number type of the components (with out considering number of bits.) */
     enum FormatType {
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index 8190206..10dabe4 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -2619,10 +2619,14 @@
                                      int vertexCount, int baseVertex,
                                      const GrBuffer* instanceBuffer, int instanceCount,
                                      int baseInstance) {
-    const GrGLenum glPrimType = gr_primitive_type_to_gl_mode(primitiveType);
-    this->setupGeometry(primProc, nullptr, vertexBuffer, 0, instanceBuffer, baseInstance);
-    GL_CALL(DrawArraysInstanced(glPrimType, baseVertex, vertexCount, instanceCount));
-    fStats.incNumDraws();
+    GrGLenum glPrimType = gr_primitive_type_to_gl_mode(primitiveType);
+    int maxInstances = this->glCaps().maxInstancesPerDrawArraysWithoutCrashing(instanceCount);
+    for (int i = 0; i < instanceCount; i += maxInstances) {
+        this->setupGeometry(primProc, nullptr, vertexBuffer, 0, instanceBuffer, baseInstance + i);
+        GL_CALL(DrawArraysInstanced(glPrimType, baseVertex, vertexCount,
+                                    SkTMin(instanceCount - i, maxInstances)));
+        fStats.incNumDraws();
+    }
 }
 
 void GrGLGpu::sendIndexedInstancedMeshToGpu(const GrPrimitiveProcessor& primProc,
diff --git a/tests/GrCCPRTest.cpp b/tests/GrCCPRTest.cpp
new file mode 100644
index 0000000..d8be726
--- /dev/null
+++ b/tests/GrCCPRTest.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTypes.h"
+#include "Test.h"
+
+#if SK_SUPPORT_GPU
+
+#include "GrContext.h"
+#include "GrContextPriv.h"
+#include "GrClip.h"
+#include "GrRenderTargetContext.h"
+#include "GrRenderTargetContextPriv.h"
+#include "GrShape.h"
+#include "GrPathRenderer.h"
+#include "GrPaint.h"
+#include "SkMatrix.h"
+#include "SkRect.h"
+#include "ccpr/GrCoverageCountingPathRenderer.h"
+#include <cmath>
+
+static constexpr int kCanvasSize = 100;
+
+class CCPRPathDrawer {
+public:
+    CCPRPathDrawer(GrContext* ctx)
+            : fCtx(ctx)
+            , fCCPR(GrCoverageCountingPathRenderer::CreateIfSupported(*fCtx->caps()))
+            , fRTC(fCtx->makeDeferredRenderTargetContext(SkBackingFit::kExact, kCanvasSize,
+                                                         kCanvasSize, kRGBA_8888_GrPixelConfig,
+                                                         nullptr)) {
+        if (fCCPR) {
+            fCtx->contextPriv().addOnFlushCallbackObject(fCCPR.get());
+        }
+    }
+
+    ~CCPRPathDrawer() {
+        if (fCCPR) {
+            fCtx->contextPriv().testingOnly_flushAndRemoveOnFlushCallbackObject(fCCPR.get());
+        }
+    }
+
+    bool valid() { return fCCPR && fRTC; }
+
+    void clear() { fRTC->clear(nullptr, 0, true); }
+
+    void drawPath(const SkPath& path, GrColor4f color = GrColor4f(0, 1, 0, 1)) {
+        GrPaint paint;
+        paint.setColor4f(color);
+        GrNoClip noClip;
+        SkIRect clipBounds = SkIRect::MakeWH(kCanvasSize, kCanvasSize);
+        SkMatrix matrix = SkMatrix::I();
+        GrShape shape(path);
+        fCCPR->drawPath({fCtx, std::move(paint), &GrUserStencilSettings::kUnused, fRTC.get(),
+                         &noClip, &clipBounds, &matrix, &shape, GrAAType::kCoverage, false});
+    }
+
+    void flush() {
+        fCtx->flush();
+    }
+
+private:
+    GrContext* const                        fCtx;
+    sk_sp<GrCoverageCountingPathRenderer>   fCCPR;
+    sk_sp<GrRenderTargetContext>            fRTC;
+};
+
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrCCPRTest, reporter, ctxInfo) {
+    GrContext* const ctx = ctxInfo.grContext();
+    if (!GrCoverageCountingPathRenderer::IsSupported(*ctx->caps())) {
+        return;
+    }
+
+    CCPRPathDrawer ccpr(ctx);
+    if (!ccpr.valid()) {
+        ERRORF(reporter, "could not create render target context for ccpr.");
+        return;
+    }
+
+    // Test very busy paths.
+    static constexpr int kNumBusyVerbs = 1 << 17;
+    ccpr.clear();
+    SkPath busyPath;
+    busyPath.moveTo(0, 0); // top left
+    busyPath.lineTo(kCanvasSize, kCanvasSize); // bottom right
+    for (int i = 2; i < kNumBusyVerbs; ++i) {
+        float offset = i * ((float)kCanvasSize / kNumBusyVerbs);
+        busyPath.lineTo(kCanvasSize - offset, kCanvasSize + offset); // offscreen
+    }
+    ccpr.drawPath(busyPath);
+
+    ccpr.flush(); // If this doesn't crash, the test passed.
+                  // If it does, maybe fiddle with fMaxInstancesPerDrawArraysWithoutCrashing in your
+                  // platform's GrGLCaps.
+}
+
+#endif