blob: 77f79d87b3db2384638af7ab5b850d4812f13e30 [file] [log] [blame]
// Copyright 2026 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <android/hardware_buffer.h>
#include <webgpu/webgpu_cpp.h>
#include <span>
#include <utility>
#include <vector>
#include "dawn/common/Algebra.h"
#include "dawn/common/Assert.h"
#include "dawn/common/ColorSpace.h"
#include "dawn/common/Range.h"
#include "dawn/native/vulkan/DeviceVk.h"
#include "dawn/native/vulkan/UtilsVulkan.h"
#include "dawn/native/vulkan/VulkanError.h"
#include "dawn/tests/DawnTest.h"
#include "dawn/utils/ComboRenderPipelineDescriptor.h"
#include "dawn/utils/WGPUHelpers.h"
#include "vulkan/vulkan_core.h"
namespace dawn {
namespace {
// Make an AHB with the YCbCr data, note that Cb and Cr are a quarter the size of Y.
AHardwareBuffer* MakeY8Cb8Cr8AHB(uint32_t width,
uint32_t height,
std::span<const uint8_t> yData,
std::span<const uint8_t> cbData,
std::span<const uint8_t> crData) {
DAWN_ASSERT(width % 2 == 0 && height % 2 == 0);
AHardwareBuffer_Desc ahbDesc = {
.width = width,
.height = height,
.layers = 1,
.format = AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420,
.usage = AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY | AHARDWAREBUFFER_USAGE_CPU_READ_NEVER |
AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE,
};
AHardwareBuffer* ahb = nullptr;
EXPECT_EQ(AHardwareBuffer_allocate(&ahbDesc, &ahb), 0);
AHardwareBuffer_Planes ahbPlanes;
EXPECT_EQ(AHardwareBuffer_lockPlanes(ahb, AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY, -1, nullptr,
&ahbPlanes),
0);
auto CopyPlane = [](std::span<const uint8_t> src, uint32_t srcWidth, uint32_t srcHeight,
const AHardwareBuffer_Plane& dst) {
uint8_t* dstData = static_cast<uint8_t*>(dst.data);
for (uint32_t x : Range(srcWidth)) {
for (uint32_t y : Range(srcHeight)) {
dstData[y * dst.rowStride + x * dst.pixelStride] = src[y * srcWidth + x];
}
}
};
CopyPlane(yData, width, height, ahbPlanes.planes[0]);
CopyPlane(cbData, width / 2, height / 2, ahbPlanes.planes[1]);
CopyPlane(crData, width / 2, height / 2, ahbPlanes.planes[2]);
EXPECT_EQ(AHardwareBuffer_unlock(ahb, nullptr), 0);
return ahb;
}
class SharedTextureMemoryYCbCrVulkanSamplersTests : public DawnTest {
protected:
std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
return {wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer,
wgpu::FeatureName::SharedFenceSyncFD, wgpu::FeatureName::YCbCrVulkanSamplers};
}
};
// Test validation of an incorrectly-configured SharedTextureMemoryAHardwareBufferProperties
// instance.
TEST_P(SharedTextureMemoryYCbCrVulkanSamplersTests,
InvalidSharedTextureMemoryAHardwareBufferProperties) {
// TODO(crbug.com/444741058): Fails on Intel-based brya devices running Android Desktop.
DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsIntel() && IsAndroid());
AHardwareBuffer_Desc aHardwareBufferDesc = {
.width = 4,
.height = 4,
.layers = 1,
.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
.usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE,
};
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 Device.
TEST_P(SharedTextureMemoryYCbCrVulkanSamplersTests, QueryYCbCrInfoFromDevice) {
// TODO(crbug.com/444741058): Fails on Intel-based brya devices running Android Desktop.
DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsIntel() && IsAndroid());
AHardwareBuffer_Desc aHardwareBufferDesc = {
.width = 4,
.height = 4,
.layers = 1,
.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
.usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE,
};
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(
bool(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 without external format.
TEST_P(SharedTextureMemoryYCbCrVulkanSamplersTests, QueryYCbCrInfoWithoutYCbCrFormat) {
// TODO(crbug.com/444741058): Fails on Intel-based brya devices running Android Desktop.
DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsIntel() && IsAndroid());
AHardwareBuffer_Desc aHardwareBufferDesc = {
.width = 4,
.height = 4,
.layers = 1,
.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
.usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE,
};
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);
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(
bool(formatFeatures &
VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_BIT),
yCbCrInfo.forceExplicitReconstruction);
uint64_t expectedExternalFormat = 0u;
EXPECT_EQ(expectedExternalFormat, yCbCrInfo.externalFormat);
}
// Test querying YCbCr info from the SharedTextureMemory with external format.
TEST_P(SharedTextureMemoryYCbCrVulkanSamplersTests, QueryYCbCrInfoWithExternalFormat) {
AHardwareBuffer_Desc aHardwareBufferDesc = {
.width = 4,
.height = 4,
.layers = 1,
.format = AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420,
.usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE,
};
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.
VkFormat expectedVkFormat = VK_FORMAT_UNDEFINED;
EXPECT_EQ(expectedVkFormat, 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(
bool(formatFeatures &
VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_BIT),
yCbCrInfo.forceExplicitReconstruction);
EXPECT_EQ(bufferFormatProperties.externalFormat, yCbCrInfo.externalFormat);
}
// Test BeginAccess on an uninitialized texture with external format fails.
TEST_P(SharedTextureMemoryYCbCrVulkanSamplersTests,
GPUReadForUninitializedTextureWithExternalFormatFails) {
const AHardwareBuffer_Desc aHardwareBufferDesc = {
.width = 4,
.height = 4,
.layers = 1,
.format = AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420,
.usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE,
};
AHardwareBuffer* aHardwareBuffer;
EXPECT_EQ(AHardwareBuffer_allocate(&aHardwareBufferDesc, &aHardwareBuffer), 0);
wgpu::SharedTextureMemoryAHardwareBufferDescriptor stmAHardwareBufferDesc;
stmAHardwareBufferDesc.handle = aHardwareBuffer;
wgpu::SharedTextureMemoryDescriptor desc;
desc.nextInChain = &stmAHardwareBufferDesc;
const wgpu::SharedTextureMemory memory = device.ImportSharedTextureMemory(&desc);
wgpu::TextureDescriptor descriptor;
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.size.width = 4;
descriptor.size.height = 4;
descriptor.size.depthOrArrayLayers = 1u;
descriptor.sampleCount = 1u;
descriptor.format = wgpu::TextureFormat::OpaqueYCbCrAndroid;
descriptor.mipLevelCount = 1u;
descriptor.usage = wgpu::TextureUsage::TextureBinding;
auto texture = memory.CreateTexture(&descriptor);
AHardwareBuffer_release(aHardwareBuffer);
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.initialized = false;
wgpu::SharedTextureMemoryVkImageLayoutBeginState beginLayout{};
beginDesc.nextInChain = &beginLayout;
ASSERT_DEVICE_ERROR(memory.BeginAccess(texture, &beginDesc));
}
DAWN_INSTANTIATE_TEST(SharedTextureMemoryYCbCrVulkanSamplersTests, VulkanBackend());
// Define a test parameter struct to check all combinations of Vulkan YCbCr sampler models. Use enum
// class so that the ostream overloads match, otherwise testnames just contain the value of the
// Vulkan enum and not its name.
enum class VkYCbCrModel { YCbCrIdentity, RGBIdentity, Rec601, Rec709, Rec2020 };
std::ostream& operator<<(std::ostream& o, VkYCbCrModel model) {
switch (model) {
case VkYCbCrModel::YCbCrIdentity:
o << "ycbcrIdentity";
break;
case VkYCbCrModel::RGBIdentity:
o << "rgbIdentity";
break;
case VkYCbCrModel::Rec601:
o << "601";
break;
case VkYCbCrModel::Rec709:
o << "709";
break;
case VkYCbCrModel::Rec2020:
o << "2020";
break;
}
return o;
}
VkSamplerYcbcrModelConversion ToVk(VkYCbCrModel model) {
switch (model) {
case VkYCbCrModel::RGBIdentity:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY;
case VkYCbCrModel::YCbCrIdentity:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY;
case VkYCbCrModel::Rec601:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601;
case VkYCbCrModel::Rec709:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709;
case VkYCbCrModel::Rec2020:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020;
}
}
enum class VkYCbCrRange { Full, Narrow };
std::ostream& operator<<(std::ostream& o, VkYCbCrRange range) {
switch (range) {
case VkYCbCrRange::Full:
o << "full";
break;
case VkYCbCrRange::Narrow:
o << "narrow";
break;
}
return o;
}
VkSamplerYcbcrRange ToVk(VkYCbCrRange range) {
switch (range) {
case VkYCbCrRange::Full:
return VK_SAMPLER_YCBCR_RANGE_ITU_FULL;
case VkYCbCrRange::Narrow:
return VK_SAMPLER_YCBCR_RANGE_ITU_NARROW;
}
}
DAWN_TEST_PARAM_STRUCT(VkYCbCrParams, VkYCbCrModel, VkYCbCrRange);
class SharedTextureMemoryVulkanYCbCrParamsTests : public DawnTestWithParams<VkYCbCrParams> {
protected:
std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
return {wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer,
wgpu::FeatureName::SharedFenceSyncFD, wgpu::FeatureName::StaticSamplers,
wgpu::FeatureName::YCbCrVulkanSamplers};
}
math::Mat4x3f ComputeYCbCrToRGB() {
// Vulkan has CrYCb (because Cr is "red", Y is "green" and Cb is "blue") but we want YCbCr.
// Stay in 4D to undo the swizzle as it is before the application of the range, but have the
// redo in 3D because it is after the range is applied.
constexpr math::Mat4x4f kUndoVulkanSwizzle = {
{0, 0, 1, 0},
{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 0, 1},
};
constexpr math::Mat3x3f kRedoVulkanSwizzle = {
{0, 1, 0},
{0, 0, 1},
{1, 0, 0},
};
math::Mat4x3f rangeTransform;
switch (GetParam().mVkYCbCrRange) {
case VkYCbCrRange::Full:
rangeTransform = kYCbCrRange_Full;
break;
case VkYCbCrRange::Narrow:
rangeTransform = kYCbCrRange_Narrow;
break;
}
math::Mat3x3f ycbcrToRgb;
switch (GetParam().mVkYCbCrModel) {
case VkYCbCrModel::RGBIdentity:
// Directly return without multiplying with the range transform as that's support to
// be what Vulkan does with RGB_IDENTITY. Sampled YCbCr values are returned raw.
return math::Mat4x3f::CropOrExpandFrom(math::Mat3x3f::Identity());
case VkYCbCrModel::YCbCrIdentity:
// Redo the swizzle since no YCbCr to RGB conversion happens and Vulkan will have
// CrYCb.
ycbcrToRgb = kRedoVulkanSwizzle;
break;
case VkYCbCrModel::Rec601:
ycbcrToRgb = kYCbCrToRGB_Rec601;
break;
case VkYCbCrModel::Rec709:
ycbcrToRgb = kYCbCrToRGB_Rec709;
break;
case VkYCbCrModel::Rec2020:
ycbcrToRgb = kYCbCrToRGB_Rec2020;
break;
}
return Mul(ycbcrToRgb, Mul(rangeTransform, kUndoVulkanSwizzle));
}
};
TEST_P(SharedTextureMemoryVulkanYCbCrParamsTests, SampleY8Cb8Cr8AHB) {
// Make the AHB and import it as an STM and wgpu::Texture.
const std::array<uint8_t, 4> yData = {50, 100, 150, 200};
const std::array<uint8_t, 1> cbData = {130};
const std::array<uint8_t, 1> crData = {140};
AHardwareBuffer* ahb = MakeY8Cb8Cr8AHB(2, 2, yData, cbData, crData);
wgpu::SharedTextureMemoryAHardwareBufferDescriptor stmAHardwareBufferDesc{};
stmAHardwareBufferDesc.handle = ahb;
wgpu::SharedTextureMemoryDescriptor stmDesc{};
stmDesc.nextInChain = &stmAHardwareBufferDesc;
wgpu::SharedTextureMemory stm = device.ImportSharedTextureMemory(&stmDesc);
wgpu::Texture ycbcrTex = stm.CreateTexture();
AHardwareBuffer_release(ahb);
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc{};
beginDesc.initialized = true;
wgpu::SharedTextureMemoryVkImageLayoutBeginState beginLayout{};
beginDesc.nextInChain = &beginLayout;
EXPECT_EQ(stm.BeginAccess(ycbcrTex, &beginDesc), wgpu::Status::Success);
// Get the YCbCrVkDescriptor
wgpu::SharedTextureMemoryProperties stmProperties{};
wgpu::SharedTextureMemoryAHardwareBufferProperties ahbProperties{};
stmProperties.nextInChain = &ahbProperties;
stm.GetProperties(&stmProperties);
wgpu::YCbCrVkDescriptor yCbCrDesc = ahbProperties.yCbCrInfo;
// Override the YCbCr descriptor to force it to use the YCbCr model and range in test params.
yCbCrDesc.vkYCbCrModel = ToVk(GetParam().mVkYCbCrModel);
yCbCrDesc.vkYCbCrRange = ToVk(GetParam().mVkYCbCrRange);
yCbCrDesc.vkComponentSwizzleRed = VK_COMPONENT_SWIZZLE_R;
yCbCrDesc.vkComponentSwizzleGreen = VK_COMPONENT_SWIZZLE_G;
yCbCrDesc.vkComponentSwizzleBlue = VK_COMPONENT_SWIZZLE_B;
yCbCrDesc.vkComponentSwizzleAlpha = VK_COMPONENT_SWIZZLE_A;
yCbCrDesc.vkChromaFilter = wgpu::FilterMode::Nearest;
// Create the BindGroupLayout with a static YCbCr sampler, and the BindGroup.
wgpu::SamplerDescriptor samplerDesc;
samplerDesc.nextInChain = &yCbCrDesc;
wgpu::StaticSamplerBindingLayout staticSamplerBinding{};
staticSamplerBinding.sampler = device.CreateSampler(&samplerDesc);
staticSamplerBinding.sampledTextureBinding = 1;
std::array<wgpu::BindGroupLayoutEntry, 2> bglEntries;
bglEntries[0].binding = 0;
bglEntries[0].visibility = wgpu::ShaderStage::Fragment;
bglEntries[0].nextInChain = &staticSamplerBinding;
bglEntries[1].binding = 1;
bglEntries[1].visibility = wgpu::ShaderStage::Fragment;
bglEntries[1].texture.sampleType = wgpu::TextureSampleType::UnfilterableFloat;
wgpu::BindGroupLayoutDescriptor bglDesc{
.entryCount = bglEntries.size(),
.entries = bglEntries.data(),
};
wgpu::BindGroupLayout bgl = device.CreateBindGroupLayout(&bglDesc);
wgpu::TextureViewDescriptor viewDesc;
viewDesc.nextInChain = &yCbCrDesc;
wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{1, ycbcrTex.CreateView(&viewDesc)}});
// Create the pipeline that copies from the YCbCr texture to an RGBA one.
wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
@vertex fn quad(@builtin(vertex_index) i : u32) -> @builtin(position) vec4f {
const pos = array(
vec2f(-1.0, -1.0),
vec2f( 3.0, -1.0),
vec2f(-1.0, 3.0));
return vec4f(pos[i], 0.0, 1.0);
}
@group(0) @binding(0) var s : sampler;
@group(0) @binding(1) var t : texture_2d<f32>;
@fragment fn fs(@builtin(position) pos : vec4f) -> @location(0) vec4f {
return textureSample(t, s, pos.xy / 2);
}
)");
utils::ComboRenderPipelineDescriptor pDesc;
pDesc.vertex.module = module;
pDesc.cFragment.module = module;
pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
pDesc.layout = utils::MakePipelineLayout(device, {bgl});
wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pDesc);
// Do the copy.
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::BasicRenderPass renderPass =
utils::CreateBasicRenderPass(device, 2, 2, wgpu::TextureFormat::RGBA8Unorm);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bg);
pass.Draw(3);
pass.End();
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
// Replicate the computation that should be done by the YCbCr vulkan sampler and check that the
// copied data is within a small tolerance.
math::Mat4x3f ycbcrToRgb = ComputeYCbCrToRGB();
auto CheckPixel = [&](uint8_t y, uint8_t cb, uint8_t cr, uint32_t pixelX, uint32_t pixelY) {
auto ycbcr = math::Vec4f(cr / 255.0, y / 255.0, cb / 255.0, 1.0);
auto rgb = math::Mul(ycbcrToRgb, ycbcr);
auto expected = utils::RGBA8(rgb[0] * 255, rgb[1] * 255, rgb[2] * 255, 255);
auto bottom = utils::RGBA8(expected.r - 1, expected.g - 1, expected.b - 1, 255);
auto top = utils::RGBA8(expected.r + 1, expected.g + 1, expected.b + 1, 255);
EXPECT_PIXEL_RGBA8_BETWEEN(bottom, top, renderPass.color, pixelX, pixelY);
};
CheckPixel(yData[0], cbData[0], crData[0], 0, 0);
CheckPixel(yData[1], cbData[0], crData[0], 1, 0);
CheckPixel(yData[2], cbData[0], crData[0], 0, 1);
CheckPixel(yData[3], cbData[0], crData[0], 1, 1);
}
DAWN_INSTANTIATE_TEST_P(SharedTextureMemoryVulkanYCbCrParamsTests,
{VulkanBackend()},
{
VkYCbCrModel::RGBIdentity,
VkYCbCrModel::YCbCrIdentity,
VkYCbCrModel::Rec601,
VkYCbCrModel::Rec709,
VkYCbCrModel::Rec2020,
},
{VkYCbCrRange::Full, VkYCbCrRange::Narrow});
} // anonymous namespace
} // namespace dawn