SkPictureGpuAnalyzer

Stateful helper for gathering multi-picture GPU stats.

Exposes the existing SkPicture GPU veto semantics, while preserving
the SKP impl (which has some nice properties: lazy, hierarchical,
cached per pic).

R=reed@google.com,bsalomon@google.com
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1974833003

Review-Url: https://codereview.chromium.org/1974833003
diff --git a/gyp/core.gypi b/gyp/core.gypi
index 0face28..477a9e7 100644
--- a/gyp/core.gypi
+++ b/gyp/core.gypi
@@ -206,6 +206,7 @@
         '<(skia_src_path)/core/SkPathRef.cpp',
         '<(skia_src_path)/core/SkPerspIter.h',
         '<(skia_src_path)/core/SkPicture.cpp',
+        '<(skia_src_path)/core/SkPictureAnalyzer.cpp',
         '<(skia_src_path)/core/SkPictureCommon.h',
         '<(skia_src_path)/core/SkPictureContentInfo.cpp',
         '<(skia_src_path)/core/SkPictureContentInfo.h',
@@ -385,6 +386,7 @@
         '<(skia_include_path)/core/SkPathMeasure.h',
         '<(skia_include_path)/core/SkPathRef.h',
         '<(skia_include_path)/core/SkPicture.h',
+        '<(skia_include_path)/core/SkPictureAnalyzer.h',
         '<(skia_include_path)/core/SkPictureRecorder.h',
         '<(skia_include_path)/core/SkPixelRef.h',
         '<(skia_include_path)/core/SkPoint.h',
diff --git a/include/core/SkPicture.h b/include/core/SkPicture.h
index 44a1e5b..eb35ef6 100644
--- a/include/core/SkPicture.h
+++ b/include/core/SkPicture.h
@@ -153,8 +153,10 @@
     static bool InternalOnly_StreamIsSKP(SkStream*, SkPictInfo*);
     static bool InternalOnly_BufferIsSKP(SkReadBuffer*, SkPictInfo*);
 
+#ifdef SK_SUPPORT_LEGACY_PICTURE_GPUVETO
     /** Return true if the picture is suitable for rendering on the GPU.  */
     bool suitableForGpuRasterization(GrContext*, const char** whyNot = NULL) const;
+#endif
 
     // Sent via SkMessageBus from destructor.
     struct DeletionMessage { int32_t fUniqueID; };  // TODO: -> uint32_t?
@@ -190,6 +192,7 @@
     friend class SkPictureData;
 
     virtual int numSlowPaths() const = 0;
+    friend class SkPictureGpuAnalyzer;
     friend struct SkPathCounter;
 
     // V35: Store SkRect (rather then width & height) in header
diff --git a/include/core/SkPictureAnalyzer.h b/include/core/SkPictureAnalyzer.h
new file mode 100644
index 0000000..fa8cdb1
--- /dev/null
+++ b/include/core/SkPictureAnalyzer.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkPictureAnalyzer_DEFINED
+#define SkPictureAnalyzer_DEFINED
+
+#include "SkRefCnt.h"
+#include "SkTypes.h"
+
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+
+class SkPicture;
+
+/** \class SkPictureGpuAnalyzer
+
+     Gathers GPU-related statistics for one or more SkPictures.
+*/
+class SK_API SkPictureGpuAnalyzer final : public SkNoncopyable {
+public:
+    explicit SkPictureGpuAnalyzer(sk_sp<GrContextThreadSafeProxy> = nullptr);
+    explicit SkPictureGpuAnalyzer(const sk_sp<SkPicture>& picture,
+                                  sk_sp<GrContextThreadSafeProxy> = nullptr);
+
+    /**
+     *  Process the given picture and accumulate its stats.
+     */
+    void analyze(const SkPicture*);
+
+    /**
+     *  Reset all accumulated stats.
+     */
+    void reset();
+
+    /**
+     *  Returns true if the analyzed pictures are suitable for rendering on the GPU.
+     */
+    bool suitableForGpuRasterization(const char** whyNot = nullptr) const;
+
+private:
+    uint32_t fNumSlowPaths;
+
+    typedef SkNoncopyable INHERITED;
+};
+
+#endif // SK_SUPPORT_GPU
+
+#endif // SkPictureAnalyzer_DEFINED
diff --git a/src/core/SkPicture.cpp b/src/core/SkPicture.cpp
index 94c1395..4e99455 100644
--- a/src/core/SkPicture.cpp
+++ b/src/core/SkPicture.cpp
@@ -219,6 +219,7 @@
     }
 }
 
+#ifdef SK_SUPPORT_LEGACY_PICTURE_GPUVETO
 bool SkPicture::suitableForGpuRasterization(GrContext*, const char** whyNot) const {
     if (this->numSlowPaths() > 5) {
         if (whyNot) { *whyNot = "Too many slow paths (either concave or dashed)."; }
@@ -226,6 +227,7 @@
     }
     return true;
 }
+#endif
 
 // Global setting to disable security precautions for serialization.
 void SkPicture::SetPictureIOSecurityPrecautionsEnabled_Dangerous(bool set) {
diff --git a/src/core/SkPictureAnalyzer.cpp b/src/core/SkPictureAnalyzer.cpp
new file mode 100644
index 0000000..0ba4202
--- /dev/null
+++ b/src/core/SkPictureAnalyzer.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkPicture.h"
+#include "SkPictureAnalyzer.h"
+
+#if SK_SUPPORT_GPU
+
+namespace {
+
+inline bool veto_predicate(uint32_t numSlowPaths) {
+    return numSlowPaths > 5;
+}
+
+} // anonymous namespace
+
+SkPictureGpuAnalyzer::SkPictureGpuAnalyzer(sk_sp<GrContextThreadSafeProxy> /* unused ATM */)
+    : fNumSlowPaths(0) { }
+
+SkPictureGpuAnalyzer::SkPictureGpuAnalyzer(const sk_sp<SkPicture>& picture,
+                                           sk_sp<GrContextThreadSafeProxy> ctx)
+    : SkPictureGpuAnalyzer(std::move(ctx)) {
+    this->analyze(picture.get());
+}
+
+void SkPictureGpuAnalyzer::analyze(const SkPicture* picture) {
+    if (!picture || veto_predicate(fNumSlowPaths)) {
+        return;
+    }
+
+    fNumSlowPaths += picture->numSlowPaths();
+}
+
+void SkPictureGpuAnalyzer::reset() {
+    fNumSlowPaths = 0;
+}
+
+bool SkPictureGpuAnalyzer::suitableForGpuRasterization(const char** whyNot) const {
+    if(veto_predicate(fNumSlowPaths)) {
+        if (whyNot) { *whyNot = "Too many slow paths (either concave or dashed)."; }
+        return false;
+    }
+    return true;
+}
+
+#endif // SK_SUPPORT_GPU
diff --git a/tests/PictureTest.cpp b/tests/PictureTest.cpp
index b7fa1d1..c71e663 100644
--- a/tests/PictureTest.cpp
+++ b/tests/PictureTest.cpp
@@ -20,6 +20,7 @@
 #include "SkMD5.h"
 #include "SkPaint.h"
 #include "SkPicture.h"
+#include "SkPictureAnalyzer.h"
 #include "SkPictureRecorder.h"
 #include "SkPictureUtils.h"
 #include "SkPixelRef.h"
@@ -158,7 +159,8 @@
     // path effects currently render an SkPicture undesireable for GPU rendering
 
     const char *reason = nullptr;
-    REPORTER_ASSERT(reporter, !picture->suitableForGpuRasterization(nullptr, &reason));
+    REPORTER_ASSERT(reporter,
+        !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization(&reason));
     REPORTER_ASSERT(reporter, reason);
 
     canvas = recorder.beginRecording(100, 100);
@@ -181,7 +183,7 @@
     }
     picture = recorder.finishRecordingAsPicture();
     // A lot of small AA concave paths should be fine for GPU rendering
-    REPORTER_ASSERT(reporter, picture->suitableForGpuRasterization(nullptr));
+    REPORTER_ASSERT(reporter, SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());
 
     canvas = recorder.beginRecording(100, 100);
     {
@@ -203,7 +205,7 @@
     }
     picture = recorder.finishRecordingAsPicture();
     // A lot of large AA concave paths currently render an SkPicture undesireable for GPU rendering
-    REPORTER_ASSERT(reporter, !picture->suitableForGpuRasterization(nullptr));
+    REPORTER_ASSERT(reporter, !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());
 
     canvas = recorder.beginRecording(100, 100);
     {
@@ -227,7 +229,7 @@
     }
     picture = recorder.finishRecordingAsPicture();
     // hairline stroked AA concave paths are fine for GPU rendering
-    REPORTER_ASSERT(reporter, picture->suitableForGpuRasterization(nullptr));
+    REPORTER_ASSERT(reporter, SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());
 
     canvas = recorder.beginRecording(100, 100);
     {
@@ -243,7 +245,7 @@
     }
     picture = recorder.finishRecordingAsPicture();
     // fast-path dashed effects are fine for GPU rendering ...
-    REPORTER_ASSERT(reporter, picture->suitableForGpuRasterization(nullptr));
+    REPORTER_ASSERT(reporter, SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());
 
     canvas = recorder.beginRecording(100, 100);
     {
@@ -257,18 +259,18 @@
     }
     picture = recorder.finishRecordingAsPicture();
     // ... but only when applied to drawPoint() calls
-    REPORTER_ASSERT(reporter, !picture->suitableForGpuRasterization(nullptr));
+    REPORTER_ASSERT(reporter, !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());
 
     // Nest the previous picture inside a new one.
     canvas = recorder.beginRecording(100, 100);
     {
-        canvas->drawPicture(picture.get());
+        canvas->drawPicture(picture);
     }
     picture = recorder.finishRecordingAsPicture();
-    REPORTER_ASSERT(reporter, !picture->suitableForGpuRasterization(nullptr));
+    REPORTER_ASSERT(reporter, !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());
 }
 
-#endif
+#endif // SK_SUPPORT_GPU
 
 static void test_savelayer_extraction(skiatest::Reporter* reporter) {
     static const int kWidth = 100;
@@ -1346,3 +1348,38 @@
     REPORTER_ASSERT(r, deserializedPicture->cullRect().right() == 3);
     REPORTER_ASSERT(r, deserializedPicture->cullRect().bottom() == 4);
 }
+
+#if SK_SUPPORT_GPU
+
+DEF_TEST(PictureGpuAnalyzer, r) {
+    SkPictureRecorder recorder;
+
+    {
+        SkCanvas* canvas = recorder.beginRecording(10, 10);
+        SkPaint paint;
+        SkScalar intervals [] = { 10, 20 };
+        paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 25));
+
+        for (int i = 0; i < 50; ++i) {
+            canvas->drawRect(SkRect::MakeWH(10, 10), paint);
+        }
+    }
+    sk_sp<SkPicture> vetoPicture(recorder.finishRecordingAsPicture());
+
+    SkPictureGpuAnalyzer analyzer;
+    REPORTER_ASSERT(r, analyzer.suitableForGpuRasterization());
+
+    analyzer.analyze(vetoPicture.get());
+    REPORTER_ASSERT(r, !analyzer.suitableForGpuRasterization());
+
+    analyzer.reset();
+    REPORTER_ASSERT(r, analyzer.suitableForGpuRasterization());
+
+    recorder.beginRecording(10, 10)->drawPicture(vetoPicture);
+    sk_sp<SkPicture> nestedVetoPicture(recorder.finishRecordingAsPicture());
+
+    analyzer.analyze(nestedVetoPicture.get());
+    REPORTER_ASSERT(r, !analyzer.suitableForGpuRasterization());
+}
+
+#endif // SK_SUPPORT_GPU
diff --git a/tools/gpuveto.cpp b/tools/gpuveto.cpp
index 59b3bef..4672a38 100644
--- a/tools/gpuveto.cpp
+++ b/tools/gpuveto.cpp
@@ -7,6 +7,7 @@
 
 #include "SkCommandLineFlags.h"
 #include "SkPicture.h"
+#include "SkPictureAnalyzer.h"
 #include "SkPictureRecorder.h"
 #include "SkStream.h"
 
@@ -57,7 +58,7 @@
                                               nullptr, 0));
     sk_sp<SkPicture> recorded(recorder.finishRecordingAsPicture());
 
-    if (recorded->suitableForGpuRasterization(nullptr)) {
+    if (SkPictureGpuAnalyzer(recorded).suitableForGpuRasterization(nullptr)) {
         SkDebugf("suitable\n");
     } else {
         SkDebugf("unsuitable\n");