[graphite] Add Caps::toString virtual

This will be used to pretty print YCbCr information in the Pipeline labels.

As an example, this will replace the substring:
  HardwareImage(3: kAwAEPcAAAAAAAAA)
with:
  HardwareImage(x247 2020+narrow cos cos nearest F rgba cf1lf0)

Unfortunately, wgpu::YCbCrVkDescriptor and skgpu::VulkanYcbcrConversionInfo have diverged (in their handling of the ChromaFilter and member variable ordering) so we can't share code between native Vulkan and Dawn Vulkan.

Bug: b/456438502
Change-Id: I3b78af7a546e30c0104cd988bb43b5f08e9886ef
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1087937
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
diff --git a/include/gpu/vk/VulkanTypes.h b/include/gpu/vk/VulkanTypes.h
index b9d0017..b3f4d5a 100644
--- a/include/gpu/vk/VulkanTypes.h
+++ b/include/gpu/vk/VulkanTypes.h
@@ -157,6 +157,7 @@
     VkBool32 forceExplicitReconstruction() const { return fForceExplicitReconstruction; }
     VkComponentMapping components() const { return fComponents; }
 
+    bool samplerFilterMustMatchChromaFilter() const { return fSamplerFilterMustMatchChromaFilter; }
     bool supportsLinearFilter() const { return fSupportsLinearFilter; }
 
     // Returns a populated VkSamplerYcbcrConversionCreateInfo object based on
@@ -225,7 +226,7 @@
                                                  VK_COMPONENT_SWIZZLE_IDENTITY,
                                                  VK_COMPONENT_SWIZZLE_IDENTITY};
 
-    // Default these values to the most restrictive. These defaults only really matter while until
+    // Default these values to the most restrictive. These defaults only really matter until
     // we force all clients to go through our constructors. At that point these will be set to the
     // correct values.
     bool fSamplerFilterMustMatchChromaFilter = true;
diff --git a/src/gpu/graphite/Caps.h b/src/gpu/graphite/Caps.h
index 905fb3e..5c34b01 100644
--- a/src/gpu/graphite/Caps.h
+++ b/src/gpu/graphite/Caps.h
@@ -189,7 +189,7 @@
      */
     size_t requiredTransferBufferAlignment() const { return fRequiredTransferBufferAlignment; }
 
-    /* Returns the aligned rowBytes when transfering to or from a Texture */
+    /* Returns the aligned rowBytes when transferring to or from a Texture */
     size_t getAlignedTextureDataRowBytes(size_t rowBytes) const {
         return SkAlignTo(rowBytes, fTextureDataRowBytesAlignment);
     }
@@ -202,10 +202,13 @@
         return {};
     }
 
+    /* Returns a compressed label describing the immutable sampler for the Pipeline label */
+    virtual std::string toString(const ImmutableSamplerInfo&) const { return ""; }
+
     /**
      * Backends may have restrictions on what types of textures support Device::writePixels().
      * If this returns false then the caller should implement a fallback where a temporary texture
-     * is created, pixels are written to it, and then that is copied or drawn into the the surface.
+     * is created, pixels are written to it, and then that is copied or drawn into the surface.
      */
     virtual bool supportsWritePixels(const TextureInfo&) const = 0;
 
diff --git a/src/gpu/graphite/dawn/DawnCaps.cpp b/src/gpu/graphite/dawn/DawnCaps.cpp
index 6d01941..b8ce276 100644
--- a/src/gpu/graphite/dawn/DawnCaps.cpp
+++ b/src/gpu/graphite/dawn/DawnCaps.cpp
@@ -1163,6 +1163,87 @@
     return {};
 }
 
+#if !defined(__EMSCRIPTEN__)
+static constexpr const char* filter_mode_to_str(wgpu::FilterMode mode) {
+    switch (mode) {
+        case wgpu::FilterMode::Undefined: return "undefined";
+        case wgpu::FilterMode::Nearest:   return "nearest";
+        case wgpu::FilterMode::Linear:    return "linear";
+    }
+    SkUNREACHABLE;
+}
+
+static constexpr const char* model_to_str(uint32_t c) {
+    switch (c) {
+        case 0 /* VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY */:   return "RGB-I";
+        case 1 /* VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY */: return "YCbCr-I";
+        case 2 /* VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709 */:      return "709";
+        case 3 /* VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601 */:      return "601";
+        case 4 /* VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020 */:     return "2020";
+        default:                                                       return "unknown";
+    }
+    SkUNREACHABLE;
+}
+
+static constexpr const char* range_to_str(uint32_t r) {
+    switch (r) {
+        case 0 /* VK_SAMPLER_YCBCR_RANGE_ITU_FULL */:   return "full";
+        case 1 /* VK_SAMPLER_YCBCR_RANGE_ITU_NARROW */: return "narrow";
+        default:                                        return "unknown";
+    }
+    SkUNREACHABLE;
+}
+
+static constexpr char swizzle_to_str(uint32_t c, char identityAnswer) {
+    switch (c) {
+        case 0 /* VK_COMPONENT_SWIZZLE_IDENTITY */: return identityAnswer;
+        case 1 /* VK_COMPONENT_SWIZZLE_ZERO */:     return '0';
+        case 2 /* VK_COMPONENT_SWIZZLE_ONE */:      return '1';
+        case 3 /* VK_COMPONENT_SWIZZLE_R */:        return 'r';
+        case 4 /* VK_COMPONENT_SWIZZLE_G */:        return 'g';
+        case 5 /* VK_COMPONENT_SWIZZLE_B */:        return 'b';
+        case 6 /* VK_COMPONENT_SWIZZLE_A */:        return 'a';
+        default:                                    return '?';
+    }
+    SkUNREACHABLE;
+}
+#endif
+
+std::string DawnCaps::toString(const ImmutableSamplerInfo& immutableSamplerInfo) const {
+#if defined(__EMSCRIPTEN__)
+    return "";
+#else
+    const wgpu::YCbCrVkDescriptor info =
+                DawnDescriptorFromImmutableSamplerInfo(immutableSamplerInfo);
+    if (!DawnDescriptorIsValid(info)) {
+        return "";
+    }
+
+    std::string result;
+
+    if (info.vkFormat == 0) {
+        result += 'x';
+        result += std::to_string(info.externalFormat);
+    } else {
+        result += info.vkFormat;
+    }
+
+    result += " ";
+    result += model_to_str(info.vkYCbCrModel);
+    result += "+";
+    result += range_to_str(info.vkYCbCrRange);
+    result += info.vkXChromaOffset ? " mid"  : " cos";  // midpoint or cosited-even
+    result += info.vkYChromaOffset ? " mid " : " cos "; // midpoint or cosited-even
+    result += filter_mode_to_str(info.vkChromaFilter);
+    result += info.forceExplicitReconstruction ? " T " : " F ";
+    result += swizzle_to_str(info.vkComponentSwizzleRed,   'r');
+    result += swizzle_to_str(info.vkComponentSwizzleGreen, 'g');
+    result += swizzle_to_str(info.vkComponentSwizzleBlue,  'b');
+    result += swizzle_to_str(info.vkComponentSwizzleAlpha, 'a');
+    return result;
+#endif
+}
+
 void DawnCaps::buildKeyForTexture(SkISize dimensions,
                                   const TextureInfo& info,
                                   ResourceType type,
diff --git a/src/gpu/graphite/dawn/DawnCaps.h b/src/gpu/graphite/dawn/DawnCaps.h
index 79ae7b1..bee374d 100644
--- a/src/gpu/graphite/dawn/DawnCaps.h
+++ b/src/gpu/graphite/dawn/DawnCaps.h
@@ -58,6 +58,8 @@
                               const RendererProvider*) const override;
     UniqueKey makeComputePipelineKey(const ComputePipelineDesc&) const override;
     ImmutableSamplerInfo getImmutableSamplerInfo(const TextureInfo&) const override;
+    std::string toString(const ImmutableSamplerInfo&) const override;
+
     bool isRenderable(const TextureInfo&) const override;
     bool isStorage(const TextureInfo&) const override;
 
diff --git a/src/gpu/graphite/vk/VulkanCaps.cpp b/src/gpu/graphite/vk/VulkanCaps.cpp
index 5a7ad43..67b9ac2 100644
--- a/src/gpu/graphite/vk/VulkanCaps.cpp
+++ b/src/gpu/graphite/vk/VulkanCaps.cpp
@@ -2239,4 +2239,49 @@
     return {};
 }
 
+static constexpr const char* vk_chromafilter_to_str(VkFilter f) {
+    switch (f) {
+        case VK_FILTER_NEAREST:   return "nearest";
+        case VK_FILTER_LINEAR:    return "linear";
+        case VK_FILTER_CUBIC_EXT: return "cubic";
+        default:                  return "unknown";
+    }
+    SkUNREACHABLE;
+}
+
+std::string VulkanCaps::toString(const ImmutableSamplerInfo& immutableSamplerInfo) const {
+    const skgpu::VulkanYcbcrConversionInfo info =
+            VulkanYcbcrConversion::FromImmutableSamplerInfo(immutableSamplerInfo);
+    if (!info.isValid()) {
+        return "";
+    }
+
+    std::string result;
+
+    if (info.hasExternalFormat()) {
+        result += 'x';
+        result += std::to_string(info.externalFormat());
+    } else {
+        result += info.format();
+    }
+
+    result += " ";
+    result += VkModelToStr(info.model());
+    result += "+";
+    result += VkRangeToStr(info.range());
+    result += info.xChromaOffset() ? " mid"  : " cos";  // midpoint or cosited-even
+    result += info.yChromaOffset() ? " mid " : " cos "; // midpoint or cosited-even
+    result += vk_chromafilter_to_str(info.chromaFilter());
+    result += info.forceExplicitReconstruction() ? " T " : " F ";
+    result += VkSwizzleToStr(info.components().r, 'r');
+    result += VkSwizzleToStr(info.components().g, 'g');
+    result += VkSwizzleToStr(info.components().b, 'b');
+    result += VkSwizzleToStr(info.components().a, 'a');
+    result += " cf";
+    result += info.samplerFilterMustMatchChromaFilter() ? '1' : '0';
+    result += "lf";
+    result += info.supportsLinearFilter() ? '1' : '0';
+    return result;
+}
+
 } // namespace skgpu::graphite
diff --git a/src/gpu/graphite/vk/VulkanCaps.h b/src/gpu/graphite/vk/VulkanCaps.h
index 5dfaa76..b67c4cd 100644
--- a/src/gpu/graphite/vk/VulkanCaps.h
+++ b/src/gpu/graphite/vk/VulkanCaps.h
@@ -52,6 +52,7 @@
     DstReadStrategy getDstReadStrategy() const override;
 
     ImmutableSamplerInfo getImmutableSamplerInfo(const TextureInfo&) const override;
+    std::string toString(const ImmutableSamplerInfo&) const override;
 
     UniqueKey makeGraphicsPipelineKey(const GraphicsPipelineDesc&,
                                       const RenderPassDesc&) const override;
diff --git a/src/gpu/vk/VulkanUtilsPriv.cpp b/src/gpu/vk/VulkanUtilsPriv.cpp
index 25b76e0..b95db22 100644
--- a/src/gpu/vk/VulkanUtilsPriv.cpp
+++ b/src/gpu/vk/VulkanUtilsPriv.cpp
@@ -397,7 +397,7 @@
     fSupportsLinearFilter = SkToBool(formatFeatures &
                                      VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT);
     if (fSamplerFilterMustMatchChromaFilter && !fSupportsLinearFilter) {
-        // Because we don't have have separate reconstruction filter, the min, mag and  filter must
+        // Because we don't have separate reconstruction filter, the min, mag and  filter must
         // all match. However, we also don't support linear sampling so the min/mag filter have to
         // be nearest. Therefore, we force the chroma filter to be nearest regardless of support for
         // the feature VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT.
diff --git a/src/gpu/vk/VulkanUtilsPriv.h b/src/gpu/vk/VulkanUtilsPriv.h
index 3b495d3..1e40948 100644
--- a/src/gpu/vk/VulkanUtilsPriv.h
+++ b/src/gpu/vk/VulkanUtilsPriv.h
@@ -300,6 +300,41 @@
     }
 }
 
+static constexpr const char* VkModelToStr(VkSamplerYcbcrModelConversion c) {
+    switch (c) {
+        case VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY:   return "RGB-I";
+        case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY: return "YCbCr-I";
+        case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709:      return "709";
+        case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601:      return "601";
+        case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020:     return "2020";
+        default:                                               return "unknown";
+    }
+    SkUNREACHABLE;
+}
+
+static constexpr const char* VkRangeToStr(VkSamplerYcbcrRange r) {
+    switch (r) {
+        case VK_SAMPLER_YCBCR_RANGE_ITU_FULL:   return "full";
+        case VK_SAMPLER_YCBCR_RANGE_ITU_NARROW: return "narrow";
+        default:                                return "unknown";
+    }
+    SkUNREACHABLE;
+}
+
+static constexpr char VkSwizzleToStr(VkComponentSwizzle c, char identityAnswer) {
+    switch (c) {
+        case VK_COMPONENT_SWIZZLE_IDENTITY: return identityAnswer;
+        case VK_COMPONENT_SWIZZLE_ZERO:     return '0';
+        case VK_COMPONENT_SWIZZLE_ONE:      return '1';
+        case VK_COMPONENT_SWIZZLE_R:        return 'r';
+        case VK_COMPONENT_SWIZZLE_G:        return 'g';
+        case VK_COMPONENT_SWIZZLE_B:        return 'b';
+        case VK_COMPONENT_SWIZZLE_A:        return 'a';
+        default:                            return '?';
+    }
+    SkUNREACHABLE;
+}
+
 #ifdef SK_BUILD_FOR_ANDROID
 /**
  * Vulkan AHardwareBuffer utility functions shared between graphite and ganesh