Add GrThreadSafePipelineBuilder

GrThreadSafePipelineBuilder is the generic, base object the
GrContextThreadSafeProxy will hold. Each backend will create a
backend-specific version that is shared between the direct context
and the (possibly many) utility contexts.

Right now GrThreadSafePipelineBuilder just holds the pipeline
creation stats. Relatedly only GrGLGpu::ProgramCache and
GrVkResourceProvider::PipelineStateCache currently derive from
the new class (since they are the only backends that generate
pipeline stats).

Change-Id: I58f441c5c2b870bb5970c29cba19d1775864d52e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/378320
Commit-Queue: Robert Phillips <robertphillips@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 1d77327..55c7c78 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -261,6 +261,8 @@
   "$_src/gpu/GrTextureResolveRenderTask.h",
   "$_src/gpu/GrThreadSafeCache.cpp",
   "$_src/gpu/GrThreadSafeCache.h",
+  "$_src/gpu/GrThreadSafePipelineBuilder.cpp",
+  "$_src/gpu/GrThreadSafePipelineBuilder.h",
   "$_src/gpu/GrTracing.h",
   "$_src/gpu/GrTransferFromRenderTask.cpp",
   "$_src/gpu/GrTransferFromRenderTask.h",
diff --git a/include/gpu/GrContextThreadSafeProxy.h b/include/gpu/GrContextThreadSafeProxy.h
index ccfbefd..f645b87 100644
--- a/include/gpu/GrContextThreadSafeProxy.h
+++ b/include/gpu/GrContextThreadSafeProxy.h
@@ -23,6 +23,7 @@
 class GrContextThreadSafeProxyPriv;
 class GrTextBlobCache;
 class GrThreadSafeCache;
+class GrThreadSafePipelineBuilder;
 class SkSurfaceCharacterization;
 class SkSurfaceProps;
 
@@ -130,15 +131,16 @@
     // TODO: This should be part of the constructor but right now we have a chicken-and-egg problem
     // with GrContext where we get the caps by creating a GPU which requires a context (see the
     // `init` method on GrContext_Base).
-    void init(sk_sp<const GrCaps>);
+    void init(sk_sp<const GrCaps>, sk_sp<GrThreadSafePipelineBuilder>);
 
-    const GrBackendApi                 fBackend;
-    const GrContextOptions             fOptions;
-    const uint32_t                     fContextID;
-    sk_sp<const GrCaps>                fCaps;
-    std::unique_ptr<GrTextBlobCache>   fTextBlobCache;
-    std::unique_ptr<GrThreadSafeCache> fThreadSafeCache;
-    std::atomic<bool>                  fAbandoned{false};
+    const GrBackendApi                      fBackend;
+    const GrContextOptions                  fOptions;
+    const uint32_t                          fContextID;
+    sk_sp<const GrCaps>                     fCaps;
+    std::unique_ptr<GrTextBlobCache>        fTextBlobCache;
+    std::unique_ptr<GrThreadSafeCache>      fThreadSafeCache;
+    sk_sp<GrThreadSafePipelineBuilder>      fPipelineBuilder;
+    std::atomic<bool>                       fAbandoned{false};
 };
 
 #else // !SK_SUPPORT_GPU
diff --git a/src/gpu/GrContextThreadSafeProxy.cpp b/src/gpu/GrContextThreadSafeProxy.cpp
index 16b1847..2c3a221 100644
--- a/src/gpu/GrContextThreadSafeProxy.cpp
+++ b/src/gpu/GrContextThreadSafeProxy.cpp
@@ -14,6 +14,7 @@
 #include "src/gpu/GrBaseContextPriv.h"
 #include "src/gpu/GrCaps.h"
 #include "src/gpu/GrThreadSafeCache.h"
+#include "src/gpu/GrThreadSafePipelineBuilder.h"
 #include "src/gpu/effects/GrSkSLFP.h"
 #include "src/image/SkSurface_Gpu.h"
 
@@ -37,10 +38,12 @@
 
 GrContextThreadSafeProxy::~GrContextThreadSafeProxy() = default;
 
-void GrContextThreadSafeProxy::init(sk_sp<const GrCaps> caps) {
+void GrContextThreadSafeProxy::init(sk_sp<const GrCaps> caps,
+                                    sk_sp<GrThreadSafePipelineBuilder> pipelineBuilder) {
     fCaps = std::move(caps);
     fTextBlobCache = std::make_unique<GrTextBlobCache>(fContextID);
     fThreadSafeCache = std::make_unique<GrThreadSafeCache>();
+    fPipelineBuilder = std::move(pipelineBuilder);
 }
 
 SkSurfaceCharacterization GrContextThreadSafeProxy::createCharacterization(
@@ -167,3 +170,8 @@
     return sk_sp<GrContextThreadSafeProxy>(new GrContextThreadSafeProxy(backend, options));
 }
 
+void GrContextThreadSafeProxyPriv::init(sk_sp<const GrCaps> caps,
+                                        sk_sp<GrThreadSafePipelineBuilder> builder) const {
+    fProxy->init(std::move(caps), std::move(builder));
+}
+
diff --git a/src/gpu/GrContextThreadSafeProxyPriv.h b/src/gpu/GrContextThreadSafeProxyPriv.h
index 606aa16..aae17e4 100644
--- a/src/gpu/GrContextThreadSafeProxyPriv.h
+++ b/src/gpu/GrContextThreadSafeProxyPriv.h
@@ -21,9 +21,7 @@
  */
 class GrContextThreadSafeProxyPriv {
 public:
-    void init(sk_sp<const GrCaps> caps) const {
-        fProxy->init(std::move(caps));
-    }
+    void init(sk_sp<const GrCaps>, sk_sp<GrThreadSafePipelineBuilder>) const;
 
     bool matches(GrContext_Base* candidate) const {
         return fProxy == candidate->threadSafeProxy().get();
diff --git a/src/gpu/GrDirectContext.cpp b/src/gpu/GrDirectContext.cpp
index 3301027..42ad297 100644
--- a/src/gpu/GrDirectContext.cpp
+++ b/src/gpu/GrDirectContext.cpp
@@ -190,7 +190,7 @@
         return false;
     }
 
-    fThreadSafeProxy->priv().init(fGpu->refCaps());
+    fThreadSafeProxy->priv().init(fGpu->refCaps(), fGpu->refPipelineBuilder());
     if (!INHERITED::init()) {
         return false;
     }
diff --git a/src/gpu/GrDirectContextPriv.cpp b/src/gpu/GrDirectContextPriv.cpp
index 9d03432..cf96536 100644
--- a/src/gpu/GrDirectContextPriv.cpp
+++ b/src/gpu/GrDirectContextPriv.cpp
@@ -17,6 +17,7 @@
 #include "src/gpu/GrSurfaceContext.h"
 #include "src/gpu/GrSurfaceDrawContext.h"
 #include "src/gpu/GrTexture.h"
+#include "src/gpu/GrThreadSafePipelineBuilder.h"
 #include "src/gpu/SkGr.h"
 #include "src/gpu/effects/GrSkSLFP.h"
 #include "src/gpu/effects/generated/GrConfigConversionEffect.h"
@@ -112,14 +113,20 @@
 
 void GrDirectContextPriv::dumpGpuStats(SkString* out) const {
 #if GR_GPU_STATS
-    return fContext->fGpu->stats()->dump(out);
+    fContext->fGpu->stats()->dump(out);
+    if (auto builder = fContext->fGpu->pipelineBuilder()) {
+        builder->stats()->dump(out);
+    }
 #endif
 }
 
 void GrDirectContextPriv::dumpGpuStatsKeyValuePairs(SkTArray<SkString>* keys,
                                                     SkTArray<double>* values) const {
 #if GR_GPU_STATS
-    return fContext->fGpu->stats()->dumpKeyValuePairs(keys, values);
+    fContext->fGpu->stats()->dumpKeyValuePairs(keys, values);
+    if (auto builder = fContext->fGpu->pipelineBuilder()) {
+        builder->stats()->dumpKeyValuePairs(keys, values);
+    }
 #endif
 }
 
diff --git a/src/gpu/GrGpu.cpp b/src/gpu/GrGpu.cpp
index 43df47f..2a450f2 100644
--- a/src/gpu/GrGpu.cpp
+++ b/src/gpu/GrGpu.cpp
@@ -720,22 +720,9 @@
 #if GR_TEST_UTILS
 
 #if GR_GPU_STATS
-static const char* cache_result_to_str(int i) {
-    const char* kCacheResultStrings[GrGpu::Stats::kNumProgramCacheResults] = {
-        "hits",
-        "misses",
-        "partials"
-    };
-    static_assert(0 == (int) GrGpu::Stats::ProgramCacheResult::kHit);
-    static_assert(1 == (int) GrGpu::Stats::ProgramCacheResult::kMiss);
-    static_assert(2 == (int) GrGpu::Stats::ProgramCacheResult::kPartial);
-    static_assert(GrGpu::Stats::kNumProgramCacheResults == 3);
-    return kCacheResultStrings[i];
-}
 
 void GrGpu::Stats::dump(SkString* out) {
     out->appendf("Render Target Binds: %d\n", fRenderTargetBinds);
-    out->appendf("Shader Compilations: %d\n", fShaderCompilations);
     out->appendf("Textures Created: %d\n", fTextureCreates);
     out->appendf("Texture Uploads: %d\n", fTextureUploads);
     out->appendf("Transfers to Texture: %d\n", fTransfersToTexture);
@@ -747,26 +734,6 @@
     out->appendf("Number of Scratch MSAA Attachments reused %d\n",
                  fNumScratchMSAAAttachmentsReused);
 
-    SkASSERT(fNumInlineCompilationFailures == 0);
-    out->appendf("Number of Inline compile failures %d\n", fNumInlineCompilationFailures);
-    for (int i = 0; i < Stats::kNumProgramCacheResults-1; ++i) {
-        out->appendf("Inline Program Cache %s %d\n", cache_result_to_str(i),
-                     fInlineProgramCacheStats[i]);
-    }
-
-    SkASSERT(fNumPreCompilationFailures == 0);
-    out->appendf("Number of precompile failures %d\n", fNumPreCompilationFailures);
-    for (int i = 0; i < Stats::kNumProgramCacheResults-1; ++i) {
-        out->appendf("Precompile Program Cache %s %d\n", cache_result_to_str(i),
-                     fPreProgramCacheStats[i]);
-    }
-
-    SkASSERT(fNumCompilationFailures == 0);
-    out->appendf("Total number of compilation failures %d\n", fNumCompilationFailures);
-    out->appendf("Total number of partial compilation successes %d\n",
-                 fNumPartialCompilationSuccesses);
-    out->appendf("Total number of compilation successes %d\n", fNumCompilationSuccesses);
-
     // enable this block to output CSV-style stats for program pre-compilation
 #if 0
     SkASSERT(fNumInlineCompilationFailures == 0);
@@ -785,7 +752,6 @@
 
 void GrGpu::Stats::dumpKeyValuePairs(SkTArray<SkString>* keys, SkTArray<double>* values) {
     keys->push_back(SkString("render_target_binds")); values->push_back(fRenderTargetBinds);
-    keys->push_back(SkString("shader_compilations")); values->push_back(fShaderCompilations);
 }
 
 #endif // GR_GPU_STATS
diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h
index 90edca8..d020c42 100644
--- a/src/gpu/GrGpu.h
+++ b/src/gpu/GrGpu.h
@@ -43,6 +43,7 @@
 class GrStencilSettings;
 class GrSurface;
 class GrTexture;
+class GrThreadSafePipelineBuilder;
 class SkJSONWriter;
 
 namespace SkSL {
@@ -83,6 +84,9 @@
     // before GrDirectContext.
     virtual void disconnect(DisconnectType);
 
+    virtual GrThreadSafePipelineBuilder* pipelineBuilder() = 0;
+    virtual sk_sp<GrThreadSafePipelineBuilder> refPipelineBuilder() = 0;
+
     // Called by GrDirectContext::isContextLost. Returns true if the backend Gpu object has gotten
     // into an unrecoverable, lost state.
     virtual bool isDeviceLost() const { return false; }
@@ -414,16 +418,6 @@
 
     class Stats {
     public:
-        enum class ProgramCacheResult {
-            kHit,       // the program was found in the cache
-            kMiss,      // the program was not found in the cache (and was, thus, compiled)
-            kPartial,   // a precompiled version was found in the persistent cache
-
-            kLast = kPartial
-        };
-
-        static const int kNumProgramCacheResults = (int)ProgramCacheResult::kLast + 1;
-
 #if GR_GPU_STATS
         Stats() = default;
 
@@ -432,9 +426,6 @@
         int renderTargetBinds() const { return fRenderTargetBinds; }
         void incRenderTargetBinds() { fRenderTargetBinds++; }
 
-        int shaderCompilations() const { return fShaderCompilations; }
-        void incShaderCompilations() { fShaderCompilations++; }
-
         int textureCreates() const { return fTextureCreates; }
         void incTextureCreates() { fTextureCreates++; }
 
@@ -468,42 +459,12 @@
         int numScratchMSAAAttachmentsReused() const { return fNumScratchMSAAAttachmentsReused; }
         void incNumScratchMSAAAttachmentsReused() { ++fNumScratchMSAAAttachmentsReused; }
 
-        int numInlineCompilationFailures() const { return fNumInlineCompilationFailures; }
-        void incNumInlineCompilationFailures() { ++fNumInlineCompilationFailures; }
-
-        int numInlineProgramCacheResult(ProgramCacheResult stat) const {
-            return fInlineProgramCacheStats[(int) stat];
-        }
-        void incNumInlineProgramCacheResult(ProgramCacheResult stat) {
-            ++fInlineProgramCacheStats[(int) stat];
-        }
-
-        int numPreCompilationFailures() const { return fNumPreCompilationFailures; }
-        void incNumPreCompilationFailures() { ++fNumPreCompilationFailures; }
-
-        int numPreProgramCacheResult(ProgramCacheResult stat) const {
-            return fPreProgramCacheStats[(int) stat];
-        }
-        void incNumPreProgramCacheResult(ProgramCacheResult stat) {
-            ++fPreProgramCacheStats[(int) stat];
-        }
-
-        int numCompilationFailures() const { return fNumCompilationFailures; }
-        void incNumCompilationFailures() { ++fNumCompilationFailures; }
-
-        int numPartialCompilationSuccesses() const { return fNumPartialCompilationSuccesses; }
-        void incNumPartialCompilationSuccesses() { ++fNumPartialCompilationSuccesses; }
-
-        int numCompilationSuccesses() const { return fNumCompilationSuccesses; }
-        void incNumCompilationSuccesses() { ++fNumCompilationSuccesses; }
-
 #if GR_TEST_UTILS
         void dump(SkString*);
         void dumpKeyValuePairs(SkTArray<SkString>* keys, SkTArray<double>* values);
 #endif
     private:
         int fRenderTargetBinds = 0;
-        int fShaderCompilations = 0;
         int fTextureCreates = 0;
         int fTextureUploads = 0;
         int fTransfersToTexture = 0;
@@ -516,16 +477,6 @@
         int fNumScratchTexturesReused = 0;
         int fNumScratchMSAAAttachmentsReused = 0;
 
-        int fNumInlineCompilationFailures = 0;
-        int fInlineProgramCacheStats[kNumProgramCacheResults] = { 0 };
-
-        int fNumPreCompilationFailures = 0;
-        int fPreProgramCacheStats[kNumProgramCacheResults] = { 0 };
-
-        int fNumCompilationFailures = 0;
-        int fNumPartialCompilationSuccesses = 0;
-        int fNumCompilationSuccesses = 0;
-
 #else
 
 #if GR_TEST_UTILS
@@ -533,7 +484,6 @@
         void dumpKeyValuePairs(SkTArray<SkString>*, SkTArray<double>*) {}
 #endif
         void incRenderTargetBinds() {}
-        void incShaderCompilations() {}
         void incTextureCreates() {}
         void incTextureUploads() {}
         void incTransfersToTexture() {}
@@ -545,13 +495,6 @@
         void incNumSubmitToGpus() {}
         void incNumScratchTexturesReused() {}
         void incNumScratchMSAAAttachmentsReused() {}
-        void incNumInlineCompilationFailures() {}
-        void incNumInlineProgramCacheResult(ProgramCacheResult stat) {}
-        void incNumPreCompilationFailures() {}
-        void incNumPreProgramCacheResult(ProgramCacheResult stat) {}
-        void incNumCompilationFailures() {}
-        void incNumPartialCompilationSuccesses() {}
-        void incNumCompilationSuccesses() {}
 #endif
     };
 
diff --git a/src/gpu/GrThreadSafePipelineBuilder.cpp b/src/gpu/GrThreadSafePipelineBuilder.cpp
new file mode 100644
index 0000000..1a522ce
--- /dev/null
+++ b/src/gpu/GrThreadSafePipelineBuilder.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2021 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/gpu/GrThreadSafePipelineBuilder.h"
+
+#if GR_GPU_STATS
+#if GR_TEST_UTILS
+#include "include/core/SkString.h"
+
+using Stats = GrThreadSafePipelineBuilder::Stats;
+
+static const char* cache_result_to_str(int i) {
+    const char* kCacheResultStrings[Stats::kNumProgramCacheResults] = {
+        "hits",
+        "misses",
+        "partials"
+    };
+    static_assert(0 == (int) Stats::ProgramCacheResult::kHit);
+    static_assert(1 == (int) Stats::ProgramCacheResult::kMiss);
+    static_assert(2 == (int) Stats::ProgramCacheResult::kPartial);
+    static_assert(Stats::kNumProgramCacheResults == 3);
+    return kCacheResultStrings[i];
+}
+
+void GrThreadSafePipelineBuilder::Stats::dump(SkString* out) {
+    out->appendf("Shader Compilations: %d\n", fShaderCompilations.load());
+
+    SkASSERT(fNumInlineCompilationFailures == 0);
+    out->appendf("Number of Inline compile failures %d\n", fNumInlineCompilationFailures.load());
+    for (int i = 0; i < Stats::kNumProgramCacheResults-1; ++i) {
+        out->appendf("Inline Program Cache %s %d\n", cache_result_to_str(i),
+                     fInlineProgramCacheStats[i].load());
+    }
+
+    SkASSERT(fNumPreCompilationFailures == 0);
+    out->appendf("Number of precompile failures %d\n", fNumPreCompilationFailures.load());
+    for (int i = 0; i < Stats::kNumProgramCacheResults-1; ++i) {
+        out->appendf("Precompile Program Cache %s %d\n", cache_result_to_str(i),
+                     fPreProgramCacheStats[i].load());
+    }
+
+    SkASSERT(fNumCompilationFailures == 0);
+    out->appendf("Total number of compilation failures %d\n", fNumCompilationFailures.load());
+    out->appendf("Total number of partial compilation successes %d\n",
+                 fNumPartialCompilationSuccesses.load());
+    out->appendf("Total number of compilation successes %d\n", fNumCompilationSuccesses.load());
+}
+
+void GrThreadSafePipelineBuilder::Stats::dumpKeyValuePairs(SkTArray<SkString>* keys,
+                                                           SkTArray<double>* values) {
+    keys->push_back(SkString("shader_compilations")); values->push_back(fShaderCompilations);
+}
+
+#endif // GR_TEST_UTILS
+#endif // GR_GPU_STATS
diff --git a/src/gpu/GrThreadSafePipelineBuilder.h b/src/gpu/GrThreadSafePipelineBuilder.h
new file mode 100644
index 0000000..6d6b3a1
--- /dev/null
+++ b/src/gpu/GrThreadSafePipelineBuilder.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2021 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrThreadSafePipelineBuilder_Base_DEFINED
+#define GrThreadSafePipelineBuilder_Base_DEFINED
+
+#include "include/core/SkRefCnt.h"
+#include "include/gpu/GrConfig.h"
+
+#if GR_TEST_UTILS
+#include "include/private/SkTArray.h"
+class SkString;
+#endif
+
+class GrThreadSafePipelineBuilder : public SkRefCnt {
+public:
+    GrThreadSafePipelineBuilder() = default;
+
+    class Stats {
+    public:
+        enum class ProgramCacheResult {
+            kHit,       // the program was found in the cache
+            kMiss,      // the program was not found in the cache (and was, thus, compiled)
+            kPartial,   // a precompiled version was found in the persistent cache
+
+            kLast = kPartial
+        };
+
+#if GR_GPU_STATS
+        static const int kNumProgramCacheResults = (int)ProgramCacheResult::kLast + 1;
+
+        Stats() = default;
+
+        int shaderCompilations() const { return fShaderCompilations; }
+        void incShaderCompilations() { fShaderCompilations++; }
+
+        int numInlineCompilationFailures() const { return fNumInlineCompilationFailures; }
+        void incNumInlineCompilationFailures() { ++fNumInlineCompilationFailures; }
+
+        int numInlineProgramCacheResult(ProgramCacheResult stat) const {
+            return fInlineProgramCacheStats[(int) stat];
+        }
+        void incNumInlineProgramCacheResult(ProgramCacheResult stat) {
+            ++fInlineProgramCacheStats[(int) stat];
+        }
+
+        int numPreCompilationFailures() const { return fNumPreCompilationFailures; }
+        void incNumPreCompilationFailures() { ++fNumPreCompilationFailures; }
+
+        int numPreProgramCacheResult(ProgramCacheResult stat) const {
+            return fPreProgramCacheStats[(int) stat];
+        }
+        void incNumPreProgramCacheResult(ProgramCacheResult stat) {
+            ++fPreProgramCacheStats[(int) stat];
+        }
+
+        int numCompilationFailures() const { return fNumCompilationFailures; }
+        void incNumCompilationFailures() { ++fNumCompilationFailures; }
+
+        int numPartialCompilationSuccesses() const { return fNumPartialCompilationSuccesses; }
+        void incNumPartialCompilationSuccesses() { ++fNumPartialCompilationSuccesses; }
+
+        int numCompilationSuccesses() const { return fNumCompilationSuccesses; }
+        void incNumCompilationSuccesses() { ++fNumCompilationSuccesses; }
+
+#if GR_TEST_UTILS
+        void dump(SkString*);
+        void dumpKeyValuePairs(SkTArray<SkString>* keys, SkTArray<double>* values);
+#endif
+
+    private:
+        std::atomic<int> fShaderCompilations{0};
+
+        std::atomic<int> fNumInlineCompilationFailures{0};
+        std::atomic<int> fInlineProgramCacheStats[kNumProgramCacheResults]{0};
+
+        std::atomic<int> fNumPreCompilationFailures{0};
+        std::atomic<int> fPreProgramCacheStats[kNumProgramCacheResults]{0};
+
+        std::atomic<int> fNumCompilationFailures{0};
+        std::atomic<int> fNumPartialCompilationSuccesses{0};
+        std::atomic<int> fNumCompilationSuccesses{0};
+
+#else
+        void incShaderCompilations() {}
+        void incNumInlineCompilationFailures() {}
+        void incNumInlineProgramCacheResult(ProgramCacheResult stat) {}
+        void incNumPreCompilationFailures() {}
+        void incNumPreProgramCacheResult(ProgramCacheResult stat) {}
+        void incNumCompilationFailures() {}
+        void incNumPartialCompilationSuccesses() {}
+        void incNumCompilationSuccesses() {}
+
+#if GR_TEST_UTILS
+        void dump(SkString*) {}
+        void dumpKeyValuePairs(SkTArray<SkString>*, SkTArray<double>*) {}
+#endif
+
+#endif // GR_GPU_STATS
+    };
+
+    Stats* stats() { return &fStats; }
+
+protected:
+    Stats fStats;
+};
+
+#endif
diff --git a/src/gpu/d3d/GrD3DGpu.cpp b/src/gpu/d3d/GrD3DGpu.cpp
index 7d76ccf..81d9ebb 100644
--- a/src/gpu/d3d/GrD3DGpu.cpp
+++ b/src/gpu/d3d/GrD3DGpu.cpp
@@ -14,6 +14,7 @@
 #include "src/gpu/GrBackendUtils.h"
 #include "src/gpu/GrDataUtils.h"
 #include "src/gpu/GrTexture.h"
+#include "src/gpu/GrThreadSafePipelineBuilder.h"
 #include "src/gpu/d3d/GrD3DAMDMemoryAllocator.h"
 #include "src/gpu/d3d/GrD3DAttachment.h"
 #include "src/gpu/d3d/GrD3DBuffer.h"
@@ -29,6 +30,15 @@
 #include <DXProgrammableCapture.h>
 #endif
 
+GrThreadSafePipelineBuilder* GrD3DGpu::pipelineBuilder() {
+    return nullptr;
+}
+
+sk_sp<GrThreadSafePipelineBuilder> GrD3DGpu::refPipelineBuilder() {
+    return nullptr;
+}
+
+
 sk_sp<GrGpu> GrD3DGpu::Make(const GrD3DBackendContext& backendContext,
                             const GrContextOptions& contextOptions, GrDirectContext* direct) {
     sk_sp<GrD3DMemoryAllocator> memoryAllocator = backendContext.fMemoryAllocator;
diff --git a/src/gpu/d3d/GrD3DGpu.h b/src/gpu/d3d/GrD3DGpu.h
index 1b0e216..9d66f15 100644
--- a/src/gpu/d3d/GrD3DGpu.h
+++ b/src/gpu/d3d/GrD3DGpu.h
@@ -36,6 +36,9 @@
 
     GrD3DResourceProvider& resourceProvider() { return fResourceProvider; }
 
+    GrThreadSafePipelineBuilder* pipelineBuilder() override;
+    sk_sp<GrThreadSafePipelineBuilder> refPipelineBuilder() override;
+
     ID3D12Device* device() const { return fDevice.get(); }
     ID3D12CommandQueue* queue() const { return fQueue.get(); }
 
diff --git a/src/gpu/dawn/GrDawnGpu.cpp b/src/gpu/dawn/GrDawnGpu.cpp
index 9e64a05..fe635b6 100644
--- a/src/gpu/dawn/GrDawnGpu.cpp
+++ b/src/gpu/dawn/GrDawnGpu.cpp
@@ -21,6 +21,7 @@
 #include "src/gpu/GrSemaphore.h"
 #include "src/gpu/GrStencilSettings.h"
 #include "src/gpu/GrTexture.h"
+#include "src/gpu/GrThreadSafePipelineBuilder.h"
 #include "src/gpu/dawn/GrDawnAttachment.h"
 #include "src/gpu/dawn/GrDawnBuffer.h"
 #include "src/gpu/dawn/GrDawnCaps.h"
@@ -145,6 +146,14 @@
     INHERITED::disconnect(type);
 }
 
+GrThreadSafePipelineBuilder* GrDawnGpu::pipelineBuilder() {
+    return nullptr;
+}
+
+sk_sp<GrThreadSafePipelineBuilder> GrDawnGpu::refPipelineBuilder() {
+    return nullptr;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 GrOpsRenderPass* GrDawnGpu::onGetOpsRenderPass(
diff --git a/src/gpu/dawn/GrDawnGpu.h b/src/gpu/dawn/GrDawnGpu.h
index d24ebcb..53bfc1b 100644
--- a/src/gpu/dawn/GrDawnGpu.h
+++ b/src/gpu/dawn/GrDawnGpu.h
@@ -34,6 +34,9 @@
 
     void disconnect(DisconnectType) override;
 
+    GrThreadSafePipelineBuilder* pipelineBuilder() override;
+    sk_sp<GrThreadSafePipelineBuilder> refPipelineBuilder() override;
+
     GrStagingBufferManager* stagingBufferManager() override { return &fStagingBufferManager; }
     void takeOwnershipOfBuffer(sk_sp<GrGpuBuffer>) override;
 
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index 7b43170..61d3ac1 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -387,6 +387,9 @@
     fPathRendering.reset();
     fCopyProgramArrayBuffer.reset();
     fMipmapProgramArrayBuffer.reset();
+    if (fProgramCache) {
+        fProgramCache->reset();
+    }
 
     fHWProgram.reset();
     if (fHWProgramID) {
@@ -461,6 +464,7 @@
     }
 
     fHWProgram.reset();
+    fProgramCache->reset();
     fProgramCache.reset();
 
     fHWProgramID = 0;
@@ -482,6 +486,14 @@
     fFinishCallbacks.callAll(/* doDelete */ DisconnectType::kCleanup == type);
 }
 
+GrThreadSafePipelineBuilder* GrGLGpu::pipelineBuilder() {
+    return fProgramCache.get();
+}
+
+sk_sp<GrThreadSafePipelineBuilder> GrGLGpu::refPipelineBuilder() {
+    return fProgramCache;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 void GrGLGpu::onResetContext(uint32_t resetBits) {
@@ -3065,15 +3077,16 @@
     std::unique_ptr<SkSL::Program> program = GrSkSLtoGLSL(this, SkSL::ProgramKind::kVertex,
                                                           sksl, settings, &glsl, errorHandler);
     GrGLuint vshader = GrGLCompileAndAttachShader(*fGLContext, fCopyPrograms[progIdx].fProgram,
-                                                  GR_GL_VERTEX_SHADER, glsl, &fStats, errorHandler);
+                                                  GR_GL_VERTEX_SHADER, glsl, fProgramCache->stats(),
+                                                  errorHandler);
     SkASSERT(program->fInputs.isEmpty());
 
     sksl.assign(fshaderTxt.c_str(), fshaderTxt.size());
     program = GrSkSLtoGLSL(this, SkSL::ProgramKind::kFragment, sksl, settings, &glsl,
                            errorHandler);
     GrGLuint fshader = GrGLCompileAndAttachShader(*fGLContext, fCopyPrograms[progIdx].fProgram,
-                                                  GR_GL_FRAGMENT_SHADER, glsl, &fStats,
-                                                  errorHandler);
+                                                  GR_GL_FRAGMENT_SHADER, glsl,
+                                                  fProgramCache->stats(), errorHandler);
     SkASSERT(program->fInputs.isEmpty());
 
     GL_CALL(LinkProgram(fCopyPrograms[progIdx].fProgram));
@@ -3218,15 +3231,16 @@
     std::unique_ptr<SkSL::Program> program = GrSkSLtoGLSL(this, SkSL::ProgramKind::kVertex,
                                                           sksl, settings, &glsl, errorHandler);
     GrGLuint vshader = GrGLCompileAndAttachShader(*fGLContext, fMipmapPrograms[progIdx].fProgram,
-                                                  GR_GL_VERTEX_SHADER, glsl, &fStats, errorHandler);
+                                                  GR_GL_VERTEX_SHADER, glsl,
+                                                  fProgramCache->stats(), errorHandler);
     SkASSERT(program->fInputs.isEmpty());
 
     sksl.assign(fshaderTxt.c_str(), fshaderTxt.size());
     program = GrSkSLtoGLSL(this, SkSL::ProgramKind::kFragment, sksl, settings, &glsl,
                            errorHandler);
     GrGLuint fshader = GrGLCompileAndAttachShader(*fGLContext, fMipmapPrograms[progIdx].fProgram,
-                                                  GR_GL_FRAGMENT_SHADER, glsl, &fStats,
-                                                  errorHandler);
+                                                  GR_GL_FRAGMENT_SHADER, glsl,
+                                                  fProgramCache->stats(), errorHandler);
     SkASSERT(program->fInputs.isEmpty());
 
     GL_CALL(LinkProgram(fMipmapPrograms[progIdx].fProgram));
@@ -3675,14 +3689,14 @@
 bool GrGLGpu::compile(const GrProgramDesc& desc, const GrProgramInfo& programInfo) {
     SkASSERT(!(GrProcessor::CustomFeatures::kSampleLocations & programInfo.requestedFeatures()));
 
-    Stats::ProgramCacheResult stat;
+    GrThreadSafePipelineBuilder::Stats::ProgramCacheResult stat;
 
     sk_sp<GrGLProgram> tmp = fProgramCache->findOrCreateProgram(desc, programInfo, &stat);
     if (!tmp) {
         return false;
     }
 
-    return stat != Stats::ProgramCacheResult::kHit;
+    return stat != GrThreadSafePipelineBuilder::Stats::ProgramCacheResult::kHit;
 }
 
 #if GR_TEST_UTILS
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index 4a27164..16ea5e1 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -15,6 +15,7 @@
 #include "src/gpu/GrGpu.h"
 #include "src/gpu/GrNativeRect.h"
 #include "src/gpu/GrProgramDesc.h"
+#include "src/gpu/GrThreadSafePipelineBuilder.h"
 #include "src/gpu/GrWindowRectsState.h"
 #include "src/gpu/GrXferProcessor.h"
 #include "src/gpu/gl/GrGLAttachment.h"
@@ -37,6 +38,9 @@
 
     void disconnect(DisconnectType) override;
 
+    GrThreadSafePipelineBuilder* pipelineBuilder() override;
+    sk_sp<GrThreadSafePipelineBuilder> refPipelineBuilder() override;
+
     const GrGLContext& glContext() const { return *fGLContext; }
 
     const GrGLInterface* glInterface() const { return fGLContext->glInterface(); }
@@ -352,10 +356,10 @@
     bool copySurfaceAsBlitFramebuffer(GrSurface* dst, GrSurface* src, const SkIRect& srcRect,
                                       const SkIPoint& dstPoint);
 
-    class ProgramCache : public ::SkNoncopyable {
+    class ProgramCache : public GrThreadSafePipelineBuilder {
     public:
         ProgramCache(GrGLGpu* gpu);
-        ~ProgramCache();
+        ~ProgramCache() override;
 
         void abandon();
         void reset();
@@ -365,9 +369,9 @@
                                                Stats::ProgramCacheResult* stat) {
             sk_sp<GrGLProgram> tmp = this->findOrCreateProgram(nullptr, desc, programInfo, stat);
             if (!tmp) {
-                fGpu->fStats.incNumPreCompilationFailures();
+                fStats.incNumPreCompilationFailures();
             } else {
-                fGpu->fStats.incNumPreProgramCacheResult(*stat);
+                fStats.incNumPreProgramCacheResult(*stat);
             }
 
             return tmp;
@@ -514,7 +518,7 @@
     std::unique_ptr<GrGLContext> fGLContext;
 
     // GL program-related state
-    std::unique_ptr<ProgramCache> fProgramCache;
+    sk_sp<ProgramCache>         fProgramCache;
 
     ///////////////////////////////////////////////////////////////////////////
     ///@name Caching of GL State
diff --git a/src/gpu/gl/GrGLGpuProgramCache.cpp b/src/gpu/gl/GrGLGpuProgramCache.cpp
index 9916aaa..4bc0e78 100644
--- a/src/gpu/gl/GrGLGpuProgramCache.cpp
+++ b/src/gpu/gl/GrGLGpuProgramCache.cpp
@@ -61,9 +61,9 @@
     Stats::ProgramCacheResult stat;
     sk_sp<GrGLProgram> tmp = this->findOrCreateProgram(renderTarget, desc, programInfo, &stat);
     if (!tmp) {
-        fGpu->fStats.incNumInlineCompilationFailures();
+        fStats.incNumInlineCompilationFailures();
     } else {
-        fGpu->fStats.incNumInlineProgramCacheResult(stat);
+        fStats.incNumInlineProgramCacheResult(stat);
     }
 
     return tmp;
@@ -84,20 +84,20 @@
         if (!(*entry)->fProgram) {
             // Should we purge the program ID from the cache at this point?
             SkDEBUGFAIL("Couldn't create program from precompiled program");
-            fGpu->fStats.incNumCompilationFailures();
+            fStats.incNumCompilationFailures();
             return nullptr;
         }
-        fGpu->fStats.incNumPartialCompilationSuccesses();
+        fStats.incNumPartialCompilationSuccesses();
         *stat = Stats::ProgramCacheResult::kPartial;
     } else if (!entry) {
         // We have a cache miss
         sk_sp<GrGLProgram> program = GrGLProgramBuilder::CreateProgram(fGpu, renderTarget,
                                                                        desc, programInfo);
         if (!program) {
-            fGpu->fStats.incNumCompilationFailures();
+            fStats.incNumCompilationFailures();
             return nullptr;
         }
-        fGpu->fStats.incNumCompilationSuccesses();
+        fStats.incNumCompilationSuccesses();
         entry = fMap.insert(desc, std::make_unique<Entry>(std::move(program)));
         *stat = Stats::ProgramCacheResult::kMiss;
     }
diff --git a/src/gpu/gl/builders/GrGLProgramBuilder.cpp b/src/gpu/gl/builders/GrGLProgramBuilder.cpp
index cb14885..d0bd1f2 100644
--- a/src/gpu/gl/builders/GrGLProgramBuilder.cpp
+++ b/src/gpu/gl/builders/GrGLProgramBuilder.cpp
@@ -106,7 +106,7 @@
                                                    programId,
                                                    type,
                                                    glsl,
-                                                   gpu->stats(),
+                                                   gpu->pipelineBuilder()->stats(),
                                                    errHandler);
     if (!shaderId) {
         return false;
@@ -614,7 +614,8 @@
         }
 
         if (GrGLuint shaderID = GrGLCompileAndAttachShader(gpu->glContext(), programID, type, glsl,
-                                                           gpu->stats(), errorHandler)) {
+                                                           gpu->pipelineBuilder()->stats(),
+                                                           errorHandler)) {
             shadersToDelete.push_back(shaderID);
             return true;
         } else {
diff --git a/src/gpu/gl/builders/GrGLShaderStringBuilder.cpp b/src/gpu/gl/builders/GrGLShaderStringBuilder.cpp
index eb32e2e..a9a9c69 100644
--- a/src/gpu/gl/builders/GrGLShaderStringBuilder.cpp
+++ b/src/gpu/gl/builders/GrGLShaderStringBuilder.cpp
@@ -55,7 +55,7 @@
                                     GrGLuint programId,
                                     GrGLenum type,
                                     const SkSL::String& glsl,
-                                    GrGpu::Stats* stats,
+                                    GrThreadSafePipelineBuilder::Stats* stats,
                                     GrContextOptions::ShaderErrorHandler* errorHandler) {
     TRACE_EVENT0_ALWAYS("skia.gpu", "driver_compile_shader");
     const GrGLInterface* gli = glCtx.glInterface();
diff --git a/src/gpu/gl/builders/GrGLShaderStringBuilder.h b/src/gpu/gl/builders/GrGLShaderStringBuilder.h
index 16ac700..01d31dc 100644
--- a/src/gpu/gl/builders/GrGLShaderStringBuilder.h
+++ b/src/gpu/gl/builders/GrGLShaderStringBuilder.h
@@ -25,7 +25,7 @@
                                     GrGLuint programId,
                                     GrGLenum type,
                                     const SkSL::String& glsl,
-                                    GrGpu::Stats*,
+                                    GrThreadSafePipelineBuilder::Stats*,
                                     GrContextOptions::ShaderErrorHandler* errorHandler);
 
 #endif
diff --git a/src/gpu/mock/GrMockGpu.cpp b/src/gpu/mock/GrMockGpu.cpp
index 0caa16a..1927b18 100644
--- a/src/gpu/mock/GrMockGpu.cpp
+++ b/src/gpu/mock/GrMockGpu.cpp
@@ -7,6 +7,7 @@
 
 #include "src/gpu/mock/GrMockGpu.h"
 
+#include "src/gpu/GrThreadSafePipelineBuilder.h"
 #include "src/gpu/mock/GrMockAttachment.h"
 #include "src/gpu/mock/GrMockBuffer.h"
 #include "src/gpu/mock/GrMockCaps.h"
@@ -79,6 +80,16 @@
     this->initCapsAndCompiler(sk_make_sp<GrMockCaps>(contextOptions, options));
 }
 
+GrMockGpu::~GrMockGpu() {}
+
+GrThreadSafePipelineBuilder* GrMockGpu::pipelineBuilder() {
+    return nullptr;
+}
+
+sk_sp<GrThreadSafePipelineBuilder> GrMockGpu::refPipelineBuilder() {
+    return nullptr;
+}
+
 void GrMockGpu::querySampleLocations(GrRenderTarget* rt, SkTArray<SkPoint>* sampleLocations) {
     sampleLocations->reset();
     int numRemainingSamples = rt->numSamples();
diff --git a/src/gpu/mock/GrMockGpu.h b/src/gpu/mock/GrMockGpu.h
index d77ce5b..a3e0c76 100644
--- a/src/gpu/mock/GrMockGpu.h
+++ b/src/gpu/mock/GrMockGpu.h
@@ -22,7 +22,10 @@
 public:
     static sk_sp<GrGpu> Make(const GrMockOptions*, const GrContextOptions&, GrDirectContext*);
 
-    ~GrMockGpu() override {}
+    ~GrMockGpu() override;
+
+    GrThreadSafePipelineBuilder* pipelineBuilder() override;
+    sk_sp<GrThreadSafePipelineBuilder> refPipelineBuilder() override;
 
     GrFence SK_WARN_UNUSED_RESULT insertFence() override { return 0; }
     bool waitFence(GrFence) override { return true; }
diff --git a/src/gpu/mtl/GrMtlGpu.h b/src/gpu/mtl/GrMtlGpu.h
index 221273a..7d9ffd4 100644
--- a/src/gpu/mtl/GrMtlGpu.h
+++ b/src/gpu/mtl/GrMtlGpu.h
@@ -38,6 +38,9 @@
 
     void disconnect(DisconnectType) override;
 
+    GrThreadSafePipelineBuilder* pipelineBuilder() override;
+    sk_sp<GrThreadSafePipelineBuilder> refPipelineBuilder() override;
+
     const GrMtlCaps& mtlCaps() const { return *fMtlCaps.get(); }
 
     id<MTLDevice> device() const { return fDevice; }
diff --git a/src/gpu/mtl/GrMtlGpu.mm b/src/gpu/mtl/GrMtlGpu.mm
index 48ad7a8..0537cd6 100644
--- a/src/gpu/mtl/GrMtlGpu.mm
+++ b/src/gpu/mtl/GrMtlGpu.mm
@@ -15,6 +15,7 @@
 #include "src/gpu/GrDataUtils.h"
 #include "src/gpu/GrRenderTarget.h"
 #include "src/gpu/GrTexture.h"
+#include "src/gpu/GrThreadSafePipelineBuilder.h"
 #include "src/gpu/mtl/GrMtlBuffer.h"
 #include "src/gpu/mtl/GrMtlCommandBuffer.h"
 #include "src/gpu/mtl/GrMtlOpsRenderPass.h"
@@ -168,6 +169,14 @@
     }
 }
 
+GrThreadSafePipelineBuilder* GrMtlGpu::pipelineBuilder() {
+    return nullptr;
+}
+
+sk_sp<GrThreadSafePipelineBuilder> GrMtlGpu::refPipelineBuilder() {
+    return nullptr;
+}
+
 void GrMtlGpu::destroyResources() {
     this->submitCommandBuffer(SyncQueue::kForce_SyncQueue);
 
diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp
index 4bffecb..47de405 100644
--- a/src/gpu/vk/GrVkGpu.cpp
+++ b/src/gpu/vk/GrVkGpu.cpp
@@ -25,6 +25,7 @@
 #include "src/gpu/GrRenderTarget.h"
 #include "src/gpu/GrSurfaceDrawContext.h"
 #include "src/gpu/GrTexture.h"
+#include "src/gpu/GrThreadSafePipelineBuilder.h"
 #include "src/gpu/SkGpuDevice.h"
 #include "src/gpu/SkGr.h"
 #include "src/gpu/vk/GrVkAMDMemoryAllocator.h"
@@ -289,6 +290,14 @@
     }
 }
 
+GrThreadSafePipelineBuilder* GrVkGpu::pipelineBuilder() {
+    return fResourceProvider.pipelineStateCache();
+}
+
+sk_sp<GrThreadSafePipelineBuilder> GrVkGpu::refPipelineBuilder() {
+    return fResourceProvider.refPipelineStateCache();
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 GrOpsRenderPass* GrVkGpu::onGetOpsRenderPass(
@@ -1978,7 +1987,7 @@
         return false;
     }
 
-    Stats::ProgramCacheResult stat;
+    GrThreadSafePipelineBuilder::Stats::ProgramCacheResult stat;
 
     auto pipelineState = this->resourceProvider().findOrCreateCompatiblePipelineState(
                                     desc,
@@ -1989,7 +1998,7 @@
         return false;
     }
 
-    return stat != Stats::ProgramCacheResult::kHit;
+    return stat != GrThreadSafePipelineBuilder::Stats::ProgramCacheResult::kHit;
 }
 
 #if GR_TEST_UTILS
diff --git a/src/gpu/vk/GrVkGpu.h b/src/gpu/vk/GrVkGpu.h
index 870d75d..e2390d8 100644
--- a/src/gpu/vk/GrVkGpu.h
+++ b/src/gpu/vk/GrVkGpu.h
@@ -43,6 +43,9 @@
     void disconnect(DisconnectType) override;
     bool disconnected() const { return fDisconnected; }
 
+    GrThreadSafePipelineBuilder* pipelineBuilder() override;
+    sk_sp<GrThreadSafePipelineBuilder> refPipelineBuilder() override;
+
     const GrVkInterface* vkInterface() const { return fInterface.get(); }
     const GrVkCaps& vkCaps() const { return *fVkCaps; }
 
diff --git a/src/gpu/vk/GrVkPipelineStateBuilder.cpp b/src/gpu/vk/GrVkPipelineStateBuilder.cpp
index 52d0f34..fc77272 100644
--- a/src/gpu/vk/GrVkPipelineStateBuilder.cpp
+++ b/src/gpu/vk/GrVkPipelineStateBuilder.cpp
@@ -29,7 +29,9 @@
         VkRenderPass compatibleRenderPass,
         bool overrideSubpassForResolveLoad) {
 
-    gpu->stats()->incShaderCompilations();
+    GrVkResourceProvider& resourceProvider = gpu->resourceProvider();
+
+    resourceProvider.pipelineStateCache()->stats()->incShaderCompilations();
 
     // ensure that we use "." as a decimal separator when creating SkSL code
     GrAutoLocaleSetter als("C");
diff --git a/src/gpu/vk/GrVkPipelineStateCache.cpp b/src/gpu/vk/GrVkPipelineStateCache.cpp
index 7aa080e..3d3fb23 100644
--- a/src/gpu/vk/GrVkPipelineStateCache.cpp
+++ b/src/gpu/vk/GrVkPipelineStateCache.cpp
@@ -50,13 +50,13 @@
     // dump stats
 #ifdef SK_DEBUG
     if (c_DisplayVkPipelineCache) {
-        using CacheResult = GrGpu::Stats::ProgramCacheResult;
+        using CacheResult = Stats::ProgramCacheResult;
 
-        int misses = fGpu->stats()->numInlineProgramCacheResult(CacheResult::kMiss) +
-                     fGpu->stats()->numPreProgramCacheResult(CacheResult::kMiss);
+        int misses = fStats.numInlineProgramCacheResult(CacheResult::kMiss) +
+                     fStats.numPreProgramCacheResult(CacheResult::kMiss);
 
-        int total = misses + fGpu->stats()->numInlineProgramCacheResult(CacheResult::kHit) +
-                             fGpu->stats()->numPreProgramCacheResult(CacheResult::kHit);
+        int total = misses + fStats.numInlineProgramCacheResult(CacheResult::kHit) +
+                             fStats.numPreProgramCacheResult(CacheResult::kHit);
 
         SkDebugf("--- Pipeline State Cache ---\n");
         SkDebugf("Total requests: %d\n", total);
@@ -94,14 +94,14 @@
         return nullptr;
     }
 
-    GrGpu::Stats::ProgramCacheResult stat;
+    Stats::ProgramCacheResult stat;
     auto tmp = this->findOrCreatePipelineState(renderTarget, desc, programInfo,
                                                compatibleRenderPass, overrideSubpassForResolveLoad,
                                                &stat);
     if (!tmp) {
-        fGpu->stats()->incNumInlineCompilationFailures();
+        fStats.incNumInlineCompilationFailures();
     } else {
-        fGpu->stats()->incNumInlineProgramCacheResult(stat);
+        fStats.incNumInlineProgramCacheResult(stat);
     }
 
     return tmp;
@@ -113,15 +113,15 @@
         const GrProgramInfo& programInfo,
         VkRenderPass compatibleRenderPass,
         bool overrideSubpassForResolveLoad,
-        GrGpu::Stats::ProgramCacheResult* stat) {
+        Stats::ProgramCacheResult* stat) {
     if (stat) {
-        *stat = GrGpu::Stats::ProgramCacheResult::kHit;
+        *stat = Stats::ProgramCacheResult::kHit;
     }
 
     std::unique_ptr<Entry>* entry = fMap.find(desc);
     if (!entry) {
         if (stat) {
-            *stat = GrGpu::Stats::ProgramCacheResult::kMiss;
+            *stat = Stats::ProgramCacheResult::kMiss;
         }
         GrVkPipelineState* pipelineState(GrVkPipelineStateBuilder::CreatePipelineState(
                 fGpu, renderTarget, desc, programInfo, compatibleRenderPass,
diff --git a/src/gpu/vk/GrVkResourceProvider.cpp b/src/gpu/vk/GrVkResourceProvider.cpp
index bcf2768..75f1a25 100644
--- a/src/gpu/vk/GrVkResourceProvider.cpp
+++ b/src/gpu/vk/GrVkResourceProvider.cpp
@@ -22,7 +22,7 @@
 GrVkResourceProvider::GrVkResourceProvider(GrVkGpu* gpu)
     : fGpu(gpu)
     , fPipelineCache(VK_NULL_HANDLE) {
-    fPipelineStateCache = new PipelineStateCache(gpu);
+    fPipelineStateCache = sk_make_sp<PipelineStateCache>(gpu);
 }
 
 GrVkResourceProvider::~GrVkResourceProvider() {
@@ -30,7 +30,6 @@
     SkASSERT(0 == fExternalRenderPasses.count());
     SkASSERT(0 == fMSAALoadPipelines.count());
     SkASSERT(VK_NULL_HANDLE == fPipelineCache);
-    delete fPipelineStateCache;
 }
 
 VkPipelineCache GrVkResourceProvider::pipelineCache() {
@@ -266,14 +265,14 @@
         const GrProgramDesc& desc,
         const GrProgramInfo& programInfo,
         VkRenderPass compatibleRenderPass,
-        GrGpu::Stats::ProgramCacheResult* stat) {
+        GrThreadSafePipelineBuilder::Stats::ProgramCacheResult* stat) {
 
     auto tmp =  fPipelineStateCache->findOrCreatePipelineState(desc, programInfo,
                                                                compatibleRenderPass, stat);
     if (!tmp) {
-        fGpu->stats()->incNumPreCompilationFailures();
+        fPipelineStateCache->stats()->incNumPreCompilationFailures();
     } else {
-        fGpu->stats()->incNumPreProgramCacheResult(*stat);
+        fPipelineStateCache->stats()->incNumPreProgramCacheResult(*stat);
     }
 
     return tmp;
diff --git a/src/gpu/vk/GrVkResourceProvider.h b/src/gpu/vk/GrVkResourceProvider.h
index 38b7744..0b269f1 100644
--- a/src/gpu/vk/GrVkResourceProvider.h
+++ b/src/gpu/vk/GrVkResourceProvider.h
@@ -17,6 +17,7 @@
 #include "src/gpu/GrManagedResource.h"
 #include "src/gpu/GrProgramDesc.h"
 #include "src/gpu/GrResourceHandle.h"
+#include "src/gpu/GrThreadSafePipelineBuilder.h"
 #include "src/gpu/vk/GrVkDescriptorPool.h"
 #include "src/gpu/vk/GrVkDescriptorSetManager.h"
 #include "src/gpu/vk/GrVkPipelineStateBuilder.h"
@@ -42,6 +43,14 @@
     GrVkResourceProvider(GrVkGpu* gpu);
     ~GrVkResourceProvider();
 
+    GrThreadSafePipelineBuilder* pipelineStateCache() {
+        return fPipelineStateCache.get();
+    }
+
+    sk_sp<GrThreadSafePipelineBuilder> refPipelineStateCache() {
+        return fPipelineStateCache;
+    }
+
     // Set up any initial vk objects
     void init();
 
@@ -139,7 +148,7 @@
             const GrProgramDesc&,
             const GrProgramInfo&,
             VkRenderPass compatibleRenderPass,
-            GrGpu::Stats::ProgramCacheResult* stat);
+            GrThreadSafePipelineBuilder::Stats::ProgramCacheResult* stat);
 
     sk_sp<const GrVkPipeline> findOrCreateMSAALoadPipeline(
             const GrVkRenderPass& renderPass,
@@ -210,10 +219,10 @@
 
 private:
 
-    class PipelineStateCache : public ::SkNoncopyable {
+    class PipelineStateCache : public GrThreadSafePipelineBuilder {
     public:
         PipelineStateCache(GrVkGpu* gpu);
-        ~PipelineStateCache();
+        ~PipelineStateCache() override;
 
         void release();
         GrVkPipelineState* findOrCreatePipelineState(GrRenderTarget*,
@@ -223,7 +232,7 @@
         GrVkPipelineState* findOrCreatePipelineState(const GrProgramDesc& desc,
                                                      const GrProgramInfo& programInfo,
                                                      VkRenderPass compatibleRenderPass,
-                                                     GrGpu::Stats::ProgramCacheResult* stat) {
+                                                     Stats::ProgramCacheResult* stat) {
             return this->findOrCreatePipelineState(nullptr, desc, programInfo,
                                                    compatibleRenderPass, false, stat);
         }
@@ -236,7 +245,7 @@
                                                      const GrProgramInfo&,
                                                      VkRenderPass compatibleRenderPass,
                                                      bool overrideSubpassForResolveLoad,
-                                                     GrGpu::Stats::ProgramCacheResult*);
+                                                     Stats::ProgramCacheResult*);
 
         struct DescHash {
             uint32_t operator()(const GrProgramDesc& desc) const {
@@ -313,7 +322,7 @@
     SkTDynamicHash<GrVkSamplerYcbcrConversion, GrVkSamplerYcbcrConversion::Key> fYcbcrConversions;
 
     // Cache of GrVkPipelineStates
-    PipelineStateCache* fPipelineStateCache;
+    sk_sp<PipelineStateCache> fPipelineStateCache;
 
     SkSTArray<4, std::unique_ptr<GrVkDescriptorSetManager>> fDescriptorSetManagers;