[Vulkan] Populate YCbCr info in SharedTextureMemory::GetProperties()

This CL populates YCbCr info when it is passed into
SharedTextureMemory::GetProperties() on Android/Vulkan. The populated
information comes from the AHB backing the SharedTextureMemory. We also
add a test verifying that the populated info matches that of the AHB
backing the STM instance, as well as validation that the passed-in
YCbCr info does not have anything chained onto it.

Bug: dawn:2476
Change-Id: Ie9536d42d59bc4e1a49d4bc3b4cf98464c89bd33
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/188240
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Colin Blundell <blundell@chromium.org>
diff --git a/docs/dawn/features/y_cb_cr_vulkan_samplers.md b/docs/dawn/features/y_cb_cr_vulkan_samplers.md
index d18d3b1..6234d5c 100644
--- a/docs/dawn/features/y_cb_cr_vulkan_samplers.md
+++ b/docs/dawn/features/y_cb_cr_vulkan_samplers.md
@@ -5,11 +5,16 @@
 can supply `YCbCrVkDescriptor` instances when creating samplers and
 texture views. Clients can also obtain the YCbCr info for a
 SharedTextureMemory instance that was created from an AHardwareBuffer by
-querying its properties. When obtaining this info, note that some of the
-info *must* be populated while other info *may* be populated, corresponding
-to which fields of the underlying `VkSamplerYcbcrConversionCreateInfo` are
-mandatory vs. optional.
+querying its properties. Most properties will be created directly from the
+corresponding buffer format properties on the underlying AHardwareBuffer. The
+two exceptions are as follows:
 
+* `vkChromaFilter`: Will be set to VK_FILTER_LINEAR iff
+  `VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT` is
+  present in the AHB format features and VK_FILTER_NEAREST otherwise
+* `forceExplicitReconstruction`: will be set to true iff
+  `VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_BIT`
+  is present in the AHB format features
 TODO(crbug.com/dawn/2476): Expand this documentation with examples and
 description of semantics (including constraints/validations) as we build out
 support
diff --git a/src/dawn/native/SharedTextureMemory.cpp b/src/dawn/native/SharedTextureMemory.cpp
index 3c715bd..f37a65b 100644
--- a/src/dawn/native/SharedTextureMemory.cpp
+++ b/src/dawn/native/SharedTextureMemory.cpp
@@ -128,6 +128,8 @@
             this, ToAPI(Feature::SharedTextureMemoryAHardwareBuffer));
     }
 
+    DAWN_TRY(GetChainedProperties(unpacked));
+
     return {};
 }
 
diff --git a/src/dawn/native/SharedTextureMemory.h b/src/dawn/native/SharedTextureMemory.h
index 0455e01..1fb8b14 100644
--- a/src/dawn/native/SharedTextureMemory.h
+++ b/src/dawn/native/SharedTextureMemory.h
@@ -75,6 +75,11 @@
     virtual ResultOrError<Ref<TextureBase>> CreateTextureImpl(
         const UnpackedPtr<TextureDescriptor>& descriptor) = 0;
 
+    virtual MaybeError GetChainedProperties(
+        UnpackedPtr<SharedTextureMemoryProperties>& properties) const {
+        return {};
+    }
+
     SharedTextureMemoryProperties mProperties;
 };
 
diff --git a/src/dawn/native/vulkan/SharedTextureMemoryVk.cpp b/src/dawn/native/vulkan/SharedTextureMemoryVk.cpp
index fb817c8..5a592c0 100644
--- a/src/dawn/native/vulkan/SharedTextureMemoryVk.cpp
+++ b/src/dawn/native/vulkan/SharedTextureMemoryVk.cpp
@@ -511,6 +511,7 @@
     }
 
     VkFormat vkFormat;
+    YCbCrVkDescriptor yCbCrAHBInfo;
     VkAndroidHardwareBufferPropertiesANDROID bufferProperties = {
         .sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
     };
@@ -529,13 +530,30 @@
 
         vkFormat = bufferFormatProperties.format;
 
-        // TODO(dawn:1745): Support external formats.
-        // https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#memory-external-android-hardware-buffer-external-formats
-        DAWN_INVALID_IF(vkFormat == VK_FORMAT_UNDEFINED,
-                        "AHardwareBuffer did not have a supported format. External format (%u) "
-                        "requires YCbCr conversion and is "
-                        "not supported yet.",
-                        bufferFormatProperties.externalFormat);
+        // Populate the YCbCr info.
+        yCbCrAHBInfo.externalFormat = bufferFormatProperties.externalFormat;
+        yCbCrAHBInfo.vkFormat = bufferFormatProperties.format;
+        yCbCrAHBInfo.vkYCbCrModel = bufferFormatProperties.suggestedYcbcrModel;
+        yCbCrAHBInfo.vkYCbCrRange = bufferFormatProperties.suggestedYcbcrRange;
+        yCbCrAHBInfo.vkComponentSwizzleRed =
+            bufferFormatProperties.samplerYcbcrConversionComponents.r;
+        yCbCrAHBInfo.vkComponentSwizzleGreen =
+            bufferFormatProperties.samplerYcbcrConversionComponents.g;
+        yCbCrAHBInfo.vkComponentSwizzleBlue =
+            bufferFormatProperties.samplerYcbcrConversionComponents.b;
+        yCbCrAHBInfo.vkComponentSwizzleAlpha =
+            bufferFormatProperties.samplerYcbcrConversionComponents.a;
+        yCbCrAHBInfo.vkXChromaOffset = bufferFormatProperties.suggestedXChromaOffset;
+        yCbCrAHBInfo.vkYChromaOffset = bufferFormatProperties.suggestedYChromaOffset;
+
+        uint32_t formatFeatures = bufferFormatProperties.formatFeatures;
+        yCbCrAHBInfo.vkChromaFilter =
+            (formatFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT)
+                ? VK_FILTER_LINEAR
+                : VK_FILTER_NEAREST;
+        yCbCrAHBInfo.forceExplicitReconstruction =
+            formatFeatures &
+            VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_BIT;
     }
     DAWN_TRY_ASSIGN(properties.format, FormatFromVkFormat(device, vkFormat));
 
@@ -549,6 +567,8 @@
     Ref<SharedTextureMemory> sharedTextureMemory =
         SharedTextureMemory::Create(device, label, properties, VK_QUEUE_FAMILY_FOREIGN_EXT);
 
+    sharedTextureMemory->mYCbCrAHBInfo = yCbCrAHBInfo;
+
     // Reflect properties to reify them.
     sharedTextureMemory->APIGetProperties(&properties);
 
@@ -1050,4 +1070,23 @@
 
 #endif  // DAWN_PLATFORM_IS(FUCHSIA) || DAWN_PLATFORM_IS(LINUX)
 
+MaybeError SharedTextureMemory::GetChainedProperties(
+    UnpackedPtr<SharedTextureMemoryProperties>& properties) const {
+    auto ahbProperties = properties.Get<SharedTextureMemoryAHardwareBufferProperties>();
+
+    if (!ahbProperties) {
+        return {};
+    }
+
+    if (ahbProperties->yCbCrInfo.nextInChain) {
+        return DAWN_VALIDATION_ERROR(
+            "yCBCrInfo field of SharedTextureMemoryAHardwareBufferProperties has a chained "
+            "struct.");
+    }
+
+    ahbProperties->yCbCrInfo = mYCbCrAHBInfo;
+
+    return {};
+}
+
 }  // namespace dawn::native::vulkan
diff --git a/src/dawn/native/vulkan/SharedTextureMemoryVk.h b/src/dawn/native/vulkan/SharedTextureMemoryVk.h
index 5912f6e..c9e35cf 100644
--- a/src/dawn/native/vulkan/SharedTextureMemoryVk.h
+++ b/src/dawn/native/vulkan/SharedTextureMemoryVk.h
@@ -78,9 +78,15 @@
                                                      ExecutionSerial lastUsageSerial,
                                                      UnpackedPtr<EndAccessState>& state) override;
 
+    MaybeError GetChainedProperties(
+        UnpackedPtr<SharedTextureMemoryProperties>& properties) const override;
+
     Ref<RefCountedVkHandle<VkImage>> mVkImage;
     Ref<RefCountedVkHandle<VkDeviceMemory>> mVkDeviceMemory;
     const uint32_t mQueueFamilyIndex;
+
+    // Populated if this instance was created from an AHardwareBuffer.
+    YCbCrVkDescriptor mYCbCrAHBInfo;
 };
 
 }  // namespace dawn::native::vulkan
diff --git a/src/dawn/tests/white_box/SharedTextureMemoryTests_android.cpp b/src/dawn/tests/white_box/SharedTextureMemoryTests_android.cpp
index 6cbe0c4..3a122e0 100644
--- a/src/dawn/tests/white_box/SharedTextureMemoryTests_android.cpp
+++ b/src/dawn/tests/white_box/SharedTextureMemoryTests_android.cpp
@@ -26,12 +26,14 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include <android/hardware_buffer.h>
-#include <vulkan/vulkan.h>
 #include <memory>
 #include <string>
 #include <utility>
 #include <vector>
 
+#include "dawn/native/vulkan/DeviceVk.h"
+#include "dawn/native/vulkan/UtilsVulkan.h"
+#include "dawn/native/vulkan/VulkanError.h"
 #include "dawn/tests/white_box/SharedTextureMemoryTests.h"
 #include "dawn/utils/WGPUHelpers.h"
 #include "dawn/webgpu_cpp.h"
@@ -314,6 +316,110 @@
                       {aHardwareBufferDesc.width, aHardwareBufferDesc.height});
 }
 
+// Test validation of an incorrectly-configured SharedTextureMemoryAHardwareBufferProperties
+// instance.
+TEST_P(SharedTextureMemoryTests, InvalidSharedTextureMemoryAHardwareBufferProperties) {
+    AHardwareBuffer_Desc aHardwareBufferDesc = {
+        .width = 4,
+        .height = 4,
+        .layers = 1,
+        .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+    };
+    AHardwareBuffer* aHardwareBuffer;
+    EXPECT_EQ(AHardwareBuffer_allocate(&aHardwareBufferDesc, &aHardwareBuffer), 0);
+
+    wgpu::SharedTextureMemoryAHardwareBufferDescriptor stmAHardwareBufferDesc;
+    stmAHardwareBufferDesc.handle = aHardwareBuffer;
+
+    wgpu::SharedTextureMemoryDescriptor desc;
+    desc.nextInChain = &stmAHardwareBufferDesc;
+
+    wgpu::SharedTextureMemory memory = device.ImportSharedTextureMemory(&desc);
+
+    wgpu::SharedTextureMemoryProperties properties;
+    wgpu::SharedTextureMemoryAHardwareBufferProperties ahbProperties = {};
+    wgpu::YCbCrVkDescriptor yCbCrDesc;
+
+    // Chaining anything onto the passed-in YCbCrVkDescriptor is invalid.
+    yCbCrDesc.nextInChain = &stmAHardwareBufferDesc;
+    ahbProperties.yCbCrInfo = yCbCrDesc;
+    properties.nextInChain = &ahbProperties;
+
+    ASSERT_DEVICE_ERROR(memory.GetProperties(&properties));
+}
+
+// Test querying YCbCr info from the SharedTextureMemory.
+TEST_P(SharedTextureMemoryTests, QueryYCbCrInfo) {
+    AHardwareBuffer_Desc aHardwareBufferDesc = {
+        .width = 4,
+        .height = 4,
+        .layers = 1,
+        .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+    };
+    AHardwareBuffer* aHardwareBuffer;
+    EXPECT_EQ(AHardwareBuffer_allocate(&aHardwareBufferDesc, &aHardwareBuffer), 0);
+
+    // Query the YCbCr properties of the AHardwareBuffer.
+    auto deviceVk = native::vulkan::ToBackend(native::FromAPI(device.Get()));
+
+    VkAndroidHardwareBufferPropertiesANDROID bufferProperties = {
+        .sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
+    };
+
+    VkAndroidHardwareBufferFormatPropertiesANDROID bufferFormatProperties;
+    native::vulkan::PNextChainBuilder bufferPropertiesChain(&bufferProperties);
+    bufferPropertiesChain.Add(&bufferFormatProperties,
+                              VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID);
+
+    VkDevice vkDevice = deviceVk->GetVkDevice();
+    EXPECT_EQ(deviceVk->fn.GetAndroidHardwareBufferPropertiesANDROID(vkDevice, aHardwareBuffer,
+                                                                     &bufferProperties),
+              VK_SUCCESS);
+
+    // Query the YCbCr properties of a SharedTextureMemory created from this
+    // AHB.
+    wgpu::SharedTextureMemoryAHardwareBufferDescriptor stmAHardwareBufferDesc;
+    stmAHardwareBufferDesc.handle = aHardwareBuffer;
+
+    wgpu::SharedTextureMemoryDescriptor desc;
+    desc.nextInChain = &stmAHardwareBufferDesc;
+
+    wgpu::SharedTextureMemory memory = device.ImportSharedTextureMemory(&desc);
+
+    wgpu::SharedTextureMemoryProperties properties;
+    wgpu::SharedTextureMemoryAHardwareBufferProperties ahbProperties = {};
+    properties.nextInChain = &ahbProperties;
+    memory.GetProperties(&properties);
+    auto yCbCrInfo = ahbProperties.yCbCrInfo;
+    uint32_t formatFeatures = bufferFormatProperties.formatFeatures;
+
+    // Verify that the YCbCr properties match.
+    EXPECT_EQ(bufferFormatProperties.format, yCbCrInfo.vkFormat);
+    EXPECT_EQ(bufferFormatProperties.suggestedYcbcrModel, yCbCrInfo.vkYCbCrModel);
+    EXPECT_EQ(bufferFormatProperties.suggestedYcbcrRange, yCbCrInfo.vkYCbCrRange);
+    EXPECT_EQ(bufferFormatProperties.samplerYcbcrConversionComponents.r,
+              yCbCrInfo.vkComponentSwizzleRed);
+    EXPECT_EQ(bufferFormatProperties.samplerYcbcrConversionComponents.g,
+              yCbCrInfo.vkComponentSwizzleGreen);
+    EXPECT_EQ(bufferFormatProperties.samplerYcbcrConversionComponents.b,
+              yCbCrInfo.vkComponentSwizzleBlue);
+    EXPECT_EQ(bufferFormatProperties.samplerYcbcrConversionComponents.a,
+              yCbCrInfo.vkComponentSwizzleAlpha);
+    EXPECT_EQ(bufferFormatProperties.suggestedXChromaOffset, yCbCrInfo.vkXChromaOffset);
+    EXPECT_EQ(bufferFormatProperties.suggestedYChromaOffset, yCbCrInfo.vkYChromaOffset);
+
+    uint32_t expectedFilter =
+        (formatFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT)
+            ? VK_FILTER_LINEAR
+            : VK_FILTER_NEAREST;
+    EXPECT_EQ(expectedFilter, yCbCrInfo.vkChromaFilter);
+    EXPECT_EQ(
+        formatFeatures &
+            VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_BIT,
+        yCbCrInfo.forceExplicitReconstruction);
+    EXPECT_EQ(bufferFormatProperties.externalFormat, yCbCrInfo.externalFormat);
+}
+
 DAWN_INSTANTIATE_PREFIXED_TEST_P(Vulkan,
                                  SharedTextureMemoryNoFeatureTests,
                                  {VulkanBackend()},