Add support for immuatable samplers in vulkan.

For ycbcr conversions we need add immutable samplers to the creation of
the VkPipeline via the descriptor set layout.

Bug: skia:
Change-Id: I5eea6037191fd34d26d49f58533035316158cacd
Reviewed-on: https://skia-review.googlesource.com/c/171642
Commit-Queue: Greg Daniel <egdaniel@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h
index 028f1da..cb8b2e9 100644
--- a/src/gpu/GrGpu.h
+++ b/src/gpu/GrGpu.h
@@ -406,6 +406,18 @@
         }
     }
 
+    /**
+     * Returns a key that represents the sampler that will be created for the passed in parameters.
+     * Currently this key is only used when we are building a vulkan pipeline with immutable
+     * samplers. In that case, we need our cache key to also contain this key.
+     *
+     * A return value of 0 indicates that the program/pipeline we are creating is not affected by
+     * the sampler.
+     */
+    virtual uint32_t getExtraSamplerKeyForProgram(const GrSamplerState&, const GrBackendFormat&) {
+        return 0;
+    }
+
 protected:
     // Handles cases where a surface will be updated without a call to flushRenderTarget.
     void didWriteToSurface(GrSurface* surface, GrSurfaceOrigin origin, const SkIRect* bounds,
diff --git a/src/gpu/GrPrimitiveProcessor.cpp b/src/gpu/GrPrimitiveProcessor.cpp
index b84af68..672056c 100644
--- a/src/gpu/GrPrimitiveProcessor.cpp
+++ b/src/gpu/GrPrimitiveProcessor.cpp
@@ -55,8 +55,9 @@
 
 GrPrimitiveProcessor::TextureSampler::TextureSampler(GrTextureType textureType,
                                                      GrPixelConfig config,
-                                                     const GrSamplerState& samplerState) {
-    this->reset(textureType, config, samplerState);
+                                                     const GrSamplerState& samplerState,
+                                                     uint32_t extraSamplerKey) {
+    this->reset(textureType, config, samplerState, extraSamplerKey);
 }
 
 GrPrimitiveProcessor::TextureSampler::TextureSampler(GrTextureType textureType,
@@ -68,12 +69,15 @@
 
 void GrPrimitiveProcessor::TextureSampler::reset(GrTextureType textureType,
                                                  GrPixelConfig config,
-                                                 const GrSamplerState& samplerState) {
+                                                 const GrSamplerState& samplerState,
+                                                 uint32_t extraSamplerKey) {
     SkASSERT(kUnknown_GrPixelConfig != config);
     fSamplerState = samplerState;
     fSamplerState.setFilterMode(clamp_filter(textureType, samplerState.filter()));
     fTextureType = textureType;
     fConfig = config;
+    fExtraSamplerKey = extraSamplerKey;
+    SkASSERT(!fExtraSamplerKey || textureType == GrTextureType::kExternal);
 }
 
 void GrPrimitiveProcessor::TextureSampler::reset(GrTextureType textureType,
diff --git a/src/gpu/GrPrimitiveProcessor.h b/src/gpu/GrPrimitiveProcessor.h
index ffb7ac0..be3d86a 100644
--- a/src/gpu/GrPrimitiveProcessor.h
+++ b/src/gpu/GrPrimitiveProcessor.h
@@ -244,7 +244,7 @@
 public:
     TextureSampler() = default;
 
-    TextureSampler(GrTextureType, GrPixelConfig, const GrSamplerState&);
+    TextureSampler(GrTextureType, GrPixelConfig, const GrSamplerState&, uint32_t extraSamplerKey);
 
     explicit TextureSampler(GrTextureType, GrPixelConfig,
                             GrSamplerState::Filter = GrSamplerState::Filter::kNearest,
@@ -253,22 +253,25 @@
     TextureSampler(const TextureSampler&) = delete;
     TextureSampler& operator=(const TextureSampler&) = delete;
 
-    void reset(GrTextureType, GrPixelConfig, const GrSamplerState&);
+    void reset(GrTextureType, GrPixelConfig, const GrSamplerState&, uint32_t extraSamplerKey = 0);
     void reset(GrTextureType, GrPixelConfig,
-               GrSamplerState::Filter = GrSamplerState::Filter::kNearest,
-               GrSamplerState::WrapMode wrapXAndY = GrSamplerState::WrapMode::kClamp);
+               GrSamplerState::Filter,
+               GrSamplerState::WrapMode wrapXAndY);
 
     GrTextureType textureType() const { return fTextureType; }
     GrPixelConfig config() const { return fConfig; }
 
     const GrSamplerState& samplerState() const { return fSamplerState; }
 
+    uint32_t extraSamplerKey() const { return fExtraSamplerKey; }
+
     bool isInitialized() const { return fConfig != kUnknown_GrPixelConfig; }
 
 private:
     GrSamplerState fSamplerState;
     GrTextureType fTextureType = GrTextureType::k2D;
     GrPixelConfig fConfig = kUnknown_GrPixelConfig;
+    uint32_t fExtraSamplerKey = 0;
 };
 
 const GrPrimitiveProcessor::TextureSampler& GrPrimitiveProcessor::IthTextureSampler(int i) {
diff --git a/src/gpu/GrProgramDesc.cpp b/src/gpu/GrProgramDesc.cpp
index 5c10b9f..aa84c98 100644
--- a/src/gpu/GrProgramDesc.cpp
+++ b/src/gpu/GrProgramDesc.cpp
@@ -50,7 +50,7 @@
 }
 
 static void add_sampler_keys(GrProcessorKeyBuilder* b, const GrFragmentProcessor& fp,
-                             const GrShaderCaps& caps) {
+                             GrGpu* gpu, const GrShaderCaps& caps) {
     int numTextureSamplers = fp.numTextureSamplers();
     // Need two bytes per key.
     int word32Count = (numTextureSamplers + 1) / 2;
@@ -62,6 +62,16 @@
         const GrFragmentProcessor::TextureSampler& sampler = fp.textureSampler(i);
         const GrTexture* tex = sampler.peekTexture();
         k16[i] = sampler_key(tex->texturePriv().textureType(), tex->config(), caps);
+        uint32_t extraSamplerKey = gpu->getExtraSamplerKeyForProgram(
+                sampler.samplerState(), sampler.proxy()->backendFormat());
+        if (extraSamplerKey) {
+            SkASSERT(sampler.proxy()->textureType() == GrTextureType::kExternal);
+            // We first mark the normal sampler key with last bit to flag that it has an extra
+            // sampler key. We then add all the extraSamplerKeys to the end of the normal ones.
+            SkASSERT((k16[i] & (1 << 15)) == 0);
+            k16[i] = k16[i] | (1 << 15);
+            b->add32(extraSamplerKey);
+        }
     }
     // zero the last 16 bits if the number of uniforms for samplers is odd.
     if (numTextureSamplers & 0x1) {
@@ -70,7 +80,7 @@
 }
 
 static void add_sampler_keys(GrProcessorKeyBuilder* b, const GrPrimitiveProcessor& pp,
-                              const GrShaderCaps& caps) {
+                             const GrShaderCaps& caps) {
     int numTextureSamplers = pp.numTextureSamplers();
     // Need two bytes per key.
     int word32Count = (numTextureSamplers + 1) / 2;
@@ -81,6 +91,15 @@
     for (int i = 0; i < numTextureSamplers; ++i) {
         const GrPrimitiveProcessor::TextureSampler& sampler = pp.textureSampler(i);
         k16[i] = sampler_key(sampler.textureType(), sampler.config(), caps);
+        uint32_t extraSamplerKey = sampler.extraSamplerKey();
+        if (extraSamplerKey) {
+            SkASSERT(sampler.textureType() == GrTextureType::kExternal);
+            // We first mark the normal sampler key with last bit to flag that it has an extra
+            // sampler key. We then add all the extraSamplerKeys to the end of the normal ones.
+            SkASSERT((k16[i] & (1 << 15)) == 0);
+            k16[i] = k16[i] | (1 << 15);
+            b->add32(extraSamplerKey);
+        }
     }
     // zero the last 16 bits if the number of uniforms for samplers is odd.
     if (numTextureSamplers & 0x1) {
@@ -98,6 +117,7 @@
  * function because it is hairy, though FPs do not have attribs, and GPs do not have transforms
  */
 static bool gen_meta_key(const GrFragmentProcessor& fp,
+                         GrGpu* gpu,
                          const GrShaderCaps& shaderCaps,
                          uint32_t transformKey,
                          GrProcessorKeyBuilder* b) {
@@ -110,7 +130,7 @@
         return false;
     }
 
-    add_sampler_keys(b, fp, shaderCaps);
+    add_sampler_keys(b, fp, gpu, shaderCaps);
 
     uint32_t* key = b->add32n(2);
     key[0] = (classID << 16) | SkToU32(processorKeySize);
@@ -157,30 +177,33 @@
 
 static bool gen_frag_proc_and_meta_keys(const GrPrimitiveProcessor& primProc,
                                         const GrFragmentProcessor& fp,
+                                        GrGpu* gpu,
                                         const GrShaderCaps& shaderCaps,
                                         GrProcessorKeyBuilder* b) {
     for (int i = 0; i < fp.numChildProcessors(); ++i) {
-        if (!gen_frag_proc_and_meta_keys(primProc, fp.childProcessor(i), shaderCaps, b)) {
+        if (!gen_frag_proc_and_meta_keys(primProc, fp.childProcessor(i), gpu, shaderCaps, b)) {
             return false;
         }
     }
 
     fp.getGLSLProcessorKey(shaderCaps, b);
 
-    return gen_meta_key(fp, shaderCaps, primProc.getTransformKey(fp.coordTransforms(),
-                                                                 fp.numCoordTransforms()), b);
+    return gen_meta_key(fp, gpu, shaderCaps, primProc.getTransformKey(fp.coordTransforms(),
+                                                                      fp.numCoordTransforms()), b);
 }
 
 bool GrProgramDesc::Build(GrProgramDesc* desc,
                           const GrPrimitiveProcessor& primProc,
                           bool hasPointSize,
                           const GrPipeline& pipeline,
-                          const GrShaderCaps& shaderCaps) {
+                          GrGpu* gpu) {
     // The descriptor is used as a cache key. Thus when a field of the
     // descriptor will not affect program generation (because of the attribute
     // bindings in use or other descriptor field settings) it should be set
     // to a canonical value to avoid duplicate programs with different keys.
 
+    const GrShaderCaps& shaderCaps = *gpu->caps()->shaderCaps();
+
     GR_STATIC_ASSERT(0 == kProcessorKeysOffset % sizeof(uint32_t));
     // Make room for everything up to the effect keys.
     desc->key().reset();
@@ -196,7 +219,7 @@
 
     for (int i = 0; i < pipeline.numFragmentProcessors(); ++i) {
         const GrFragmentProcessor& fp = pipeline.getFragmentProcessor(i);
-        if (!gen_frag_proc_and_meta_keys(primProc, fp, shaderCaps, &b)) {
+        if (!gen_frag_proc_and_meta_keys(primProc, fp, gpu, shaderCaps, &b)) {
             desc->key().reset();
             return false;
         }
diff --git a/src/gpu/GrProgramDesc.h b/src/gpu/GrProgramDesc.h
index f687876..0a634cb 100644
--- a/src/gpu/GrProgramDesc.h
+++ b/src/gpu/GrProgramDesc.h
@@ -36,14 +36,14 @@
     *                        general draw information, as well as the specific color, geometry,
     *                        and coverage stages which will be used to generate the GL Program for
     *                        this optstate.
-    * @param GrShaderCaps   Capabilities of the shading language.
+    * @param GrGpu          Ptr to the GrGpu object the program will be used with.
     * @param GrProgramDesc  The built and finalized descriptor
     **/
     static bool Build(GrProgramDesc*,
                       const GrPrimitiveProcessor&,
                       bool hasPointSize,
                       const GrPipeline&,
-                      const GrShaderCaps&);
+                      GrGpu*);
 
     // Returns this as a uint32_t array to be used as a key in the program cache.
     const uint32_t* asKey() const {
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index aa5d8472d..eb920db 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -309,7 +309,7 @@
         ~ProgramCache();
 
         void abandon();
-        GrGLProgram* refProgram(const GrGLGpu*, const GrPrimitiveProcessor&,
+        GrGLProgram* refProgram(GrGLGpu*, const GrPrimitiveProcessor&,
                                 const GrTextureProxy* const primProcProxies[],
                                 const GrPipeline&, bool hasPointSize);
 
diff --git a/src/gpu/gl/GrGLGpuProgramCache.cpp b/src/gpu/gl/GrGLGpuProgramCache.cpp
index fa738cd..851640a 100644
--- a/src/gpu/gl/GrGLGpuProgramCache.cpp
+++ b/src/gpu/gl/GrGLGpuProgramCache.cpp
@@ -68,7 +68,7 @@
     fMap.reset();
 }
 
-GrGLProgram* GrGLGpu::ProgramCache::refProgram(const GrGLGpu* gpu,
+GrGLProgram* GrGLGpu::ProgramCache::refProgram(GrGLGpu* gpu,
                                                const GrPrimitiveProcessor& primProc,
                                                const GrTextureProxy* const primProcProxies[],
                                                const GrPipeline& pipeline,
@@ -79,7 +79,7 @@
 
     // Get GrGLProgramDesc
     GrProgramDesc desc;
-    if (!GrProgramDesc::Build(&desc, primProc, isPoints, pipeline, *gpu->caps()->shaderCaps())) {
+    if (!GrProgramDesc::Build(&desc, primProc, isPoints, pipeline, gpu)) {
         GrCapsDebugf(gpu->caps(), "Failed to gl program descriptor!\n");
         return nullptr;
     }
diff --git a/src/gpu/mtl/GrMtlGpuCommandBuffer.mm b/src/gpu/mtl/GrMtlGpuCommandBuffer.mm
index 34f02bb..0eb4230 100644
--- a/src/gpu/mtl/GrMtlGpuCommandBuffer.mm
+++ b/src/gpu/mtl/GrMtlGpuCommandBuffer.mm
@@ -119,7 +119,7 @@
         }
     }
     GrProgramDesc desc;
-    if (!GrProgramDesc::Build(&desc, primProc, hasPoints, pipeline, *fGpu->caps()->shaderCaps())) {
+    if (!GrProgramDesc::Build(&desc, primProc, hasPoints, pipeline, fGpu)) {
         return nullptr;
     }
     desc.finalize();
diff --git a/src/gpu/ops/GrLatticeOp.cpp b/src/gpu/ops/GrLatticeOp.cpp
index 19f9d72..0975025 100644
--- a/src/gpu/ops/GrLatticeOp.cpp
+++ b/src/gpu/ops/GrLatticeOp.cpp
@@ -8,9 +8,11 @@
 #include "GrLatticeOp.h"
 #include "GrDefaultGeoProcFactory.h"
 #include "GrDrawOpTest.h"
+#include "GrGpu.h"
 #include "GrMeshDrawOp.h"
 #include "GrOpFlushState.h"
 #include "GrResourceProvider.h"
+#include "GrResourceProviderPriv.h"
 #include "GrSimpleMeshDrawOpHelper.h"
 #include "GrVertexWriter.h"
 #include "SkBitmap.h"
@@ -25,10 +27,11 @@
 
 class LatticeGP : public GrGeometryProcessor {
 public:
-    static sk_sp<GrGeometryProcessor> Make(const GrTextureProxy* proxy,
+    static sk_sp<GrGeometryProcessor> Make(GrGpu* gpu,
+                                           const GrTextureProxy* proxy,
                                            sk_sp<GrColorSpaceXform> csxf,
                                            GrSamplerState::Filter filter) {
-        return sk_sp<GrGeometryProcessor>(new LatticeGP(proxy, std::move(csxf), filter));
+        return sk_sp<GrGeometryProcessor>(new LatticeGP(gpu, proxy, std::move(csxf), filter));
     }
 
     const char* name() const override { return "LatticeGP"; }
@@ -86,10 +89,17 @@
     }
 
 private:
-    LatticeGP(const GrTextureProxy* proxy, sk_sp<GrColorSpaceXform> csxf,
+    LatticeGP(GrGpu* gpu, const GrTextureProxy* proxy, sk_sp<GrColorSpaceXform> csxf,
               GrSamplerState::Filter filter)
             : INHERITED(kLatticeGP_ClassID), fColorSpaceXform(std::move(csxf)) {
-        fSampler.reset(proxy->textureType(), proxy->config(), filter);
+
+        GrSamplerState samplerState = GrSamplerState(GrSamplerState::WrapMode::kClamp,
+                                                     filter);
+        uint32_t extraSamplerKey = gpu->getExtraSamplerKeyForProgram(samplerState,
+                                                                     proxy->backendFormat());
+
+        fSampler.reset(proxy->textureType(), proxy->config(), samplerState,
+                       extraSamplerKey);
         this->setTextureSamplerCnt(1);
         fInPosition = {"position", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
         fInTextureCoords = {"textureCoords", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
@@ -193,7 +203,8 @@
 
 private:
     void onPrepareDraws(Target* target) override {
-        auto gp = LatticeGP::Make(fProxy.get(), fColorSpaceXform, fFilter);
+        GrGpu* gpu = target->resourceProvider()->priv().gpu();
+        auto gp = LatticeGP::Make(gpu, fProxy.get(), fColorSpaceXform, fFilter);
         if (!gp) {
             SkDebugf("Couldn't create GrGeometryProcessor\n");
             return;
diff --git a/src/gpu/ops/GrQuadPerEdgeAA.cpp b/src/gpu/ops/GrQuadPerEdgeAA.cpp
index 2849e29..f893a91 100644
--- a/src/gpu/ops/GrQuadPerEdgeAA.cpp
+++ b/src/gpu/ops/GrQuadPerEdgeAA.cpp
@@ -472,10 +472,11 @@
 
     static sk_sp<GrGeometryProcessor> Make(const VertexSpec& vertexSpec, const GrShaderCaps& caps,
                                            GrTextureType textureType, GrPixelConfig textureConfig,
-                                           const GrSamplerState::Filter filter,
+                                           const GrSamplerState& samplerState,
+                                           uint32_t extraSamplerKey,
                                            sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
         return sk_sp<QuadPerEdgeAAGeometryProcessor>(new QuadPerEdgeAAGeometryProcessor(
-                vertexSpec, caps, textureType, textureConfig, filter,
+                vertexSpec, caps, textureType, textureConfig, samplerState, extraSamplerKey,
                 std::move(textureColorSpaceXform)));
     }
 
@@ -634,11 +635,12 @@
 
     QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec, const GrShaderCaps& caps,
                                    GrTextureType textureType, GrPixelConfig textureConfig,
-                                   GrSamplerState::Filter filter,
+                                   const GrSamplerState& samplerState,
+                                   uint32_t extraSamplerKey,
                                    sk_sp<GrColorSpaceXform> textureColorSpaceXform)
             : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
             , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
-            , fSampler(textureType, textureConfig, filter) {
+            , fSampler(textureType, textureConfig, samplerState, extraSamplerKey) {
         SkASSERT(spec.hasVertexColors() && spec.hasLocalCoords());
         this->initializeAttrs(spec);
         this->setTextureSamplerCnt(1);
@@ -696,8 +698,10 @@
 
 sk_sp<GrGeometryProcessor> MakeTexturedProcessor(const VertexSpec& spec, const GrShaderCaps& caps,
         GrTextureType textureType, GrPixelConfig textureConfig,
-        const GrSamplerState::Filter filter, sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
-    return QuadPerEdgeAAGeometryProcessor::Make(spec, caps, textureType, textureConfig, filter,
+        const GrSamplerState& samplerState, uint32_t extraSamplerKey,
+        sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
+    return QuadPerEdgeAAGeometryProcessor::Make(spec, caps, textureType, textureConfig,
+                                                samplerState, extraSamplerKey,
                                                 std::move(textureColorSpaceXform));
 }
 
diff --git a/src/gpu/ops/GrQuadPerEdgeAA.h b/src/gpu/ops/GrQuadPerEdgeAA.h
index 9d1b650..68e798c 100644
--- a/src/gpu/ops/GrQuadPerEdgeAA.h
+++ b/src/gpu/ops/GrQuadPerEdgeAA.h
@@ -69,7 +69,8 @@
 
     sk_sp<GrGeometryProcessor> MakeTexturedProcessor(const VertexSpec& spec,
             const GrShaderCaps& caps, GrTextureType textureType, GrPixelConfig textureConfig,
-            const GrSamplerState::Filter filter, sk_sp<GrColorSpaceXform> textureColorSpaceXform);
+            const GrSamplerState& samplerState, uint32_t extraSamplerKey,
+            sk_sp<GrColorSpaceXform> textureColorSpaceXform);
 
     // Fill vertices with the vertex data needed to represent the given quad. The device position,
     // local coords, vertex color, domain, and edge coefficients will be written and/or computed
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index 6fe8c15..255f937 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -13,12 +13,14 @@
 #include "GrContextPriv.h"
 #include "GrDrawOpTest.h"
 #include "GrGeometryProcessor.h"
+#include "GrGpu.h"
 #include "GrMemoryPool.h"
 #include "GrMeshDrawOp.h"
 #include "GrOpFlushState.h"
 #include "GrQuad.h"
 #include "GrQuadPerEdgeAA.h"
 #include "GrResourceProvider.h"
+#include "GrResourceProviderPriv.h"
 #include "GrShaderCaps.h"
 #include "GrTexture.h"
 #include "GrTexturePriv.h"
@@ -352,9 +354,17 @@
         VertexSpec vertexSpec(quadType, wideColor ? ColorType::kHalf : ColorType::kByte,
                               GrQuadType::kRect, /* hasLocal */ true, domain, aaType);
 
+        GrSamplerState samplerState = GrSamplerState(GrSamplerState::WrapMode::kClamp,
+                                                     this->filter());
+        GrGpu* gpu = target->resourceProvider()->priv().gpu();
+        uint32_t extraSamplerKey = gpu->getExtraSamplerKeyForProgram(
+                samplerState, fProxies[0].fProxy->backendFormat());
+
         sk_sp<GrGeometryProcessor> gp = GrQuadPerEdgeAA::MakeTexturedProcessor(
                 vertexSpec, *target->caps().shaderCaps(),
-                textureType, config, this->filter(), std::move(fTextureColorSpaceXform));
+                textureType, config, samplerState, extraSamplerKey,
+                std::move(fTextureColorSpaceXform));
+
         GrPipeline::InitArgs args;
         args.fProxy = target->proxy();
         args.fCaps = &target->caps();
diff --git a/src/gpu/vk/GrVkDescriptorSetManager.cpp b/src/gpu/vk/GrVkDescriptorSetManager.cpp
index b032584..ba0a060 100644
--- a/src/gpu/vk/GrVkDescriptorSetManager.cpp
+++ b/src/gpu/vk/GrVkDescriptorSetManager.cpp
@@ -23,31 +23,56 @@
     }
     visibilities.push_back(geomStages);
     visibilities.push_back(kFragment_GrShaderFlag);
-    return new GrVkDescriptorSetManager(gpu, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, visibilities);
+
+    SkTArray<const GrVkSampler*> samplers;
+    return new GrVkDescriptorSetManager(gpu, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, visibilities,
+                                        samplers);
 }
 
 GrVkDescriptorSetManager* GrVkDescriptorSetManager::CreateSamplerManager(
         GrVkGpu* gpu, VkDescriptorType type, const GrVkUniformHandler& uniformHandler) {
     SkSTArray<4, uint32_t> visibilities;
+    SkSTArray<4, const GrVkSampler*> immutableSamplers;
     SkASSERT(type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
     for (int i = 0 ; i < uniformHandler.numSamplers(); ++i) {
         visibilities.push_back(uniformHandler.samplerVisibility(i));
+        immutableSamplers.push_back(uniformHandler.immutableSampler(i));
     }
-    return CreateSamplerManager(gpu, type, visibilities);
+    return new GrVkDescriptorSetManager(gpu, type, visibilities, immutableSamplers);
 }
 
 GrVkDescriptorSetManager* GrVkDescriptorSetManager::CreateSamplerManager(
         GrVkGpu* gpu, VkDescriptorType type, const SkTArray<uint32_t>& visibilities) {
-    return new GrVkDescriptorSetManager(gpu, type, visibilities);
+    SkSTArray<4, const GrVkSampler*> immutableSamplers;
+    SkASSERT(type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
+    for (int i = 0 ; i < visibilities.count(); ++i) {
+        immutableSamplers.push_back(nullptr);
+    }
+    return new GrVkDescriptorSetManager(gpu, type, visibilities, immutableSamplers);
 }
 
-GrVkDescriptorSetManager::GrVkDescriptorSetManager(GrVkGpu* gpu,
-                                                   VkDescriptorType type,
-                                                   const SkTArray<uint32_t>& visibilities)
-    : fPoolManager(type, gpu, visibilities) {
+GrVkDescriptorSetManager::GrVkDescriptorSetManager(
+        GrVkGpu* gpu, VkDescriptorType type,
+        const SkTArray<uint32_t>& visibilities,
+        const SkTArray<const GrVkSampler*>& immutableSamplers)
+    : fPoolManager(type, gpu, visibilities, immutableSamplers) {
+#ifdef SK_DEBUG
+    if (type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) {
+        SkASSERT(visibilities.count() == immutableSamplers.count());
+    } else {
+        SkASSERT(immutableSamplers.count() == 0);
+    }
+#endif
     for (int i = 0; i < visibilities.count(); ++i) {
         fBindingVisibilities.push_back(visibilities[i]);
     }
+    for (int i = 0; i < immutableSamplers.count(); ++i) {
+        const GrVkSampler* sampler = immutableSamplers[i];
+        if (sampler) {
+            sampler->ref();
+        }
+        fImmutableSamplers.push_back(sampler);
+    }
 }
 
 const GrVkDescriptorSet* GrVkDescriptorSetManager::getDescriptorSet(GrVkGpu* gpu,
@@ -79,6 +104,13 @@
         fFreeSets[i]->unref(gpu);
     }
     fFreeSets.reset();
+
+    for (int i = 0; i < fImmutableSamplers.count(); ++i) {
+        if (fImmutableSamplers[i]) {
+            fImmutableSamplers[i]->unref(gpu);
+        }
+    }
+    fImmutableSamplers.reset();
 }
 
 void GrVkDescriptorSetManager::abandon() {
@@ -88,6 +120,13 @@
         fFreeSets[i]->unrefAndAbandon();
     }
     fFreeSets.reset();
+
+    for (int i = 0; i < fImmutableSamplers.count(); ++i) {
+        if (fImmutableSamplers[i]) {
+            fImmutableSamplers[i]->unrefAndAbandon();
+        }
+    }
+    fImmutableSamplers.reset();
 }
 
 bool GrVkDescriptorSetManager::isCompatible(VkDescriptorType type,
@@ -102,7 +141,8 @@
         return false;
     }
     for (int i = 0; i < uniHandler->numSamplers(); ++i) {
-        if (uniHandler->samplerVisibility(i) != fBindingVisibilities[i]) {
+        if (uniHandler->samplerVisibility(i) != fBindingVisibilities[i] ||
+            uniHandler->immutableSampler(i) != fImmutableSamplers[i]) {
             return false;
         }
     }
@@ -121,7 +161,7 @@
             return false;
         }
         for (int i = 0; i < visibilities.count(); ++i) {
-            if (visibilities[i] != fBindingVisibilities[i]) {
+            if (visibilities[i] != fBindingVisibilities[i] || fImmutableSamplers[i] != nullptr) {
                 return false;
             }
         }
@@ -149,7 +189,8 @@
 GrVkDescriptorSetManager::DescriptorPoolManager::DescriptorPoolManager(
         VkDescriptorType type,
         GrVkGpu* gpu,
-        const SkTArray<uint32_t>& visibilities)
+        const SkTArray<uint32_t>& visibilities,
+        const SkTArray<const GrVkSampler*>& immutableSamplers)
     : fDescType(type)
     , fCurrentDescriptorCount(0)
     , fPool(nullptr) {
@@ -166,7 +207,13 @@
             dsSamplerBindings[i].descriptorType = type;
             dsSamplerBindings[i].descriptorCount = 1;
             dsSamplerBindings[i].stageFlags = visibility_to_vk_stage_flags(visibility);
-            dsSamplerBindings[i].pImmutableSamplers = nullptr;
+            if (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER == type) {
+                if (immutableSamplers[i]) {
+                    dsSamplerBindings[i].pImmutableSamplers = immutableSamplers[i]->samplerPtr();
+                } else {
+                    dsSamplerBindings[i].pImmutableSamplers = nullptr;
+                }
+            }
         }
 
         VkDescriptorSetLayoutCreateInfo dsSamplerLayoutCreateInfo;
diff --git a/src/gpu/vk/GrVkDescriptorSetManager.h b/src/gpu/vk/GrVkDescriptorSetManager.h
index 9afb272..f9ee7b2 100644
--- a/src/gpu/vk/GrVkDescriptorSetManager.h
+++ b/src/gpu/vk/GrVkDescriptorSetManager.h
@@ -12,6 +12,7 @@
 
 #include "GrResourceHandle.h"
 #include "GrVkDescriptorPool.h"
+#include "GrVkSampler.h"
 #include "SkRefCnt.h"
 #include "SkTArray.h"
 
@@ -51,7 +52,8 @@
 private:
     struct DescriptorPoolManager {
         DescriptorPoolManager(VkDescriptorType type, GrVkGpu* gpu,
-                              const SkTArray<uint32_t>& visibilities);
+                              const SkTArray<uint32_t>& visibilities,
+                              const SkTArray<const GrVkSampler*>& immutableSamplers);
 
 
         ~DescriptorPoolManager() {
@@ -83,12 +85,14 @@
 
     GrVkDescriptorSetManager(GrVkGpu* gpu,
                              VkDescriptorType,
-                             const SkTArray<uint32_t>& visibilities);
+                             const SkTArray<uint32_t>& visibilities,
+                             const SkTArray<const GrVkSampler*>& immutableSamplers);
 
 
     DescriptorPoolManager                    fPoolManager;
     SkTArray<const GrVkDescriptorSet*, true> fFreeSets;
     SkSTArray<4, uint32_t>                   fBindingVisibilities;
+    SkSTArray<4, const GrVkSampler*>         fImmutableSamplers;
 };
 
 #endif
diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp
index 87af8dc..ca48271 100644
--- a/src/gpu/vk/GrVkGpu.cpp
+++ b/src/gpu/vk/GrVkGpu.cpp
@@ -2165,3 +2165,17 @@
     fDrawables.emplace_back(std::move(drawable));
 }
 
+uint32_t GrVkGpu::getExtraSamplerKeyForProgram(const GrSamplerState& samplerState,
+                                               const GrBackendFormat& format) {
+    const GrVkYcbcrConversionInfo* ycbcrInfo = format.getVkYcbcrConversionInfo();
+    SkASSERT(ycbcrInfo);
+    if (!ycbcrInfo->isValid()) {
+        return 0;
+    }
+
+    const GrVkSampler* sampler = this->resourceProvider().findOrCreateCompatibleSampler(
+            samplerState, *ycbcrInfo);
+
+    return sampler->uniqueID();
+}
+
diff --git a/src/gpu/vk/GrVkGpu.h b/src/gpu/vk/GrVkGpu.h
index 298c4c5..c195d3b 100644
--- a/src/gpu/vk/GrVkGpu.h
+++ b/src/gpu/vk/GrVkGpu.h
@@ -156,6 +156,9 @@
                     VkDeviceSize dstOffset, VkDeviceSize size);
     bool updateBuffer(GrVkBuffer* buffer, const void* src, VkDeviceSize offset, VkDeviceSize size);
 
+    uint32_t getExtraSamplerKeyForProgram(const GrSamplerState&,
+                                          const GrBackendFormat& format) override;
+
 private:
     GrVkGpu(GrContext*, const GrContextOptions&, const GrVkBackendContext&,
             sk_sp<const GrVkInterface>);
diff --git a/src/gpu/vk/GrVkPipelineState.cpp b/src/gpu/vk/GrVkPipelineState.cpp
index 53a5a3a..9f18b1a 100644
--- a/src/gpu/vk/GrVkPipelineState.cpp
+++ b/src/gpu/vk/GrVkPipelineState.cpp
@@ -37,7 +37,7 @@
         const UniformInfoArray& uniforms,
         uint32_t geometryUniformSize,
         uint32_t fragmentUniformSize,
-        uint32_t numSamplers,
+        const UniformInfoArray& samplers,
         std::unique_ptr<GrGLSLPrimitiveProcessor> geometryProcessor,
         std::unique_ptr<GrGLSLXferProcessor> xferProcessor,
         std::unique_ptr<std::unique_ptr<GrGLSLFragmentProcessor>[]> fragmentProcessors,
@@ -60,7 +60,13 @@
     fGeometryUniformBuffer.reset(GrVkUniformBuffer::Create(gpu, geometryUniformSize));
     fFragmentUniformBuffer.reset(GrVkUniformBuffer::Create(gpu, fragmentUniformSize));
 
-    fNumSamplers = numSamplers;
+    fNumSamplers = samplers.count();
+
+    for (int i = 0; i < fNumSamplers; ++i) {
+        // We store the immutable samplers here and take ownership of the ref from the
+        // GrVkUnformHandler.
+        fImmutableSamplers.push_back(samplers[i].fImmutableSampler);
+    }
 }
 
 GrVkPipelineState::~GrVkPipelineState() {
@@ -243,8 +249,14 @@
             GrVkTexture* texture = samplerBindings[i].fTexture;
 
             const GrVkImageView* textureView = texture->textureView();
-            GrVkSampler* sampler = gpu->resourceProvider().findOrCreateCompatibleSampler(
+            const GrVkSampler* sampler = nullptr;
+            if (fImmutableSamplers[i]) {
+                sampler = fImmutableSamplers[i];
+            } else {
+                sampler = gpu->resourceProvider().findOrCreateCompatibleSampler(
                     state, texture->ycbcrConversionInfo());
+            }
+            SkASSERT(sampler);
 
             VkDescriptorImageInfo imageInfo;
             memset(&imageInfo, 0, sizeof(VkDescriptorImageInfo));
@@ -268,7 +280,9 @@
             GR_VK_CALL(gpu->vkInterface(),
                        UpdateDescriptorSets(gpu->device(), 1, &writeInfo, 0, nullptr));
             commandBuffer->addResource(sampler);
-            sampler->unref(gpu);
+            if (!fImmutableSamplers[i]) {
+                sampler->unref(gpu);
+            }
             commandBuffer->addResource(samplerBindings[i].fTexture->textureView());
             commandBuffer->addResource(samplerBindings[i].fTexture->resource());
         }
diff --git a/src/gpu/vk/GrVkPipelineState.h b/src/gpu/vk/GrVkPipelineState.h
index 232b4f3..e8a1b39 100644
--- a/src/gpu/vk/GrVkPipelineState.h
+++ b/src/gpu/vk/GrVkPipelineState.h
@@ -49,7 +49,7 @@
             const UniformInfoArray& uniforms,
             uint32_t geometryUniformSize,
             uint32_t fragmentUniformSize,
-            uint32_t numSamplers,
+            const UniformInfoArray& samplers,
             std::unique_ptr<GrGLSLPrimitiveProcessor> geometryProcessor,
             std::unique_ptr<GrGLSLXferProcessor> xferProcessor,
             std::unique_ptr<std::unique_ptr<GrGLSLFragmentProcessor>[]> fragmentProcessors,
@@ -136,6 +136,8 @@
 
     const GrVkDescriptorSetManager::Handle fSamplerDSHandle;
 
+    SkSTArray<4, const GrVkSampler*>   fImmutableSamplers;
+
     std::unique_ptr<GrVkUniformBuffer> fGeometryUniformBuffer;
     std::unique_ptr<GrVkUniformBuffer> fFragmentUniformBuffer;
 
diff --git a/src/gpu/vk/GrVkPipelineStateBuilder.cpp b/src/gpu/vk/GrVkPipelineStateBuilder.cpp
index 486bd73..e2ccbda 100644
--- a/src/gpu/vk/GrVkPipelineStateBuilder.cpp
+++ b/src/gpu/vk/GrVkPipelineStateBuilder.cpp
@@ -197,7 +197,7 @@
                                  fUniformHandler.fUniforms,
                                  fUniformHandler.fCurrentGeometryUBOOffset,
                                  fUniformHandler.fCurrentFragmentUBOOffset,
-                                 (uint32_t)fUniformHandler.numSamplers(),
+                                 fUniformHandler.fSamplers,
                                  std::move(fGeometryProcessor),
                                  std::move(fXferProcessor),
                                  std::move(fFragmentProcessors),
@@ -228,9 +228,9 @@
                                            const GrPipeline& pipeline,
                                            const GrStencilSettings& stencil,
                                            GrPrimitiveType primitiveType,
-                                           const GrShaderCaps& caps) {
+                                           GrVkGpu* gpu) {
     if (!INHERITED::Build(desc, primProc, primitiveType == GrPrimitiveType::kPoints, pipeline,
-                          caps)) {
+                          gpu)) {
         return false;
     }
 
diff --git a/src/gpu/vk/GrVkPipelineStateBuilder.h b/src/gpu/vk/GrVkPipelineStateBuilder.h
index 8f7b00b..b70539e 100644
--- a/src/gpu/vk/GrVkPipelineStateBuilder.h
+++ b/src/gpu/vk/GrVkPipelineStateBuilder.h
@@ -43,7 +43,7 @@
                           const GrPipeline&,
                           const GrStencilSettings&,
                           GrPrimitiveType primitiveType,
-                          const GrShaderCaps&);
+                          GrVkGpu* gpu);
 
     private:
         typedef GrProgramDesc INHERITED;
diff --git a/src/gpu/vk/GrVkPipelineStateCache.cpp b/src/gpu/vk/GrVkPipelineStateCache.cpp
index bb07827..7b0f9f8 100644
--- a/src/gpu/vk/GrVkPipelineStateCache.cpp
+++ b/src/gpu/vk/GrVkPipelineStateCache.cpp
@@ -96,7 +96,7 @@
     // Get GrVkProgramDesc
     GrVkPipelineStateBuilder::Desc desc;
     if (!GrVkPipelineStateBuilder::Desc::Build(&desc, primProc, pipeline, stencil, primitiveType,
-                                               *fGpu->caps()->shaderCaps())) {
+                                               fGpu)) {
         GrCapsDebugf(fGpu->caps(), "Failed to build vk program descriptor!\n");
         return nullptr;
     }
diff --git a/src/gpu/vk/GrVkSampler.h b/src/gpu/vk/GrVkSampler.h
index 713bdbc..9d74d8f 100644
--- a/src/gpu/vk/GrVkSampler.h
+++ b/src/gpu/vk/GrVkSampler.h
@@ -12,6 +12,7 @@
 
 #include "GrVkResource.h"
 #include "GrVkSamplerYcbcrConversion.h"
+#include "SkAtomics.h"
 #include "SkOpts.h"
 #include "vk/GrVkTypes.h"
 
@@ -23,6 +24,7 @@
     static GrVkSampler* Create(GrVkGpu* gpu, const GrSamplerState&, const GrVkYcbcrConversionInfo&);
 
     VkSampler sampler() const { return fSampler; }
+    const VkSampler* samplerPtr() const { return &fSampler; }
 
     struct Key {
         Key(uint16_t samplerKey, const GrVkSamplerYcbcrConversion::Key& ycbcrKey) {
@@ -49,6 +51,8 @@
         return SkOpts::hash(reinterpret_cast<const uint32_t*>(&key), sizeof(Key));
     }
 
+    uint32_t uniqueID() const { return fUniqueID; }
+
 #ifdef SK_TRACE_VK_RESOURCES
     void dumpInfo() const override {
         SkDebugf("GrVkSampler: %d (%d refs)\n", fSampler, this->getRefCnt());
@@ -57,14 +61,28 @@
 
 private:
     GrVkSampler(VkSampler sampler, GrVkSamplerYcbcrConversion* ycbcrConversion, Key key)
-            : INHERITED(), fSampler(sampler), fYcbcrConversion(ycbcrConversion), fKey(key) {}
+            : INHERITED()
+            , fSampler(sampler)
+            , fYcbcrConversion(ycbcrConversion)
+            , fKey(key)
+            , fUniqueID(GenID()) {}
 
     void freeGPUData(const GrVkGpu* gpu) const override;
     void abandonGPUData() const override;
 
+    static uint32_t GenID() {
+        static int32_t gUniqueID = SK_InvalidUniqueID;
+        uint32_t id;
+        do {
+            id = static_cast<uint32_t>(sk_atomic_inc(&gUniqueID) + 1);
+        } while (id == SK_InvalidUniqueID);
+        return id;
+    }
+
     VkSampler                   fSampler;
     GrVkSamplerYcbcrConversion* fYcbcrConversion;
     Key                         fKey;
+    uint32_t                    fUniqueID;
 
     typedef GrVkResource INHERITED;
 };