[YCbCr Samplers] Pass VkSamplerYcbcrConversionInfo on creating Sampler

Use the VkSamplerYcbcrConversionCreateInfo passed on the sampler
descriptor and use it for creating the VkSampler properly. Also
add relevant tests for passing the VkExternalFormatANDROID on the
ycbcr info and enable feature under flags.

Change-Id: I16b0075fde249752a6078b65e50d950fd905c626
Bug: dawn:2476
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/183961
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Saifuddin Hitawala <hitawala@chromium.org>
diff --git a/src/dawn/native/vulkan/DeviceVk.cpp b/src/dawn/native/vulkan/DeviceVk.cpp
index a0e90d4..24cf4a4 100644
--- a/src/dawn/native/vulkan/DeviceVk.cpp
+++ b/src/dawn/native/vulkan/DeviceVk.cpp
@@ -513,6 +513,14 @@
         featuresChain.Add(&usedKnobs.shaderSubgroupUniformControlFlowFeatures);
     }
 
+    if (HasFeature(Feature::YCbCrVulkanSamplers) &&
+        mDeviceInfo.HasExt(DeviceExt::SamplerYCbCrConversion) &&
+        mDeviceInfo.HasExt(DeviceExt::ExternalMemoryAndroidHardwareBuffer)) {
+        usedKnobs.samplerYCbCrConversionFeatures.samplerYcbcrConversion = VK_TRUE;
+        featuresChain.Add(&usedKnobs.samplerYCbCrConversionFeatures,
+                          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES);
+    }
+
     // Find a universal queue family
     {
         // Note that GRAPHICS and COMPUTE imply TRANSFER so we don't need to check for it.
diff --git a/src/dawn/native/vulkan/FencedDeleter.cpp b/src/dawn/native/vulkan/FencedDeleter.cpp
index 5894a59..9350140 100644
--- a/src/dawn/native/vulkan/FencedDeleter.cpp
+++ b/src/dawn/native/vulkan/FencedDeleter.cpp
@@ -45,6 +45,7 @@
     DAWN_ASSERT(mPipelineLayoutsToDelete.Empty());
     DAWN_ASSERT(mQueryPoolsToDelete.Empty());
     DAWN_ASSERT(mRenderPassesToDelete.Empty());
+    DAWN_ASSERT(mSamplerYcbcrConversionsToDelete.Empty());
     DAWN_ASSERT(mSamplersToDelete.Empty());
     DAWN_ASSERT(mSemaphoresToDelete.Empty());
     DAWN_ASSERT(mShaderModulesToDelete.Empty());
@@ -92,6 +93,11 @@
     mRenderPassesToDelete.Enqueue(renderPass, mDevice->GetQueue()->GetPendingCommandSerial());
 }
 
+void FencedDeleter::DeleteWhenUnused(VkSamplerYcbcrConversion samplerYcbcrConversion) {
+    mSamplerYcbcrConversionsToDelete.Enqueue(samplerYcbcrConversion,
+                                             mDevice->GetQueue()->GetPendingCommandSerial());
+}
+
 void FencedDeleter::DeleteWhenUnused(VkSampler sampler) {
     mSamplersToDelete.Enqueue(sampler, mDevice->GetQueue()->GetPendingCommandSerial());
 }
@@ -187,6 +193,12 @@
     }
     mQueryPoolsToDelete.ClearUpTo(completedSerial);
 
+    for (VkSamplerYcbcrConversion samplerYcbcrConversion :
+         mSamplerYcbcrConversionsToDelete.IterateUpTo(completedSerial)) {
+        mDevice->fn.DestroySamplerYcbcrConversion(vkDevice, samplerYcbcrConversion, nullptr);
+    }
+    mSamplerYcbcrConversionsToDelete.ClearUpTo(completedSerial);
+
     for (VkSampler sampler : mSamplersToDelete.IterateUpTo(completedSerial)) {
         mDevice->fn.DestroySampler(vkDevice, sampler, nullptr);
     }
diff --git a/src/dawn/native/vulkan/FencedDeleter.h b/src/dawn/native/vulkan/FencedDeleter.h
index a8b4179..43bad63 100644
--- a/src/dawn/native/vulkan/FencedDeleter.h
+++ b/src/dawn/native/vulkan/FencedDeleter.h
@@ -52,6 +52,7 @@
     void DeleteWhenUnused(VkRenderPass renderPass);
     void DeleteWhenUnused(VkPipeline pipeline);
     void DeleteWhenUnused(VkQueryPool querypool);
+    void DeleteWhenUnused(VkSamplerYcbcrConversion samplerYcbcrConversion);
     void DeleteWhenUnused(VkSampler sampler);
     void DeleteWhenUnused(VkSemaphore semaphore);
     void DeleteWhenUnused(VkShaderModule module);
@@ -72,6 +73,7 @@
     SerialQueue<ExecutionSerial, VkPipelineLayout> mPipelineLayoutsToDelete;
     SerialQueue<ExecutionSerial, VkQueryPool> mQueryPoolsToDelete;
     SerialQueue<ExecutionSerial, VkRenderPass> mRenderPassesToDelete;
+    SerialQueue<ExecutionSerial, VkSamplerYcbcrConversion> mSamplerYcbcrConversionsToDelete;
     SerialQueue<ExecutionSerial, VkSampler> mSamplersToDelete;
     SerialQueue<ExecutionSerial, VkSemaphore> mSemaphoresToDelete;
     SerialQueue<ExecutionSerial, VkShaderModule> mShaderModulesToDelete;
diff --git a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
index 0161177..e6bd5a6 100644
--- a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
+++ b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
@@ -266,6 +266,12 @@
         EnableFeature(Feature::DepthClipControl);
     }
 
+    if (mDeviceInfo.HasExt(DeviceExt::SamplerYCbCrConversion) &&
+        mDeviceInfo.HasExt(DeviceExt::ExternalMemoryAndroidHardwareBuffer) &&
+        mDeviceInfo.samplerYCbCrConversionFeatures.samplerYcbcrConversion == VK_TRUE) {
+        EnableFeature(Feature::YCbCrVulkanSamplers);
+    }
+
     VkFormatProperties rg11b10Properties;
     mVulkanInstance->GetFunctions().GetPhysicalDeviceFormatProperties(
         mVkPhysicalDevice, VK_FORMAT_B10G11R11_UFLOAT_PACK32, &rg11b10Properties);
diff --git a/src/dawn/native/vulkan/SamplerVk.cpp b/src/dawn/native/vulkan/SamplerVk.cpp
index 4536572..e10248f 100644
--- a/src/dawn/native/vulkan/SamplerVk.cpp
+++ b/src/dawn/native/vulkan/SamplerVk.cpp
@@ -29,6 +29,7 @@
 
 #include <algorithm>
 
+#include "dawn/native/ChainUtils.h"
 #include "dawn/native/vulkan/DeviceVk.h"
 #include "dawn/native/vulkan/FencedDeleter.h"
 #include "dawn/native/vulkan/UtilsVulkan.h"
@@ -120,6 +121,33 @@
         createInfo.maxAnisotropy = 1;
     }
 
+    VkSamplerYcbcrConversionInfo samplerYCbCrInfo = {};
+    if (auto* vulkanYCbCrDescriptor =
+            Unpack(descriptor).Get<vulkan::SamplerYCbCrVulkanDescriptor>()) {
+        const VkSamplerYcbcrConversionCreateInfo& vulkanYCbCrInfo =
+            vulkanYCbCrDescriptor->vulkanYCbCrInfo;
+#if DAWN_PLATFORM_IS(ANDROID)
+        const VkExternalFormatANDROID* vkExternalFormat =
+            static_cast<const VkExternalFormatANDROID*>(vulkanYCbCrInfo.pNext);
+        if (vkExternalFormat) {
+            DAWN_INVALID_IF((vkExternalFormat->externalFormat == 0 &&
+                             vulkanYCbCrInfo.format == VK_FORMAT_UNDEFINED),
+                            "Both VkFormat and VkExternalFormatANDROID are undefined.");
+        }
+#endif  // DAWN_PLATFORM_IS(ANDROID)
+
+        DAWN_TRY(CheckVkSuccess(
+            device->fn.CreateSamplerYcbcrConversion(device->GetVkDevice(), &vulkanYCbCrInfo,
+                                                    nullptr, &*mSamplerYCbCrConversion),
+            "CreateSamplerYcbcrConversion"));
+
+        samplerYCbCrInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO;
+        samplerYCbCrInfo.pNext = nullptr;
+        samplerYCbCrInfo.conversion = mSamplerYCbCrConversion;
+
+        createInfo.pNext = &samplerYCbCrInfo;
+    }
+
     DAWN_TRY(CheckVkSuccess(
         device->fn.CreateSampler(device->GetVkDevice(), &createInfo, nullptr, &*mHandle),
         "CreateSampler"));
@@ -133,6 +161,10 @@
 
 void Sampler::DestroyImpl() {
     SamplerBase::DestroyImpl();
+    if (mSamplerYCbCrConversion != VK_NULL_HANDLE) {
+        ToBackend(GetDevice())->GetFencedDeleter()->DeleteWhenUnused(mSamplerYCbCrConversion);
+        mSamplerYCbCrConversion = VK_NULL_HANDLE;
+    }
     if (mHandle != VK_NULL_HANDLE) {
         ToBackend(GetDevice())->GetFencedDeleter()->DeleteWhenUnused(mHandle);
         mHandle = VK_NULL_HANDLE;
diff --git a/src/dawn/native/vulkan/SamplerVk.h b/src/dawn/native/vulkan/SamplerVk.h
index b2e2ff1..bbde4ae 100644
--- a/src/dawn/native/vulkan/SamplerVk.h
+++ b/src/dawn/native/vulkan/SamplerVk.h
@@ -53,6 +53,7 @@
     void SetLabelImpl() override;
 
     VkSampler mHandle = VK_NULL_HANDLE;
+    VkSamplerYcbcrConversion mSamplerYCbCrConversion = VK_NULL_HANDLE;
 };
 
 }  // namespace dawn::native::vulkan
diff --git a/src/dawn/native/vulkan/VulkanFunctions.cpp b/src/dawn/native/vulkan/VulkanFunctions.cpp
index 483984e..b5b4ce5 100644
--- a/src/dawn/native/vulkan/VulkanFunctions.cpp
+++ b/src/dawn/native/vulkan/VulkanFunctions.cpp
@@ -391,6 +391,11 @@
         GET_DEVICE_PROC(GetImageSparseMemoryRequirements2);
     }
 
+    if (deviceInfo.HasExt(DeviceExt::SamplerYCbCrConversion)) {
+        GET_DEVICE_PROC(CreateSamplerYcbcrConversion);
+        GET_DEVICE_PROC(DestroySamplerYcbcrConversion);
+    }
+
 #if VK_USE_PLATFORM_FUCHSIA
     if (deviceInfo.HasExt(DeviceExt::ExternalMemoryZirconHandle)) {
         GET_DEVICE_PROC(GetMemoryZirconHandleFUCHSIA);
diff --git a/src/dawn/native/vulkan/VulkanFunctions.h b/src/dawn/native/vulkan/VulkanFunctions.h
index 6d8be892..61d2ba8 100644
--- a/src/dawn/native/vulkan/VulkanFunctions.h
+++ b/src/dawn/native/vulkan/VulkanFunctions.h
@@ -324,6 +324,8 @@
     VkFn<PFN_vkUnmapMemory> UnmapMemory = nullptr;
     VkFn<PFN_vkUpdateDescriptorSets> UpdateDescriptorSets = nullptr;
     VkFn<PFN_vkWaitForFences> WaitForFences = nullptr;
+    VkFn<PFN_vkCreateSamplerYcbcrConversion> CreateSamplerYcbcrConversion = nullptr;
+    VkFn<PFN_vkDestroySamplerYcbcrConversion> DestroySamplerYcbcrConversion = nullptr;
 
     // VK_KHR_external_memory_fd
     VkFn<PFN_vkGetMemoryFdKHR> GetMemoryFdKHR = nullptr;
diff --git a/src/dawn/native/vulkan/VulkanInfo.cpp b/src/dawn/native/vulkan/VulkanInfo.cpp
index fcb2419..5f63e9d 100644
--- a/src/dawn/native/vulkan/VulkanInfo.cpp
+++ b/src/dawn/native/vulkan/VulkanInfo.cpp
@@ -309,6 +309,11 @@
                               VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT);
         }
 
+        if (info.extensions[DeviceExt::SamplerYCbCrConversion]) {
+            featuresChain.Add(&info.samplerYCbCrConversionFeatures,
+                              VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES);
+        }
+
         // Check subgroup features and properties
         propertiesChain.Add(&info.subgroupProperties,
                             VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES);
diff --git a/src/dawn/native/vulkan/VulkanInfo.h b/src/dawn/native/vulkan/VulkanInfo.h
index f87b179..d254213 100644
--- a/src/dawn/native/vulkan/VulkanInfo.h
+++ b/src/dawn/native/vulkan/VulkanInfo.h
@@ -70,6 +70,7 @@
     VkPhysicalDeviceRobustness2FeaturesEXT robustness2Features;
     VkPhysicalDeviceShaderSubgroupUniformControlFlowFeaturesKHR
         shaderSubgroupUniformControlFlowFeatures;
+    VkPhysicalDeviceSamplerYcbcrConversionFeatures samplerYCbCrConversionFeatures;
 
     bool HasExt(DeviceExt ext) const;
     DeviceExtSet extensions;
diff --git a/src/dawn/tests/end2end/YCbCrSamplerTests.cpp b/src/dawn/tests/end2end/YCbCrSamplerTests.cpp
index 9e19b53..fadb9a6 100644
--- a/src/dawn/tests/end2end/YCbCrSamplerTests.cpp
+++ b/src/dawn/tests/end2end/YCbCrSamplerTests.cpp
@@ -30,6 +30,10 @@
 #include "dawn/native/VulkanBackend.h"
 #include "dawn/tests/DawnTest.h"
 
+#if DAWN_PLATFORM_IS(ANDROID)
+#include <android/hardware_buffer.h>
+#endif  // DAWN_PLATFORM_IS(ANDROID)
+
 namespace dawn {
 namespace {
 
@@ -56,11 +60,86 @@
 TEST_P(YCbCrSamplerTest, YCbCrSamplerValidWhenFeatureEnabled) {
     wgpu::SamplerDescriptor samplerDesc = {};
     native::vulkan::SamplerYCbCrVulkanDescriptor samplerYCbCrDesc = {};
+    samplerYCbCrDesc.vulkanYCbCrInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO;
+    samplerYCbCrDesc.vulkanYCbCrInfo.pNext = nullptr;
+    samplerYCbCrDesc.vulkanYCbCrInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
+
     samplerDesc.nextInChain = &samplerYCbCrDesc;
 
     device.CreateSampler(&samplerDesc);
 }
 
+// Test that it is possible to create the sampler with ycbcr sampler descriptor with only vulkan
+// format set.
+TEST_P(YCbCrSamplerTest, YCbCrSamplerValidWithOnlyVkFormat) {
+    wgpu::SamplerDescriptor samplerDesc = {};
+    native::vulkan::SamplerYCbCrVulkanDescriptor samplerYCbCrDesc = {};
+    samplerYCbCrDesc.vulkanYCbCrInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO;
+    samplerYCbCrDesc.vulkanYCbCrInfo.pNext = nullptr;
+    samplerYCbCrDesc.vulkanYCbCrInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
+
+#if DAWN_PLATFORM_IS(ANDROID)
+    VkExternalFormatANDROID vulkanExternalFormat = {};
+    vulkanExternalFormat.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID;
+    vulkanExternalFormat.pNext = nullptr;
+    // format is set as VK_FORMAT.
+    vulkanExternalFormat.externalFormat = 0;
+
+    samplerYCbCrDesc.vulkanYCbCrInfo.pNext = &vulkanExternalFormat;
+#endif  // DAWN_PLATFORM_IS(ANDROID)
+
+    samplerDesc.nextInChain = &samplerYCbCrDesc;
+
+    device.CreateSampler(&samplerDesc);
+}
+
+// Test that it is possible to create the sampler with ycbcr sampler descriptor with only external
+// format set.
+TEST_P(YCbCrSamplerTest, YCbCrSamplerValidWithOnlyExternalFormat) {
+    wgpu::SamplerDescriptor samplerDesc = {};
+    native::vulkan::SamplerYCbCrVulkanDescriptor samplerYCbCrDesc = {};
+    samplerYCbCrDesc.vulkanYCbCrInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO;
+    samplerYCbCrDesc.vulkanYCbCrInfo.pNext = nullptr;
+    // format is set as externalFormat.
+    samplerYCbCrDesc.vulkanYCbCrInfo.format = VK_FORMAT_UNDEFINED;
+
+#if DAWN_PLATFORM_IS(ANDROID)
+    VkExternalFormatANDROID vulkanExternalFormat = {};
+    vulkanExternalFormat.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID;
+    vulkanExternalFormat.pNext = nullptr;
+    vulkanExternalFormat.externalFormat = 5;
+
+    samplerYCbCrDesc.vulkanYCbCrInfo.pNext = &vulkanExternalFormat;
+#endif  // DAWN_PLATFORM_IS(ANDROID)
+
+    samplerDesc.nextInChain = &samplerYCbCrDesc;
+
+    device.CreateSampler(&samplerDesc);
+}
+
+// Test that it is not possible to create the sampler with ycbcr sampler descriptor and no format
+// set.
+TEST_P(YCbCrSamplerTest, YCbCrSamplerInvalidWithNoFormat) {
+    wgpu::SamplerDescriptor samplerDesc = {};
+    native::vulkan::SamplerYCbCrVulkanDescriptor samplerYCbCrDesc = {};
+    samplerYCbCrDesc.vulkanYCbCrInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO;
+    samplerYCbCrDesc.vulkanYCbCrInfo.pNext = nullptr;
+    samplerYCbCrDesc.vulkanYCbCrInfo.format = VK_FORMAT_UNDEFINED;
+
+#if DAWN_PLATFORM_IS(ANDROID)
+    VkExternalFormatANDROID vulkanExternalFormat = {};
+    vulkanExternalFormat.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID;
+    vulkanExternalFormat.pNext = nullptr;
+    vulkanExternalFormat.externalFormat = 0;
+
+    samplerYCbCrDesc.vulkanYCbCrInfo.pNext = &vulkanExternalFormat;
+#endif  // DAWN_PLATFORM_IS(ANDROID)
+
+    samplerDesc.nextInChain = &samplerYCbCrDesc;
+
+    ASSERT_DEVICE_ERROR(device.CreateSampler(&samplerDesc));
+}
+
 DAWN_INSTANTIATE_TEST(YCbCrSamplerTest, VulkanBackend());
 
 }  // anonymous namespace