Enable YCbCr sampler support on platforms other than Android

Previously YCbCr Vulkan samplers were supported only on Android for
external images, while Vulkan requires YCbCr sampler for I420 YUV image
formats such as VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM,
VK_FORMAT_G8_B8R8_2PLANE_420_UNORM.
This CL:
 - Adds VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM and
   VK_FORMAT_G8_B8R8_2PLANE_420_UNORM as supported Vulkan formats
 - Updates GrVkYcbcrConversionInfo to add fFormat field and allow
   fExternalFormat=0.
 - Removes assertions format=VK_FORMAT_UNDEFINED for all images that
   have ycbcr info.

Bug: chromium:981022
Change-Id: I3989f72e918a257a081b81ae20429a6771229ad6
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/230918
Commit-Queue: Sergey Ulanov <sergeyu@chromium.org>
Commit-Queue: Greg Daniel <egdaniel@google.com>
Auto-Submit: Sergey Ulanov <sergeyu@chromium.org>
Reviewed-by: Greg Daniel <egdaniel@google.com>
diff --git a/src/gpu/GrAHardwareBufferUtils.cpp b/src/gpu/GrAHardwareBufferUtils.cpp
index 47899e8..77a5ed0 100644
--- a/src/gpu/GrAHardwareBufferUtils.cpp
+++ b/src/gpu/GrAHardwareBufferUtils.cpp
@@ -138,7 +138,7 @@
                     ycbcrConversion.fYChromaOffset = hwbFormatProps.suggestedYChromaOffset;
                     ycbcrConversion.fForceExplicitReconstruction = VK_FALSE;
                     ycbcrConversion.fExternalFormat = hwbFormatProps.externalFormat;
-                    ycbcrConversion.fExternalFormatFeatures = hwbFormatProps.formatFeatures;
+                    ycbcrConversion.fFormatFeatures = hwbFormatProps.formatFeatures;
                     if (VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT &
                         hwbFormatProps.formatFeatures) {
                         ycbcrConversion.fChromaFilter = VK_FILTER_LINEAR;
diff --git a/src/gpu/GrBackendSurface.cpp b/src/gpu/GrBackendSurface.cpp
index 412d8da..fa95520 100644
--- a/src/gpu/GrBackendSurface.cpp
+++ b/src/gpu/GrBackendSurface.cpp
@@ -92,11 +92,8 @@
 }
 
 GrBackendFormat GrBackendFormat::MakeVk(const GrVkYcbcrConversionInfo& ycbcrInfo) {
-#ifdef SK_BUILD_FOR_ANDROID
-    return GrBackendFormat(VK_FORMAT_UNDEFINED, ycbcrInfo);
-#else
-    return GrBackendFormat();
-#endif
+    SkASSERT(ycbcrInfo.isValid());
+    return GrBackendFormat(ycbcrInfo.fFormat, ycbcrInfo);
 }
 
 GrBackendFormat::GrBackendFormat(VkFormat vkFormat, const GrVkYcbcrConversionInfo& ycbcrInfo)
@@ -109,7 +106,7 @@
         , fTextureType(GrTextureType::k2D) {
     fVk.fFormat = vkFormat;
     fVk.fYcbcrConversionInfo = ycbcrInfo;
-    if (fVk.fYcbcrConversionInfo.isValid()) {
+    if (fVk.fYcbcrConversionInfo.isValid() && fVk.fYcbcrConversionInfo.fExternalFormat) {
         fTextureType = GrTextureType::kExternal;
     }
 }
@@ -564,7 +561,7 @@
         case GrBackendApi::kVulkan: {
             auto info = fVkInfo.snapImageInfo();
             if (info.fYcbcrConversionInfo.isValid()) {
-                SkASSERT(info.fFormat == VK_FORMAT_UNDEFINED);
+                SkASSERT(info.fFormat == info.fYcbcrConversionInfo.fFormat);
                 return GrBackendFormat::MakeVk(info.fYcbcrConversionInfo);
             }
             return GrBackendFormat::MakeVk(info.fFormat);
@@ -848,7 +845,7 @@
         case GrBackendApi::kVulkan: {
             auto info = fVkInfo.snapImageInfo();
             if (info.fYcbcrConversionInfo.isValid()) {
-                SkASSERT(info.fFormat == VK_FORMAT_UNDEFINED);
+                SkASSERT(info.fFormat == info.fYcbcrConversionInfo.fFormat);
                 return GrBackendFormat::MakeVk(info.fYcbcrConversionInfo);
             }
             return GrBackendFormat::MakeVk(info.fFormat);
diff --git a/src/gpu/GrPrimitiveProcessor.cpp b/src/gpu/GrPrimitiveProcessor.cpp
index 32790b9..becab18 100644
--- a/src/gpu/GrPrimitiveProcessor.cpp
+++ b/src/gpu/GrPrimitiveProcessor.cpp
@@ -81,7 +81,6 @@
     fTextureType = textureType;
     fConfig = config;
     fExtraSamplerKey = extraSamplerKey;
-    SkASSERT(!fExtraSamplerKey || textureType == GrTextureType::kExternal);
 }
 
 void GrPrimitiveProcessor::TextureSampler::reset(GrTextureType textureType,
diff --git a/src/gpu/GrProgramDesc.cpp b/src/gpu/GrProgramDesc.cpp
index b826125..d20d6e1 100644
--- a/src/gpu/GrProgramDesc.cpp
+++ b/src/gpu/GrProgramDesc.cpp
@@ -71,7 +71,6 @@
         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 both keys.
             SkASSERT((samplerKey & (1 << 31)) == 0);
@@ -95,7 +94,6 @@
                 sampler.textureType(), sampler.swizzle(), 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 both keys.
             SkASSERT((samplerKey & (1 << 31)) == 0);
diff --git a/src/gpu/vk/GrVkCaps.cpp b/src/gpu/vk/GrVkCaps.cpp
index e6118b0..3b6184f 100644
--- a/src/gpu/vk/GrVkCaps.cpp
+++ b/src/gpu/vk/GrVkCaps.cpp
@@ -335,18 +335,15 @@
 
     auto ycbcrFeatures =
             get_extension_feature_struct<VkPhysicalDeviceSamplerYcbcrConversionFeatures>(
-                    features,
-                    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES);
+                    features, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES);
     if (ycbcrFeatures && ycbcrFeatures->samplerYcbcrConversion &&
-        fSupportsAndroidHWBExternalMemory &&
         (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) ||
          (extensions.hasExtension(VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME, 1) &&
-          this->supportsMaintenance1() &&
-          this->supportsBindMemory2() &&
-          this->supportsMemoryRequirements2() &&
-          this->supportsPhysicalDeviceProperties2()))) {
+          this->supportsMaintenance1() && this->supportsBindMemory2() &&
+          this->supportsMemoryRequirements2() && this->supportsPhysicalDeviceProperties2()))) {
         fSupportsYcbcrConversion = true;
     }
+
     // We always push back the default GrVkYcbcrConversionInfo so that the case of no conversion
     // will return a key of 0.
     fYcbcrInfos.push_back(GrVkYcbcrConversionInfo());
@@ -650,6 +647,8 @@
     VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK,
     VK_FORMAT_R16_UNORM,
     VK_FORMAT_R16G16_UNORM,
+    VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM,
+    VK_FORMAT_G8_B8R8_2PLANE_420_UNORM,
     // Experimental (for Y416 and mutant P016/P010)
     VK_FORMAT_R16G16B16A16_UNORM,
     VK_FORMAT_R16G16_SFLOAT,
@@ -967,6 +966,36 @@
             }
         }
     }
+    // Format: VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM
+    {
+        auto& info = this->getFormatInfo(VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM);
+        if (SkToBool(info.fOptimalFlags & FormatInfo::kTextureable_Flag)) {
+            info.fColorTypeInfoCount = 1;
+            info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]());
+            int ctIdx = 0;
+            // Format: VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM, Surface: kRGB_888x
+            {
+                auto& ctInfo = info.fColorTypeInfos[ctIdx++];
+                ctInfo.fColorType = GrColorType::kRGB_888x;
+                ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag;
+            }
+        }
+    }
+    // Format: VK_FORMAT_G8_B8R8_2PLANE_420_UNORM
+    {
+        auto& info = this->getFormatInfo(VK_FORMAT_G8_B8R8_2PLANE_420_UNORM);
+        if (SkToBool(info.fOptimalFlags & FormatInfo::kTextureable_Flag)) {
+            info.fColorTypeInfoCount = 1;
+            info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]());
+            int ctIdx = 0;
+            // Format: VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, Surface: kRGB_888x
+            {
+                auto& ctInfo = info.fColorTypeInfos[ctIdx++];
+                ctInfo.fColorType = GrColorType::kRGB_888x;
+                ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag;
+            }
+        }
+    }
     // Format: VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK
     {
         // No supported GrColorTypes.
@@ -1203,6 +1232,11 @@
         return {GrColorType::kUnknown, 0};
     }
 
+
+    if (GrVkFormatNeedsYcbcrSampler(vkFormat)) {
+        return {GrColorType::kUnknown, 0};
+    }
+
     // The VkBufferImageCopy bufferOffset field must be both a multiple of 4 and of a single texel.
     size_t offsetAlignment = align_to_4(GrVkBytesPerFormat(vkFormat));
 
@@ -1250,21 +1284,23 @@
 }
 
 static GrPixelConfig validate_image_info(VkFormat format, GrColorType ct, bool hasYcbcrConversion) {
-    if (format == VK_FORMAT_UNDEFINED) {
-        // If the format is undefined then it is only valid as an external image which requires that
-        // we have a valid VkYcbcrConversion.
-        if (hasYcbcrConversion) {
+    if (hasYcbcrConversion) {
+        if (GrVkFormatNeedsYcbcrSampler(format)) {
+            return kRGB_888X_GrPixelConfig;
+        }
+
+        // Format may be undefined for external images, which are required to have YCbCr conversion.
+        if (VK_FORMAT_UNDEFINED == format) {
             // We don't actually care what the color type or config are since we won't use those
             // values for external textures. However, for read pixels we will draw to a non ycbcr
             // texture of this config so we set RGBA here for that.
             return kRGBA_8888_GrPixelConfig;
-        } else {
-            return kUnknown_GrPixelConfig;
         }
+
+        return kUnknown_GrPixelConfig;
     }
 
-    if (hasYcbcrConversion) {
-        // We only support having a ycbcr conversion for external images.
+    if (VK_FORMAT_UNDEFINED == format) {
         return kUnknown_GrPixelConfig;
     }
 
@@ -1485,6 +1521,10 @@
         return {GrColorType::kUnknown, 0};
     }
 
+    if (GrVkFormatNeedsYcbcrSampler(vkFormat)) {
+        return {GrColorType::kUnknown, 0};
+    }
+
     // The VkBufferImageCopy bufferOffset field must be both a multiple of 4 and of a single texel.
     size_t offsetAlignment = align_to_4(GrVkBytesPerFormat(vkFormat));
 
diff --git a/src/gpu/vk/GrVkCaps.h b/src/gpu/vk/GrVkCaps.h
index 938eff5..2cc1083 100644
--- a/src/gpu/vk/GrVkCaps.h
+++ b/src/gpu/vk/GrVkCaps.h
@@ -253,7 +253,7 @@
         std::unique_ptr<ColorTypeInfo[]> fColorTypeInfos;
         int fColorTypeInfoCount = 0;
     };
-    static const size_t kNumVkFormats = 18;
+    static const size_t kNumVkFormats = 20;
     FormatInfo fFormatTable[kNumVkFormats];
 
     FormatInfo& getFormatInfo(VkFormat);
diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp
index 04e39ca..db47cd69 100644
--- a/src/gpu/vk/GrVkGpu.cpp
+++ b/src/gpu/vk/GrVkGpu.cpp
@@ -1137,7 +1137,7 @@
     }
 
     if (info.fYcbcrConversionInfo.isValid()) {
-        if (!caps.supportsYcbcrConversion() || info.fFormat != VK_NULL_HANDLE) {
+        if (!caps.supportsYcbcrConversion()) {
             return false;
         }
     }
@@ -1875,6 +1875,11 @@
         return GrBackendTexture();
     }
 
+    if (GrVkFormatNeedsYcbcrSampler(vkFormat)) {
+        SkDebugf("Can't create BackendTexture that requires Ycbcb sampler.\n");
+        return GrBackendTexture();
+    }
+
     GrVkImageInfo info;
     if (!this->createVkImageForBackendSurface(vkFormat, w, h, true,
                                               GrRenderable::kYes == renderable, mipMapped, srcData,
@@ -2630,7 +2635,11 @@
     const GrVkSampler* sampler = this->resourceProvider().findOrCreateCompatibleSampler(
             samplerState, *ycbcrInfo);
 
-    return sampler->uniqueID();
+    uint32_t result = sampler->uniqueID();
+
+    sampler->unref(this);
+
+    return result;
 }
 
 void GrVkGpu::storeVkPipelineCacheData() {
diff --git a/src/gpu/vk/GrVkImage.h b/src/gpu/vk/GrVkImage.h
index 222fdd5..8f44863 100644
--- a/src/gpu/vk/GrVkImage.h
+++ b/src/gpu/vk/GrVkImage.h
@@ -57,7 +57,7 @@
     VkFormat imageFormat() const { return fInfo.fFormat; }
     GrBackendFormat getBackendFormat() const {
         if (fResource && this->ycbcrConversionInfo().isValid()) {
-            SkASSERT(this->imageFormat() == VK_FORMAT_UNDEFINED);
+            SkASSERT(this->imageFormat() == this->ycbcrConversionInfo().fFormat);
             return GrBackendFormat::MakeVk(this->ycbcrConversionInfo());
         }
         SkASSERT(this->imageFormat() != VK_FORMAT_UNDEFINED);
diff --git a/src/gpu/vk/GrVkImageView.cpp b/src/gpu/vk/GrVkImageView.cpp
index 73b54e3..ce7b1c5 100644
--- a/src/gpu/vk/GrVkImageView.cpp
+++ b/src/gpu/vk/GrVkImageView.cpp
@@ -19,7 +19,7 @@
     GrVkSamplerYcbcrConversion* ycbcrConversion = nullptr;
 
     if (ycbcrInfo.isValid()) {
-        SkASSERT(gpu->vkCaps().supportsYcbcrConversion() && format == VK_FORMAT_UNDEFINED);
+        SkASSERT(gpu->vkCaps().supportsYcbcrConversion() && format == ycbcrInfo.fFormat);
 
         ycbcrConversion =
                 gpu->resourceProvider().findOrCreateCompatibleSamplerYcbcrConversion(ycbcrInfo);
diff --git a/src/gpu/vk/GrVkResourceProvider.cpp b/src/gpu/vk/GrVkResourceProvider.cpp
index 8befc5b..cb21719 100644
--- a/src/gpu/vk/GrVkResourceProvider.cpp
+++ b/src/gpu/vk/GrVkResourceProvider.cpp
@@ -388,12 +388,16 @@
     fExternalRenderPasses.reset();
 
     // Iterate through all store GrVkSamplers and unref them before resetting the hash.
-    SkTDynamicHash<GrVkSampler, GrVkSampler::Key>::Iter iter(&fSamplers);
-    for (; !iter.done(); ++iter) {
+    for (decltype(fSamplers)::Iter iter(&fSamplers); !iter.done(); ++iter) {
         (*iter).unref(fGpu);
     }
     fSamplers.reset();
 
+    for (decltype(fYcbcrConversions)::Iter iter(&fYcbcrConversions); !iter.done(); ++iter) {
+        (*iter).unref(fGpu);
+    }
+    fYcbcrConversions.reset();
+
     fPipelineStateCache->release();
 
     GR_VK_CALL(fGpu->vkInterface(), DestroyPipelineCache(fGpu->device(), fPipelineCache, nullptr));
@@ -462,6 +466,11 @@
     }
     fSamplers.reset();
 
+    for (decltype(fYcbcrConversions)::Iter iter(&fYcbcrConversions); !iter.done(); ++iter) {
+        (*iter).unrefAndAbandon();
+    }
+    fYcbcrConversions.reset();
+
     fPipelineStateCache->abandon();
 
     fPipelineCache = VK_NULL_HANDLE;
diff --git a/src/gpu/vk/GrVkSampler.cpp b/src/gpu/vk/GrVkSampler.cpp
index 4f702c9..8e2d62d 100644
--- a/src/gpu/vk/GrVkSampler.cpp
+++ b/src/gpu/vk/GrVkSampler.cpp
@@ -83,8 +83,7 @@
 
         createInfo.pNext = &conversionInfo;
 
-        const VkFormatFeatureFlags& flags = ycbcrInfo.fExternalFormatFeatures;
-
+        VkFormatFeatureFlags flags = ycbcrInfo.fFormatFeatures;
         if (!SkToBool(flags & VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT)) {
             createInfo.magFilter = VK_FILTER_NEAREST;
             createInfo.minFilter = VK_FILTER_NEAREST;
diff --git a/src/gpu/vk/GrVkSamplerYcbcrConversion.cpp b/src/gpu/vk/GrVkSamplerYcbcrConversion.cpp
index 8757ac5..f33f521 100644
--- a/src/gpu/vk/GrVkSamplerYcbcrConversion.cpp
+++ b/src/gpu/vk/GrVkSamplerYcbcrConversion.cpp
@@ -14,11 +14,9 @@
     if (!gpu->vkCaps().supportsYcbcrConversion()) {
         return nullptr;
     }
-    // We only support creating ycbcr conversion for external formats;
-    SkASSERT(info.fExternalFormat);
 
 #ifdef SK_DEBUG
-    const VkFormatFeatureFlags& featureFlags = info.fExternalFormatFeatures;
+    const VkFormatFeatureFlags& featureFlags = info.fFormatFeatures;
     if (info.fXChromaOffset == VK_CHROMA_LOCATION_MIDPOINT ||
         info.fYChromaOffset == VK_CHROMA_LOCATION_MIDPOINT) {
         SkASSERT(featureFlags & VK_FORMAT_FEATURE_MIDPOINT_CHROMA_SAMPLES_BIT);
@@ -36,36 +34,50 @@
     }
 #endif
 
-#ifdef SK_BUILD_FOR_ANDROID
-    VkExternalFormatANDROID externalFormat;
-    externalFormat.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID;
-    externalFormat.pNext = nullptr;
-    externalFormat.externalFormat = info.fExternalFormat;
 
     VkSamplerYcbcrConversionCreateInfo ycbcrCreateInfo;
-    memset(&ycbcrCreateInfo, 0, sizeof(VkSamplerYcbcrConversionCreateInfo));
     ycbcrCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO;
-    ycbcrCreateInfo.pNext = &externalFormat;
-    ycbcrCreateInfo.format = VK_FORMAT_UNDEFINED;
+    ycbcrCreateInfo.pNext = nullptr;
+    ycbcrCreateInfo.format = info.fFormat;
     ycbcrCreateInfo.ycbcrModel = info.fYcbcrModel;
     ycbcrCreateInfo.ycbcrRange = info.fYcbcrRange;
-    // Componets is ignored for external format conversions;
-    // ycbcrCreateInfo.components = {0, 0, 0, 0};
+
+    // Components is ignored for external format conversions. For all other formats identity swizzle
+    // is used. It can be added to GrVkYcbcrConversionInfo if necessary.
+    ycbcrCreateInfo.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
+                                  VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY};
     ycbcrCreateInfo.xChromaOffset = info.fXChromaOffset;
     ycbcrCreateInfo.yChromaOffset = info.fYChromaOffset;
     ycbcrCreateInfo.chromaFilter = info.fChromaFilter;
     ycbcrCreateInfo.forceExplicitReconstruction = info.fForceExplicitReconstruction;
 
+#ifdef SK_BUILD_FOR_ANDROID
+    VkExternalFormatANDROID externalFormat;
+    if (info.fExternalFormat) {
+        // Format must not be specified for external images.
+        SkASSERT(info.fFormat == VK_FORMAT_UNDEFINED);
+        externalFormat.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID;
+        externalFormat.pNext = nullptr;
+        externalFormat.externalFormat = info.fExternalFormat;
+        ycbcrCreateInfo.pNext = &externalFormat;
+    }
+#else
+    // External images are supported only on Android;
+    SkASSERT(!info.fExternalFormat);
+#endif
+
+    if (!info.fExternalFormat) {
+        SkASSERT(info.fFormat != VK_FORMAT_UNDEFINED);
+    }
+
     VkSamplerYcbcrConversion conversion;
     GR_VK_CALL(gpu->vkInterface(), CreateSamplerYcbcrConversion(gpu->device(), &ycbcrCreateInfo,
                                                                 nullptr, &conversion));
     if (conversion == VK_NULL_HANDLE) {
         return nullptr;
     }
+
     return new GrVkSamplerYcbcrConversion(conversion, GenerateKey(info));
-#else
-    return nullptr;
-#endif
 }
 
 void GrVkSamplerYcbcrConversion::freeGPUData(GrVkGpu* gpu) const {
@@ -96,6 +108,5 @@
     ycbcrKey |= (static_cast<uint8_t>(ycbcrInfo.fChromaFilter) << kChromaFilterShift);
     ycbcrKey |= (static_cast<uint8_t>(ycbcrInfo.fForceExplicitReconstruction) << kReconShift);
 
-    return {ycbcrInfo.fExternalFormat, ycbcrKey};
+    return Key{ycbcrInfo.fFormat, ycbcrInfo.fExternalFormat, ycbcrKey};
 }
-
diff --git a/src/gpu/vk/GrVkSamplerYcbcrConversion.h b/src/gpu/vk/GrVkSamplerYcbcrConversion.h
index 1b57772..cf7a2c5 100644
--- a/src/gpu/vk/GrVkSamplerYcbcrConversion.h
+++ b/src/gpu/vk/GrVkSamplerYcbcrConversion.h
@@ -22,18 +22,21 @@
     VkSamplerYcbcrConversion ycbcrConversion() const { return fYcbcrConversion; }
 
     struct Key {
-        Key() : fExternalFormat(0), fConversionKey(0) {}
-        Key(uint64_t externalFormat, uint8_t conversionKey) {
+        Key() : fVkFormat(VK_FORMAT_UNDEFINED), fExternalFormat(0), fConversionKey(0) {}
+        Key(VkFormat vkFormat, uint64_t externalFormat, uint8_t conversionKey) {
             memset(this, 0, sizeof(Key));
+            fVkFormat = vkFormat;
             fExternalFormat = externalFormat;
             fConversionKey = conversionKey;
         }
 
+        VkFormat fVkFormat;
         uint64_t fExternalFormat;
         uint8_t  fConversionKey;
 
         bool operator==(const Key& that) const {
-            return this->fExternalFormat == that.fExternalFormat &&
+            return this->fVkFormat == that.fVkFormat &&
+                   this->fExternalFormat == that.fExternalFormat &&
                    this->fConversionKey == that.fConversionKey;
         }
     };
diff --git a/src/gpu/vk/GrVkUniformHandler.cpp b/src/gpu/vk/GrVkUniformHandler.cpp
index 32210d6..cee1706 100644
--- a/src/gpu/vk/GrVkUniformHandler.cpp
+++ b/src/gpu/vk/GrVkUniformHandler.cpp
@@ -204,6 +204,16 @@
     }
 }
 
+GrVkUniformHandler::~GrVkUniformHandler() {
+    GrVkGpu* gpu = static_cast<GrVkPipelineStateBuilder*>(fProgramBuilder)->gpu();
+    for (decltype(fSamplers)::Iter iter(&fSamplers); iter.next();) {
+        if (iter->fImmutableSampler) {
+            iter->fImmutableSampler->unref(gpu);
+            iter->fImmutableSampler = nullptr;
+        }
+    }
+}
+
 GrGLSLUniformHandler::UniformHandle GrVkUniformHandler::internalAddUniformArray(
                                                                             uint32_t visibility,
                                                                             GrSLType type,
@@ -282,10 +292,9 @@
     info.fVisibility = kFragment_GrShaderFlag;
     info.fUBOffset = 0;
 
-    // Check if we are dealing with an external texture and store the needed information if so
+    // Check if we are dealing with an external texture and store the needed information if so.
     const GrVkTexture* vkTexture = static_cast<const GrVkTexture*>(texture);
     if (vkTexture->ycbcrConversionInfo().isValid()) {
-        SkASSERT(type == GrTextureType::kExternal);
         GrVkGpu* gpu = static_cast<GrVkPipelineStateBuilder*>(fProgramBuilder)->gpu();
         info.fImmutableSampler = gpu->resourceProvider().findOrCreateCompatibleSampler(
                 state, vkTexture->ycbcrConversionInfo());
diff --git a/src/gpu/vk/GrVkUniformHandler.h b/src/gpu/vk/GrVkUniformHandler.h
index 8d80f20..f280970 100644
--- a/src/gpu/vk/GrVkUniformHandler.h
+++ b/src/gpu/vk/GrVkUniformHandler.h
@@ -39,12 +39,13 @@
         uint32_t                fVisibility;
         // fUBOffset is only valid if the GrSLType of the fVariable is not a sampler
         uint32_t                fUBOffset;
-        // The SamplerState, maxMipLevel, and ycbcrInfo are only valid if the GrSLType is a sampler
-        // and that sampler is used for sampling an external image with a ycbcr conversion.
+        // fImmutableSampler is used for sampling an image with a ycbcr conversion.
         const GrVkSampler*      fImmutableSampler = nullptr;
     };
     typedef GrTAllocator<UniformInfo> UniformInfoArray;
 
+    ~GrVkUniformHandler() override;
+
     const GrShaderVar& getUniformVariable(UniformHandle u) const override {
         return fUniforms[u.toIndex()].fVariable;
     }
diff --git a/src/gpu/vk/GrVkUtil.cpp b/src/gpu/vk/GrVkUtil.cpp
index 395c02f..80214ac 100644
--- a/src/gpu/vk/GrVkUtil.cpp
+++ b/src/gpu/vk/GrVkUtil.cpp
@@ -101,30 +101,32 @@
 #ifdef SK_DEBUG
 bool GrVkFormatColorTypePairIsValid(VkFormat format, GrColorType colorType) {
     switch (format) {
-        case VK_FORMAT_R8G8B8A8_UNORM:           return GrColorType::kRGBA_8888 == colorType ||
-                                                        GrColorType::kRGB_888x == colorType;
-        case VK_FORMAT_B8G8R8A8_UNORM:           return GrColorType::kBGRA_8888 == colorType;
-        case VK_FORMAT_R8G8B8A8_SRGB:            return GrColorType::kRGBA_8888_SRGB == colorType;
-        case VK_FORMAT_R8G8B8_UNORM:             return GrColorType::kRGB_888x == colorType;
-        case VK_FORMAT_R8G8_UNORM:               return GrColorType::kRG_88 == colorType;
-        case VK_FORMAT_A2B10G10R10_UNORM_PACK32: return GrColorType::kRGBA_1010102 == colorType;
-        case VK_FORMAT_R5G6B5_UNORM_PACK16:      return GrColorType::kBGR_565 == colorType;
+        case VK_FORMAT_R8G8B8A8_UNORM:            return GrColorType::kRGBA_8888 == colorType ||
+                                                         GrColorType::kRGB_888x == colorType;
+        case VK_FORMAT_B8G8R8A8_UNORM:            return GrColorType::kBGRA_8888 == colorType;
+        case VK_FORMAT_R8G8B8A8_SRGB:             return GrColorType::kRGBA_8888_SRGB == colorType;
+        case VK_FORMAT_R8G8B8_UNORM:              return GrColorType::kRGB_888x == colorType;
+        case VK_FORMAT_R8G8_UNORM:                return GrColorType::kRG_88 == colorType;
+        case VK_FORMAT_A2B10G10R10_UNORM_PACK32:  return GrColorType::kRGBA_1010102 == colorType;
+        case VK_FORMAT_R5G6B5_UNORM_PACK16:       return GrColorType::kBGR_565 == colorType;
         // R4G4B4A4 is not required to be supported so we actually
         // store RGBA_4444 data as B4G4R4A4.
-        case VK_FORMAT_B4G4R4A4_UNORM_PACK16:    return GrColorType::kABGR_4444 == colorType;
-        case VK_FORMAT_R4G4B4A4_UNORM_PACK16:    return GrColorType::kABGR_4444 == colorType;
+        case VK_FORMAT_B4G4R4A4_UNORM_PACK16:     return GrColorType::kABGR_4444 == colorType;
+        case VK_FORMAT_R4G4B4A4_UNORM_PACK16:     return GrColorType::kABGR_4444 == colorType;
         case VK_FORMAT_R8_UNORM:                 return GrColorType::kAlpha_8 == colorType ||
-                                                        GrColorType::kGray_8 == colorType;
-        case VK_FORMAT_R32G32B32A32_SFLOAT:      return GrColorType::kRGBA_F32 == colorType;
-        case VK_FORMAT_R16G16B16A16_SFLOAT:      return GrColorType::kRGBA_F16 == colorType ||
-                                                        GrColorType::kRGBA_F16_Clamped == colorType;
-        case VK_FORMAT_R16_SFLOAT:               return GrColorType::kAlpha_F16 == colorType;
-        case VK_FORMAT_R16_UNORM:                return GrColorType::kR_16 == colorType;
-        case VK_FORMAT_R16G16_UNORM:             return GrColorType::kRG_1616 == colorType;
+                                                         GrColorType::kGray_8 == colorType;
+        case VK_FORMAT_R32G32B32A32_SFLOAT:       return GrColorType::kRGBA_F32 == colorType;
+        case VK_FORMAT_R16G16B16A16_SFLOAT:       return GrColorType::kRGBA_F16 == colorType ||
+                                                         GrColorType::kRGBA_F16_Clamped == colorType;
+        case VK_FORMAT_R16_SFLOAT:                return GrColorType::kAlpha_F16 == colorType;
+        case VK_FORMAT_R16_UNORM:                 return GrColorType::kR_16 == colorType;
+        case VK_FORMAT_R16G16_UNORM:              return GrColorType::kRG_1616 == colorType;
+        case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM: return GrColorType::kRGB_888x == colorType;
+        case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:  return GrColorType::kRGB_888x == colorType;
         // Experimental (for Y416 and mutant P016/P010)
-        case VK_FORMAT_R16G16B16A16_UNORM:       return GrColorType::kRGBA_16161616 == colorType;
-        case VK_FORMAT_R16G16_SFLOAT:            return GrColorType::kRG_F16 == colorType;
-        default:                                 return false;
+        case VK_FORMAT_R16G16B16A16_UNORM:        return GrColorType::kRGBA_16161616 == colorType;
+        case VK_FORMAT_R16G16_SFLOAT:             return GrColorType::kRG_F16 == colorType;
+        default:                                  return false;
     }
 
     SkUNREACHABLE;
@@ -149,6 +151,8 @@
         case VK_FORMAT_R16_SFLOAT:
         case VK_FORMAT_R16_UNORM:
         case VK_FORMAT_R16G16_UNORM:
+        case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM:
+        case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
         // Experimental (for Y416 and mutant P016/P010)
         case VK_FORMAT_R16G16B16A16_UNORM:
         case VK_FORMAT_R16G16_SFLOAT:
@@ -158,6 +162,11 @@
     }
 }
 
+bool GrVkFormatNeedsYcbcrSampler(VkFormat format) {
+    return format == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM ||
+           format == VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM;
+}
+
 bool GrSampleCountToVkSampleCount(uint32_t samples, VkSampleCountFlagBits* vkSamples) {
     SkASSERT(samples >= 1);
     switch (samples) {
diff --git a/src/gpu/vk/GrVkUtil.h b/src/gpu/vk/GrVkUtil.h
index d58f585..dded319 100644
--- a/src/gpu/vk/GrVkUtil.h
+++ b/src/gpu/vk/GrVkUtil.h
@@ -36,6 +36,8 @@
 
 bool GrVkFormatIsSupported(VkFormat);
 
+bool GrVkFormatNeedsYcbcrSampler(VkFormat format);
+
 #ifdef SK_DEBUG
 /**
  * Returns true if the passed in VkFormat and GrColorType are compatible with each other.