[Android] Add Device::GetAHardwareBufferProperties()

This CL adds Device::GetAHardwareBufferProperties(), which will allow
getting the properties of an AHardwareBuffer (notably the YCbCr info)
without having to first create a SharedTextureMemory instance. This is
useful because it turns out that when using external sampling, it will
be necessary to configure the SharedTextureMemory at the time of its
creation so that it correctly creates its VkImage. Having this API on
Device allows clients to determine whether they want to use external
sampling with a given AHB before creating the STM for that AHB.

Note:

* I didn't bother deduping the implementation with that of
  SharedTextureMemory as once this new API is in place Chromium will
  transition to using it entirely and we can remove obtaining the AHB
  properties from SharedTextureMemory

Change-Id: Iaa0b6ad166bb6b7c668acce2336863dd021fe8a1
Bug: dawn:2476
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/190140
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Colin Blundell <blundell@chromium.org>
diff --git a/src/dawn/dawn.json b/src/dawn/dawn.json
index be35ce2..84f8a70 100644
--- a/src/dawn/dawn.json
+++ b/src/dawn/dawn.json
@@ -1247,6 +1247,13 @@
             {"value": 3, "name": "back"}
         ]
     },
+    "a hardware buffer properties": {
+        "category": "structure",
+        "tags": ["dawn", "native"],
+        "members": [
+            {"name": "y cb cr info", "type": "y cb cr vk descriptor"}
+        ]
+    },
     "device": {
         "category": "object",
         "methods": [
@@ -1471,6 +1478,15 @@
                 "name": "destroy"
             },
             {
+                "name": "get a hardware buffer properties",
+                "tags": ["dawn", "native"],
+                "returns": "status",
+                "args": [
+                    {"name": "handle", "type": "void *"},
+                    {"name": "properties", "type": "a hardware buffer properties", "annotation": "*"}
+                ]
+            },
+            {
                 "name": "get limits",
                 "returns": "status",
                 "args": [
@@ -3925,7 +3941,8 @@
             {"value": 1215, "name": "shared buffer memory D3D12 resource descriptor", "tags": ["dawn", "native"]},
             {"value": 1216, "name": "static sampler binding layout", "tags": ["dawn"]},
             {"value": 1217, "name": "y cb cr vk descriptor", "tags": ["dawn"]},
-            {"value": 1218, "name": "shared texture memory a hardware buffer properties", "tags": ["dawn", "native"]}
+            {"value": 1218, "name": "shared texture memory a hardware buffer properties", "tags": ["dawn", "native"]},
+            {"value": 1219, "name": "a hardware buffer properties", "tags": ["dawn", "native"]}
         ]
     },
     "texture": {
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index 35e773d..50da2b0 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -1902,6 +1902,23 @@
     }
 }
 
+wgpu::Status DeviceBase::APIGetAHardwareBufferProperties(void* handle,
+                                                         AHardwareBufferProperties* properties) {
+    if (!HasFeature(Feature::SharedTextureMemoryAHardwareBuffer)) {
+        ConsumeError(
+            DAWN_VALIDATION_ERROR("Queried APIGetAHardwareBufferProperties() on %s "
+                                  "without the %s feature being set.",
+                                  this, ToAPI(Feature::SharedTextureMemoryAHardwareBuffer)));
+        return wgpu::Status::Error;
+    }
+
+    if (ConsumedError(GetAHardwareBufferPropertiesImpl(handle, properties))) {
+        return wgpu::Status::Error;
+    }
+
+    return wgpu::Status::Success;
+}
+
 wgpu::Status DeviceBase::APIGetLimits(SupportedLimits* limits) const {
     DAWN_ASSERT(limits != nullptr);
     InstanceBase* instance = GetAdapter()->GetInstance();
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index 3880a5d..e36cac7 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -294,6 +294,8 @@
     AdapterBase* APIGetAdapter();
     QueueBase* APIGetQueue();
 
+    wgpu::Status APIGetAHardwareBufferProperties(void* handle,
+                                                 AHardwareBufferProperties* properties);
     wgpu::Status APIGetLimits(SupportedLimits* limits) const;
     bool APIHasFeature(wgpu::FeatureName feature) const;
     size_t APIEnumerateFeatures(wgpu::FeatureName* features) const;
@@ -465,6 +467,12 @@
     void DestroyObjects();
     void Destroy();
 
+    virtual MaybeError GetAHardwareBufferPropertiesImpl(
+        void* handle,
+        AHardwareBufferProperties* properties) const {
+        DAWN_UNREACHABLE();
+    }
+
     // Device lost event needs to be protected for now because mock device needs it.
     // TODO(dawn:1702) Make this private and move the class in the implementation file when we mock
     // the adapter.
diff --git a/src/dawn/native/vulkan/DeviceVk.cpp b/src/dawn/native/vulkan/DeviceVk.cpp
index 73285c4..37d0af2 100644
--- a/src/dawn/native/vulkan/DeviceVk.cpp
+++ b/src/dawn/native/vulkan/DeviceVk.cpp
@@ -902,6 +902,56 @@
     CheckDebugMessagesAfterDestruction();
 }
 
+MaybeError Device::GetAHardwareBufferPropertiesImpl(void* handle,
+                                                    AHardwareBufferProperties* properties) const {
+#if DAWN_PLATFORM_IS(ANDROID)
+    auto* aHardwareBuffer = static_cast<struct AHardwareBuffer*>(handle);
+
+    VkAndroidHardwareBufferPropertiesANDROID bufferProperties = {
+        .sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
+    };
+
+    // Query the properties to find the appropriate VkFormat and memory type.
+    VkAndroidHardwareBufferFormatPropertiesANDROID bufferFormatProperties;
+    PNextChainBuilder bufferPropertiesChain(&bufferProperties);
+    bufferPropertiesChain.Add(&bufferFormatProperties,
+                              VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID);
+
+    DAWN_TRY(CheckVkSuccess(fn.GetAndroidHardwareBufferPropertiesANDROID(
+                                GetVkDevice(), aHardwareBuffer, &bufferProperties),
+                            "vkGetAndroidHardwareBufferPropertiesANDROID"));
+
+    // Populate the YCbCr info.
+    properties->yCbCrInfo.externalFormat = bufferFormatProperties.externalFormat;
+    properties->yCbCrInfo.vkFormat = bufferFormatProperties.format;
+    properties->yCbCrInfo.vkYCbCrModel = bufferFormatProperties.suggestedYcbcrModel;
+    properties->yCbCrInfo.vkYCbCrRange = bufferFormatProperties.suggestedYcbcrRange;
+    properties->yCbCrInfo.vkComponentSwizzleRed =
+        bufferFormatProperties.samplerYcbcrConversionComponents.r;
+    properties->yCbCrInfo.vkComponentSwizzleGreen =
+        bufferFormatProperties.samplerYcbcrConversionComponents.g;
+    properties->yCbCrInfo.vkComponentSwizzleBlue =
+        bufferFormatProperties.samplerYcbcrConversionComponents.b;
+    properties->yCbCrInfo.vkComponentSwizzleAlpha =
+        bufferFormatProperties.samplerYcbcrConversionComponents.a;
+    properties->yCbCrInfo.vkXChromaOffset = bufferFormatProperties.suggestedXChromaOffset;
+    properties->yCbCrInfo.vkYChromaOffset = bufferFormatProperties.suggestedYChromaOffset;
+
+    uint32_t formatFeatures = bufferFormatProperties.formatFeatures;
+    properties->yCbCrInfo.vkChromaFilter =
+        (formatFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT)
+            ? wgpu::FilterMode::Linear
+            : wgpu::FilterMode::Nearest;
+    properties->yCbCrInfo.forceExplicitReconstruction =
+        formatFeatures &
+        VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_BIT;
+
+    return {};
+#else
+    return DeviceBase::GetAHardwareBufferPropertiesImpl(handle, properties);
+#endif
+}
+
 uint32_t Device::GetOptimalBytesPerRowAlignment() const {
     return mDeviceInfo.properties.limits.optimalBufferCopyRowPitchAlignment;
 }
diff --git a/src/dawn/native/vulkan/DeviceVk.h b/src/dawn/native/vulkan/DeviceVk.h
index 8e3c4a5..5635717 100644
--- a/src/dawn/native/vulkan/DeviceVk.h
+++ b/src/dawn/native/vulkan/DeviceVk.h
@@ -177,6 +177,8 @@
     void CheckDebugMessagesAfterDestruction() const;
 
     void DestroyImpl() override;
+    MaybeError GetAHardwareBufferPropertiesImpl(void* handle, AHardwareBufferProperties* properties)
+        const override;
 
     // To make it easier to use fn it is a public const member. However
     // the Device is allowed to mutate them through these private methods.
diff --git a/src/dawn/tests/white_box/SharedTextureMemoryTests_android.cpp b/src/dawn/tests/white_box/SharedTextureMemoryTests_android.cpp
index 72f4d11..17d3533 100644
--- a/src/dawn/tests/white_box/SharedTextureMemoryTests_android.cpp
+++ b/src/dawn/tests/white_box/SharedTextureMemoryTests_android.cpp
@@ -348,6 +348,67 @@
     ASSERT_DEVICE_ERROR(memory.GetProperties(&properties));
 }
 
+// Test querying YCbCr info from the Device.
+TEST_P(SharedTextureMemoryTests, QueryYCbCrInfoFromDevice) {
+    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 this AHB via the Device.
+    wgpu::AHardwareBufferProperties ahbProperties;
+    device.GetAHardwareBufferProperties(aHardwareBuffer, &ahbProperties);
+    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);
+
+    wgpu::FilterMode expectedFilter =
+        (formatFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT)
+            ? wgpu::FilterMode::Linear
+            : wgpu::FilterMode::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);
+}
+
 // Test querying YCbCr info from the SharedTextureMemory.
 TEST_P(SharedTextureMemoryTests, QueryYCbCrInfo) {
     AHardwareBuffer_Desc aHardwareBufferDesc = {