Add GrMtlPipelineStateCache.

Allows us to cache MTLRenderPipelines rather than regenerating them
for every use.

Bug: skia:
Change-Id: I3357d3bc6d644074bd9d544a8d5205af56d918e5
Reviewed-on: https://skia-review.googlesource.com/c/195127
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Jim Van Verth <jvanverth@google.com>
diff --git a/src/gpu/GrPipeline.cpp b/src/gpu/GrPipeline.cpp
index 537c543..b16f164 100644
--- a/src/gpu/GrPipeline.cpp
+++ b/src/gpu/GrPipeline.cpp
@@ -106,3 +106,20 @@
         fFlags |= kScissorEnabled_Flag;
     }
 }
+
+uint32_t GrPipeline::getBlendInfoKey() const {
+    GrXferProcessor::BlendInfo blendInfo;
+    this->getXferProcessor().getBlendInfo(&blendInfo);
+
+    static const uint32_t kBlendWriteShift = 1;
+    static const uint32_t kBlendCoeffShift = 5;
+    GR_STATIC_ASSERT(kLast_GrBlendCoeff < (1 << kBlendCoeffShift));
+    GR_STATIC_ASSERT(kFirstAdvancedGrBlendEquation - 1 < 4);
+
+    uint32_t key = blendInfo.fWriteColor;
+    key |= (blendInfo.fSrcBlend << kBlendWriteShift);
+    key |= (blendInfo.fDstBlend << (kBlendWriteShift + kBlendCoeffShift));
+    key |= (blendInfo.fEquation << (kBlendWriteShift + 2 * kBlendCoeffShift));
+
+    return key;
+}
diff --git a/src/gpu/GrPipeline.h b/src/gpu/GrPipeline.h
index 91f619e..7efff26 100644
--- a/src/gpu/GrPipeline.h
+++ b/src/gpu/GrPipeline.h
@@ -195,6 +195,9 @@
         return SkString("No pipeline flags\n");
     }
 
+    // Used by Vulkan and Metal to cache their respective pipeline objects
+    uint32_t getBlendInfoKey() const;
+
 private:
     void markAsBad() { fFlags |= kIsBad_Flag; }
 
diff --git a/src/gpu/mtl/GrMtlGpuCommandBuffer.h b/src/gpu/mtl/GrMtlGpuCommandBuffer.h
index 1505789..322a2e8 100644
--- a/src/gpu/mtl/GrMtlGpuCommandBuffer.h
+++ b/src/gpu/mtl/GrMtlGpuCommandBuffer.h
@@ -73,8 +73,7 @@
             const GrPrimitiveProcessor& primProc,
             const GrPipeline& pipeline,
             const GrPipeline::FixedDynamicState* fixedDynamicState,
-            const GrMesh meshes[],
-            int meshCount);
+            GrPrimitiveType primType);
 
     void onDraw(const GrPrimitiveProcessor& primProc,
                 const GrPipeline& pipeline,
diff --git a/src/gpu/mtl/GrMtlGpuCommandBuffer.mm b/src/gpu/mtl/GrMtlGpuCommandBuffer.mm
index 914e9e3..77a1f5a 100644
--- a/src/gpu/mtl/GrMtlGpuCommandBuffer.mm
+++ b/src/gpu/mtl/GrMtlGpuCommandBuffer.mm
@@ -108,21 +108,8 @@
         const GrPrimitiveProcessor& primProc,
         const GrPipeline& pipeline,
         const GrPipeline::FixedDynamicState* fixedDynamicState,
-        const GrMesh meshes[],
-        int meshCount) {
+        GrPrimitiveType primType) {
     // TODO: resolve textures and regenerate mipmaps as needed
-    bool hasPoints = false;
-    for (int i = 0; i < meshCount; ++i) {
-        if (meshes[i].primitiveType() == GrPrimitiveType::kPoints) {
-            hasPoints = true;
-            break;
-        }
-    }
-    GrProgramDesc desc;
-    if (!GrProgramDesc::Build(&desc, fRenderTarget->config(), primProc, hasPoints,
-                              pipeline, fGpu)) {
-        return nullptr;
-    }
 
     const GrTextureProxy* const* primProcProxies = nullptr;
     if (fixedDynamicState) {
@@ -130,17 +117,19 @@
     }
     SkASSERT(SkToBool(primProcProxies) == SkToBool(primProc.numTextureSamplers()));
 
-    // TODO: use resource provider for pipeline
     GrMtlPipelineState* pipelineState =
-            GrMtlPipelineStateBuilder::CreatePipelineState(fRenderTarget, fOrigin, primProc,
-                                                           primProcProxies, pipeline,
-                                                           &desc, fGpu);
+        fGpu->resourceProvider().findOrCreateCompatiblePipelineState(fRenderTarget, fOrigin,
+                                                                     pipeline,
+                                                                     primProc,
+                                                                     primProcProxies,
+                                                                     primType);
     if (!pipelineState) {
         return nullptr;
     }
     // We cannot have an active encoder when we set the pipeline data since it requires its own
     // command encoder.
-    SkASSERT(fActiveRenderCmdEncoder == nil);
+    // TODO: this doesn't appear to be true -- we only use a command encoder here
+    // SkASSERT(fActiveRenderCmdEncoder == nil);
     pipelineState->setData(fRenderTarget, fOrigin, primProc, pipeline, primProcProxies);
 
     return pipelineState;
@@ -160,15 +149,16 @@
         return; // TODO: ScissorRects are not supported.
     }
 
-    std::unique_ptr<GrMtlPipelineState> pipelineState(
-            this->prepareDrawState(primProc, pipeline, fixedDynamicState, meshes, meshCount));
+    GrPrimitiveType primitiveType = meshes[0].primitiveType();
+    GrMtlPipelineState* pipelineState = this->prepareDrawState(primProc, pipeline,
+                                                               fixedDynamicState, primitiveType);
     if (!pipelineState) {
         return;
     }
 
     this->internalBegin();
-    [fActiveRenderCmdEncoder setRenderPipelineState: pipelineState->mtlPipelineState()];
 
+    [fActiveRenderCmdEncoder setRenderPipelineState: pipelineState->mtlPipelineState()];
     pipelineState->bind(fActiveRenderCmdEncoder);
     pipelineState->setBlendConstants(fActiveRenderCmdEncoder, fRenderTarget->config(),
                                      pipeline.getXferProcessor());
@@ -176,9 +166,26 @@
 
     for (int i = 0; i < meshCount; ++i) {
         const GrMesh& mesh = meshes[i];
-        SkASSERT(fActiveRenderCmdEncoder);
+        SkASSERT(nil != fActiveRenderCmdEncoder);
+        if (mesh.primitiveType() != primitiveType) {
+            SkDEBUGCODE(pipelineState = nullptr);
+            primitiveType = mesh.primitiveType();
+            pipelineState = this->prepareDrawState(primProc, pipeline, fixedDynamicState,
+                                                   primitiveType);
+            if (!pipelineState) {
+                return;
+            }
+
+            [fActiveRenderCmdEncoder setRenderPipelineState: pipelineState->mtlPipelineState()];
+            pipelineState->bind(fActiveRenderCmdEncoder);
+            pipelineState->setBlendConstants(fActiveRenderCmdEncoder, fRenderTarget->config(),
+                                             pipeline.getXferProcessor());
+            pipelineState->setDepthStencilState(fActiveRenderCmdEncoder);
+        }
+
         mesh.sendToGpu(this);
     }
+
     this->internalEnd();
     fCommandBufferInfo.fBounds.join(bounds);
 }
diff --git a/src/gpu/mtl/GrMtlPipelineStateBuilder.h b/src/gpu/mtl/GrMtlPipelineStateBuilder.h
index ce41618..fe4ef02 100644
--- a/src/gpu/mtl/GrMtlPipelineStateBuilder.h
+++ b/src/gpu/mtl/GrMtlPipelineStateBuilder.h
@@ -22,28 +22,62 @@
 
 class GrMtlPipelineStateBuilder : public GrGLSLProgramBuilder {
 public:
-    static GrMtlPipelineState* CreatePipelineState(GrRenderTarget*, GrSurfaceOrigin,
+    /**
+     * For Metal we want to cache the entire pipeline for reuse of draws. The Desc here holds all
+     * the information needed to differentiate one pipeline from another.
+     *
+     * The GrProgramDesc contains all the information need to create the actual shaders for the
+     * pipeline.
+     *
+     * For Metal we need to add to the GrProgramDesc to include the rest of the state on the
+     * pipeline. This includes blending information and primitive type. The pipeline is immutable
+     * so any remaining dynamic state is set via the MtlRenderCmdEncoder.
+     */
+    class Desc : public GrProgramDesc {
+    public:
+        static bool Build(Desc*,
+                          GrRenderTarget*,
+                          const GrPrimitiveProcessor&,
+                          const GrPipeline&,
+                          GrPrimitiveType,
+                          GrMtlGpu* gpu);
+
+        size_t shaderKeyLength() const { return fShaderKeyLength; }
+
+    private:
+        size_t fShaderKeyLength;
+
+        typedef GrProgramDesc INHERITED;
+    };
+
+    /** Generates a pipeline state.
+     *
+     * The GrMtlPipelineState implements what is specified in the GrPipeline and
+     * GrPrimitiveProcessor as input. After successful generation, the builder result objects are
+     * available to be used. This function may modify the program key by setting the surface origin
+     * key to 0 (unspecified) if it turns out the program does not care about the surface origin.
+     * @return true if generation was successful.
+     */
+    static GrMtlPipelineState* CreatePipelineState(GrMtlGpu*,
+                                                   GrRenderTarget*, GrSurfaceOrigin,
                                                    const GrPrimitiveProcessor&,
                                                    const GrTextureProxy* const primProcProxies[],
                                                    const GrPipeline&,
-                                                   GrProgramDesc*,
-                                                   GrMtlGpu*);
+                                                   Desc*);
 
 private:
-    GrMtlPipelineStateBuilder(GrRenderTarget*, GrSurfaceOrigin,
+    GrMtlPipelineStateBuilder(GrMtlGpu*, GrRenderTarget*, GrSurfaceOrigin,
+                              const GrPipeline&,
                               const GrPrimitiveProcessor&,
                               const GrTextureProxy* const primProcProxies[],
-                              const GrPipeline&,
-                              GrProgramDesc*, GrMtlGpu*);
+                              GrProgramDesc*);
+
+    GrMtlPipelineState* finalize(const GrPrimitiveProcessor& primProc,
+                                 const GrPipeline& pipeline,
+                                 Desc*);
 
     const GrCaps* caps() const override;
 
-    GrGLSLUniformHandler* uniformHandler() override { return &fUniformHandler; }
-
-    const GrGLSLUniformHandler* uniformHandler() const override { return &fUniformHandler; }
-
-    GrGLSLVaryingHandler* varyingHandler() override { return &fVaryingHandler; }
-
     void finalizeFragmentOutputColor(GrShaderVar& outputColor) override;
 
     void finalizeFragmentSecondaryColor(GrShaderVar& outputColor) override;
@@ -53,7 +87,9 @@
                                           const SkSL::Program::Settings& settings,
                                           GrProgramDesc* desc);
 
-    GrMtlPipelineState* finalize(const GrPrimitiveProcessor&, const GrPipeline&, GrProgramDesc*);
+    GrGLSLUniformHandler* uniformHandler() override { return &fUniformHandler; }
+    const GrGLSLUniformHandler* uniformHandler() const override { return &fUniformHandler; }
+    GrGLSLVaryingHandler* varyingHandler() override { return &fVaryingHandler; }
 
     GrMtlGpu* fGpu;
     GrMtlUniformHandler fUniformHandler;
diff --git a/src/gpu/mtl/GrMtlPipelineStateBuilder.mm b/src/gpu/mtl/GrMtlPipelineStateBuilder.mm
index 93ba094..8f0003b 100644
--- a/src/gpu/mtl/GrMtlPipelineStateBuilder.mm
+++ b/src/gpu/mtl/GrMtlPipelineStateBuilder.mm
@@ -17,14 +17,14 @@
 #import <simd/simd.h>
 
 GrMtlPipelineState* GrMtlPipelineStateBuilder::CreatePipelineState(
+        GrMtlGpu* gpu,
         GrRenderTarget* renderTarget, GrSurfaceOrigin origin,
         const GrPrimitiveProcessor& primProc,
         const GrTextureProxy* const primProcProxies[],
         const GrPipeline& pipeline,
-        GrProgramDesc* desc,
-        GrMtlGpu* gpu) {
-    GrMtlPipelineStateBuilder builder(renderTarget, origin, primProc, primProcProxies, pipeline,
-                                      desc, gpu);
+        Desc* desc) {
+    GrMtlPipelineStateBuilder builder(gpu, renderTarget, origin, pipeline, primProc,
+                                      primProcProxies, desc);
 
     if (!builder.emitAndInstallProcs()) {
         return nullptr;
@@ -32,12 +32,13 @@
     return builder.finalize(primProc, pipeline, desc);
 }
 
-GrMtlPipelineStateBuilder::GrMtlPipelineStateBuilder(GrRenderTarget* renderTarget, GrSurfaceOrigin origin,
+GrMtlPipelineStateBuilder::GrMtlPipelineStateBuilder(GrMtlGpu* gpu,
+                                                     GrRenderTarget* renderTarget,
+                                                     GrSurfaceOrigin origin,
+                                                     const GrPipeline& pipeline,
                                                      const GrPrimitiveProcessor& primProc,
                                                      const GrTextureProxy* const primProcProxies[],
-                                                     const GrPipeline& pipeline,
-                                                     GrProgramDesc* desc,
-                                                     GrMtlGpu* gpu)
+                                                     GrProgramDesc* desc)
         : INHERITED(renderTarget, origin, primProc, primProcProxies, pipeline, desc)
         , fGpu(gpu)
         , fUniformHandler(this)
@@ -316,7 +317,7 @@
 
 GrMtlPipelineState* GrMtlPipelineStateBuilder::finalize(const GrPrimitiveProcessor& primProc,
                                                         const GrPipeline& pipeline,
-                                                        GrProgramDesc* desc) {
+                                                        Desc* desc) {
     auto pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
 
     fVS.extensions().appendf("#extension GL_ARB_separate_shader_objects : enable\n");
@@ -332,6 +333,7 @@
     settings.fSharpenTextures = fGpu->getContext()->priv().options().fSharpenMipmappedTextures;
     SkASSERT(!this->fragColorIsInOut());
 
+    // TODO: Store shaders in cache
     id<MTLLibrary> vertexLibrary = nil;
     id<MTLLibrary> fragmentLibrary = nil;
     vertexLibrary = this->createMtlShaderLibrary(fVS,
@@ -354,6 +356,10 @@
     pipelineDescriptor.fragmentFunction = fragmentFunction;
     pipelineDescriptor.vertexDescriptor = create_vertex_descriptor(primProc);
     pipelineDescriptor.colorAttachments[0] = create_color_attachment(this->config(), pipeline);
+    GrMtlCaps* mtlCaps = (GrMtlCaps*)this->caps();
+    pipelineDescriptor.stencilAttachmentPixelFormat =
+        pipeline.isStencilEnabled() ? mtlCaps->preferredStencilFormat().fInternalFormat
+                                    : MTLPixelFormatInvalid;
 
     SkASSERT(pipelineDescriptor.vertexFunction);
     SkASSERT(pipelineDescriptor.fragmentFunction);
@@ -392,3 +398,35 @@
                                   std::move(fFragmentProcessors),
                                   fFragmentProcessorCnt);
 }
+
+//////////////////////////////////////////////////////////////////////////////
+
+bool GrMtlPipelineStateBuilder::Desc::Build(Desc* desc,
+                                            GrRenderTarget* renderTarget,
+                                            const GrPrimitiveProcessor& primProc,
+                                            const GrPipeline& pipeline,
+                                            GrPrimitiveType primitiveType,
+                                            GrMtlGpu* gpu) {
+    if (!INHERITED::Build(desc, renderTarget->config(), primProc,
+                          GrPrimitiveType::kLines == primitiveType, pipeline, gpu)) {
+        return false;
+    }
+
+    GrProcessorKeyBuilder b(&desc->key());
+
+    int keyLength = desc->key().count();
+    SkASSERT(0 == (keyLength % 4));
+    desc->fShaderKeyLength = SkToU32(keyLength);
+
+    b.add32(renderTarget->config());
+    b.add32(renderTarget->numColorSamples());
+    b.add32(pipeline.isStencilEnabled() ? gpu->mtlCaps().preferredStencilFormat().fInternalFormat
+                                        : MTLPixelFormatInvalid);
+    // Stencil samples don't seem to be tracked in the MTLRenderPipeline
+
+    b.add32(pipeline.getBlendInfoKey());
+
+    b.add32((uint32_t)primitiveType);
+
+    return true;
+}
diff --git a/src/gpu/mtl/GrMtlResourceProvider.h b/src/gpu/mtl/GrMtlResourceProvider.h
index d5b66ed..c0c0545 100644
--- a/src/gpu/mtl/GrMtlResourceProvider.h
+++ b/src/gpu/mtl/GrMtlResourceProvider.h
@@ -9,6 +9,8 @@
 #define GrMtlResourceProvider_DEFINED
 
 #include "GrMtlCopyPipelineState.h"
+#include "GrMtlPipelineStateBuilder.h"
+#include "SkLRUCache.h"
 #include "SkTArray.h"
 
 #import <metal/metal.h>
@@ -17,17 +19,67 @@
 
 class GrMtlResourceProvider {
 public:
-    GrMtlResourceProvider(GrMtlGpu* gpu) : fGpu(gpu) {}
+    GrMtlResourceProvider(GrMtlGpu* gpu);
 
     GrMtlCopyPipelineState* findOrCreateCopyPipelineState(MTLPixelFormat dstPixelFormat,
                                                           id<MTLFunction> vertexFunction,
                                                           id<MTLFunction> fragmentFunction,
                                                           MTLVertexDescriptor* vertexDescriptor);
 
+    GrMtlPipelineState* findOrCreateCompatiblePipelineState(
+        GrRenderTarget*, GrSurfaceOrigin,
+        const GrPipeline&,
+        const GrPrimitiveProcessor&,
+        const GrTextureProxy* const primProcProxies[],
+        GrPrimitiveType);
+
 private:
+#ifdef SK_DEBUG
+#define GR_PIPELINE_STATE_CACHE_STATS
+#endif
+
+    class PipelineStateCache : public ::SkNoncopyable {
+    public:
+        PipelineStateCache(GrMtlGpu* gpu);
+        ~PipelineStateCache();
+
+        GrMtlPipelineState* refPipelineState(GrRenderTarget*, GrSurfaceOrigin,
+                                             const GrPrimitiveProcessor&,
+                                             const GrTextureProxy* const primProcProxies[],
+                                             const GrPipeline&,
+                                             GrPrimitiveType);
+
+    private:
+        enum {
+            // We may actually have kMaxEntries+1 PipelineStates in context because we create a new
+            // PipelineState before evicting from the cache.
+            kMaxEntries = 128,
+        };
+
+        struct Entry;
+
+        struct DescHash {
+            uint32_t operator()(const GrProgramDesc& desc) const {
+                return SkOpts::hash_fn(desc.asKey(), desc.keyLength(), 0);
+            }
+        };
+
+        SkLRUCache<const GrMtlPipelineStateBuilder::Desc, std::unique_ptr<Entry>, DescHash> fMap;
+
+        GrMtlGpu*                    fGpu;
+
+#ifdef GR_PIPELINE_STATE_CACHE_STATS
+        int                         fTotalRequests;
+        int                         fCacheMisses;
+#endif
+    };
+
     SkTArray<std::unique_ptr<GrMtlCopyPipelineState>> fCopyPipelineStateCache;
 
     GrMtlGpu* fGpu;
+
+    // Cache of GrMtlPipelineStates
+    std::unique_ptr<PipelineStateCache> fPipelineStateCache;
 };
 
 #endif
diff --git a/src/gpu/mtl/GrMtlResourceProvider.mm b/src/gpu/mtl/GrMtlResourceProvider.mm
index 2e1e40d..9708978 100644
--- a/src/gpu/mtl/GrMtlResourceProvider.mm
+++ b/src/gpu/mtl/GrMtlResourceProvider.mm
@@ -9,10 +9,17 @@
 
 #include "GrMtlCopyManager.h"
 #include "GrMtlGpu.h"
+#include "GrMtlPipelineState.h"
 #include "GrMtlUtil.h"
 
 #include "SkSLCompiler.h"
 
+
+GrMtlResourceProvider::GrMtlResourceProvider(GrMtlGpu* gpu)
+    : fGpu(gpu) {
+    fPipelineStateCache.reset(new PipelineStateCache(gpu));
+}
+
 GrMtlCopyPipelineState* GrMtlResourceProvider::findOrCreateCopyPipelineState(
         MTLPixelFormat dstPixelFormat,
         id<MTLFunction> vertexFunction,
@@ -29,3 +36,92 @@
              fGpu, dstPixelFormat, vertexFunction, fragmentFunction, vertexDescriptor));
     return fCopyPipelineStateCache.back().get();
 }
+
+GrMtlPipelineState* GrMtlResourceProvider::findOrCreateCompatiblePipelineState(
+        GrRenderTarget* renderTarget, GrSurfaceOrigin origin,
+        const GrPipeline& pipeline, const GrPrimitiveProcessor& proc,
+        const GrTextureProxy* const primProcProxies[], GrPrimitiveType primType) {
+    return fPipelineStateCache->refPipelineState(renderTarget, origin, proc, primProcProxies,
+                                                 pipeline, primType);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////
+
+#ifdef GR_PIPELINE_STATE_CACHE_STATS
+// Display pipeline state cache usage
+static const bool c_DisplayMtlPipelineCache{false};
+#endif
+
+struct GrMtlResourceProvider::PipelineStateCache::Entry {
+    Entry(GrMtlGpu* gpu, GrMtlPipelineState* pipelineState)
+    : fGpu(gpu)
+    , fPipelineState(pipelineState) {}
+
+    GrMtlGpu* fGpu;
+    std::unique_ptr<GrMtlPipelineState> fPipelineState;
+};
+
+GrMtlResourceProvider::PipelineStateCache::PipelineStateCache(GrMtlGpu* gpu)
+    : fMap(kMaxEntries)
+    , fGpu(gpu)
+#ifdef GR_PIPELINE_STATE_CACHE_STATS
+    , fTotalRequests(0)
+    , fCacheMisses(0)
+#endif
+{}
+
+GrMtlResourceProvider::PipelineStateCache::~PipelineStateCache() {
+    // TODO: determine if we need abandon/release methods
+    fMap.reset();
+    // dump stats
+#ifdef GR_PIPELINE_STATE_CACHE_STATS
+    if (c_DisplayMtlPipelineCache) {
+        SkDebugf("--- Pipeline State Cache ---\n");
+        SkDebugf("Total requests: %d\n", fTotalRequests);
+        SkDebugf("Cache misses: %d\n", fCacheMisses);
+        SkDebugf("Cache miss %%: %f\n", (fTotalRequests > 0) ?
+                 100.f * fCacheMisses / fTotalRequests :
+                 0.f);
+        SkDebugf("---------------------\n");
+    }
+#endif
+}
+
+GrMtlPipelineState* GrMtlResourceProvider::PipelineStateCache::refPipelineState(
+        GrRenderTarget* renderTarget,
+        GrSurfaceOrigin origin,
+        const GrPrimitiveProcessor& primProc,
+        const GrTextureProxy* const primProcProxies[],
+        const GrPipeline& pipeline,
+        GrPrimitiveType primType) {
+#ifdef GR_PIPELINE_STATE_CACHE_STATS
+    ++fTotalRequests;
+#endif
+    // Get GrMtlProgramDesc
+    GrMtlPipelineStateBuilder::Desc desc;
+    if (!GrMtlPipelineStateBuilder::Desc::Build(&desc, renderTarget, primProc, pipeline, primType,
+                                                fGpu)) {
+        GrCapsDebugf(fGpu->caps(), "Failed to build mtl program descriptor!\n");
+        return nullptr;
+    }
+
+    std::unique_ptr<Entry>* entry = fMap.find(desc);
+    if (!entry) {
+        // Didn't find an origin-independent version, check with the specific origin
+        desc.setSurfaceOriginKey(GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(origin));
+        entry = fMap.find(desc);
+    }
+    if (!entry) {
+#ifdef GR_PIPELINE_STATE_CACHE_STATS
+        ++fCacheMisses;
+#endif
+        GrMtlPipelineState* pipelineState(GrMtlPipelineStateBuilder::CreatePipelineState(
+                fGpu, renderTarget, origin, primProc, primProcProxies, pipeline, &desc));
+        if (nullptr == pipelineState) {
+            return nullptr;
+        }
+        entry = fMap.insert(desc, std::unique_ptr<Entry>(new Entry(fGpu, pipelineState)));
+        return (*entry)->fPipelineState.get();
+    }
+    return (*entry)->fPipelineState.get();
+}
diff --git a/src/gpu/vk/GrVkPipelineStateBuilder.cpp b/src/gpu/vk/GrVkPipelineStateBuilder.cpp
index 400c1e9..b7485a6 100644
--- a/src/gpu/vk/GrVkPipelineStateBuilder.cpp
+++ b/src/gpu/vk/GrVkPipelineStateBuilder.cpp
@@ -374,23 +374,6 @@
 
 //////////////////////////////////////////////////////////////////////////////
 
-uint32_t get_blend_info_key(const GrPipeline& pipeline) {
-    GrXferProcessor::BlendInfo blendInfo;
-    pipeline.getXferProcessor().getBlendInfo(&blendInfo);
-
-    static const uint32_t kBlendWriteShift = 1;
-    static const uint32_t kBlendCoeffShift = 5;
-    GR_STATIC_ASSERT(kLast_GrBlendCoeff < (1 << kBlendCoeffShift));
-    GR_STATIC_ASSERT(kFirstAdvancedGrBlendEquation - 1 < 4);
-
-    uint32_t key = blendInfo.fWriteColor;
-    key |= (blendInfo.fSrcBlend << kBlendWriteShift);
-    key |= (blendInfo.fDstBlend << (kBlendWriteShift + kBlendCoeffShift));
-    key |= (blendInfo.fEquation << (kBlendWriteShift + 2 * kBlendCoeffShift));
-
-    return key;
-}
-
 bool GrVkPipelineStateBuilder::Desc::Build(Desc* desc,
                                            GrRenderTarget* renderTarget,
                                            const GrPrimitiveProcessor& primProc,
@@ -415,7 +398,7 @@
 
     stencil.genKey(&b);
 
-    b.add32(get_blend_info_key(pipeline));
+    b.add32(pipeline.getBlendInfoKey());
 
     b.add32((uint32_t)primitiveType);