Added MultiPlanarFormatExtendedUsages feature.
This feature allows:
- Creating multiplanar textures without importing external image.
- Including CopyDst as texture's usage.
- Copy per-plane data between texture and buffer.
In future this feature would allow:
- Using the texture as render target.
- Copy per-plane data between textures.
This is useful for Chrome to be able to use a multiplanar shared image
with SwiftShader. Chrome emulates the support by creating "shadow"
WGPUTexture to copy/from the shared image. The WGPUTexture needs to be
multiplanar formatted.
Bug: dawn:1923
Bug: chromium:1467566
Change-Id: I00674c41a59cb3b39a39194520c470662efd56ae
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/148000
Reviewed-by: Austin Eng <enga@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Quyen Le <lehoangquyen@chromium.org>
diff --git a/dawn.json b/dawn.json
index afa734e..378a3e9 100644
--- a/dawn.json
+++ b/dawn.json
@@ -1862,6 +1862,7 @@
{"value": 1017, "name": "pixel local storage coherent", "tags": ["dawn"]},
{"value": 1018, "name": "pixel local storage non coherent", "tags": ["dawn"]},
{"value": 1019, "name": "norm16 texture formats", "tags": ["dawn"]},
+ {"value": 1020, "name": "multi planar format extended usages", "tags": ["dawn"]},
{"value": 1100, "name": "shared texture memory vk dedicated allocation", "tags": ["dawn", "native"]},
{"value": 1101, "name": "shared texture memory a hardware buffer", "tags": ["dawn", "native"]},
diff --git a/src/dawn/native/CommandValidation.cpp b/src/dawn/native/CommandValidation.cpp
index 7d30e42..26e7899 100644
--- a/src/dawn/native/CommandValidation.cpp
+++ b/src/dawn/native/CommandValidation.cpp
@@ -400,8 +400,9 @@
ASSERT(format.aspects & Aspect::Stencil);
return Aspect::Stencil;
case wgpu::TextureAspect::Plane0Only:
+ return Aspect::Plane0;
case wgpu::TextureAspect::Plane1Only:
- break;
+ return Aspect::Plane1;
}
UNREACHABLE();
}
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index b57bf19..d441d66 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -1525,7 +1525,13 @@
}
void DeviceBase::APIValidateTextureDescriptor(const TextureDescriptor* desc) {
- DAWN_UNUSED(ConsumedError(ValidateTextureDescriptor(this, desc)));
+ AllowMultiPlanarTextureFormat allowMultiPlanar;
+ if (HasFeature(Feature::MultiPlanarFormatExtendedUsages)) {
+ allowMultiPlanar = AllowMultiPlanarTextureFormat::Yes;
+ } else {
+ allowMultiPlanar = AllowMultiPlanarTextureFormat::No;
+ }
+ DAWN_UNUSED(ConsumedError(ValidateTextureDescriptor(this, desc, allowMultiPlanar)));
}
QueueBase* DeviceBase::GetQueue() const {
@@ -1806,7 +1812,14 @@
ResultOrError<Ref<TextureBase>> DeviceBase::CreateTexture(const TextureDescriptor* descriptor) {
DAWN_TRY(ValidateIsAlive());
if (IsValidationEnabled()) {
- DAWN_TRY_CONTEXT(ValidateTextureDescriptor(this, descriptor), "validating %s.", descriptor);
+ AllowMultiPlanarTextureFormat allowMultiPlanar;
+ if (HasFeature(Feature::MultiPlanarFormatExtendedUsages)) {
+ allowMultiPlanar = AllowMultiPlanarTextureFormat::Yes;
+ } else {
+ allowMultiPlanar = AllowMultiPlanarTextureFormat::No;
+ }
+ DAWN_TRY_CONTEXT(ValidateTextureDescriptor(this, descriptor, allowMultiPlanar),
+ "validating %s.", descriptor);
}
return CreateTextureImpl(descriptor);
}
diff --git a/src/dawn/native/Features.cpp b/src/dawn/native/Features.cpp
index 323d9e5..77b396f 100644
--- a/src/dawn/native/Features.cpp
+++ b/src/dawn/native/Features.cpp
@@ -108,6 +108,11 @@
{Feature::DawnMultiPlanarFormats,
{"Import and use multi-planar texture formats with per plane views",
"https://bugs.chromium.org/p/dawn/issues/detail?id=551", FeatureInfo::FeatureState::Stable}},
+ {Feature::MultiPlanarFormatExtendedUsages,
+ {"Enable creating multi-planar formatted textures directly without importing. Also allows "
+ "including CopyDst as texture's usage and per plane copies between a texture and a buffer.",
+ "https://bugs.chromium.org/p/dawn/issues/detail?id=551",
+ FeatureInfo::FeatureState::Experimental}},
{Feature::DawnNative,
{"WebGPU is running on top of dawn_native.",
"https://dawn.googlesource.com/dawn/+/refs/heads/main/docs/dawn/features/"
diff --git a/src/dawn/native/Format.cpp b/src/dawn/native/Format.cpp
index 9489d24..065e939 100644
--- a/src/dawn/native/Format.cpp
+++ b/src/dawn/native/Format.cpp
@@ -139,6 +139,40 @@
return aspectInfo[aspectIndex];
}
+Extent3D Format::GetAspectSize(Aspect aspect, const Extent3D& textureSize) const {
+ switch (aspect) {
+ case Aspect::Color:
+ case Aspect::Depth:
+ case Aspect::Stencil:
+ case Aspect::CombinedDepthStencil:
+ return textureSize;
+ case Aspect::Plane0:
+ ASSERT(IsMultiPlanar());
+ return textureSize;
+ case Aspect::Plane1: {
+ ASSERT(IsMultiPlanar());
+ auto planeSize = textureSize;
+ switch (format) {
+ case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
+ if (planeSize.width > 1) {
+ planeSize.width >>= 1;
+ }
+ if (planeSize.height > 1) {
+ planeSize.height >>= 1;
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+ return planeSize;
+ }
+ case Aspect::None:
+ break;
+ }
+
+ UNREACHABLE();
+}
+
FormatIndex Format::GetIndex() const {
return ComputeFormatIndex(format);
}
diff --git a/src/dawn/native/Format.h b/src/dawn/native/Format.h
index 96ff348..d571c2f 100644
--- a/src/dawn/native/Format.h
+++ b/src/dawn/native/Format.h
@@ -153,6 +153,9 @@
// Currently means they differ only in sRGB-ness.
bool ViewCompatibleWith(const Format& otherFormat) const;
+ // Returns the aspect's size given the texture's size.
+ Extent3D GetAspectSize(Aspect aspect, const Extent3D& textureSize) const;
+
private:
// Used to store the aspectInfo for one or more planes. For single plane "color" formats,
// only the first aspect info or aspectInfo[0] is valid. For depth-stencil, the first aspect
diff --git a/src/dawn/native/Texture.cpp b/src/dawn/native/Texture.cpp
index 0de3436..b560856 100644
--- a/src/dawn/native/Texture.cpp
+++ b/src/dawn/native/Texture.cpp
@@ -361,9 +361,12 @@
// Legacy path
// TODO(crbug.com/dawn/1795): Remove after migrating all old usages.
// Only allows simple readonly texture usages.
- constexpr wgpu::TextureUsage kValidMultiPlanarUsages =
+ wgpu::TextureUsage validMultiPlanarUsages =
wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopySrc;
- DAWN_INVALID_IF(format->IsMultiPlanar() && !IsSubset(usage, kValidMultiPlanarUsages),
+ if (device->HasFeature(Feature::MultiPlanarFormatExtendedUsages)) {
+ validMultiPlanarUsages |= wgpu::TextureUsage::CopyDst;
+ }
+ DAWN_INVALID_IF(format->IsMultiPlanar() && !IsSubset(usage, validMultiPlanarUsages),
"The texture usage (%s) is incompatible with the multi-planar format (%s).",
usage, format->format);
} else {
diff --git a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
index fae26ae..ea7fd26 100644
--- a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
+++ b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
@@ -317,6 +317,7 @@
if (allMultiplanarFormatsSupported) {
EnableFeature(Feature::DawnMultiPlanarFormats);
+ EnableFeature(Feature::MultiPlanarFormatExtendedUsages);
}
EnableFeature(Feature::SurfaceCapabilities);
diff --git a/src/dawn/native/vulkan/TextureVk.cpp b/src/dawn/native/vulkan/TextureVk.cpp
index 588bc1a..09cd9c5 100644
--- a/src/dawn/native/vulkan/TextureVk.cpp
+++ b/src/dawn/native/vulkan/TextureVk.cpp
@@ -709,7 +709,7 @@
VkImageFormatListCreateInfo imageFormatListInfo = {};
std::vector<VkFormat> viewFormats;
- bool requiresCreateMutableFormatBit = GetViewFormats().any();
+ bool requiresViewFormatsList = GetViewFormats().any();
// As current SPIR-V SPEC doesn't support 'bgra8' as a valid image format, to support the
// STORAGE usage of BGRA8Unorm we have to create an RGBA8Unorm image view on the BGRA8Unorm
// storage texture and polyfill it as RGBA8Unorm in Tint. See http://crbug.com/dawn/1641 for
@@ -717,10 +717,19 @@
if (createInfo.format == VK_FORMAT_B8G8R8A8_UNORM &&
createInfo.usage & VK_IMAGE_USAGE_STORAGE_BIT) {
viewFormats.push_back(VK_FORMAT_R8G8B8A8_UNORM);
- requiresCreateMutableFormatBit = true;
+ requiresViewFormatsList = true;
}
- if (requiresCreateMutableFormatBit) {
+ if (GetFormat().IsMultiPlanar() || requiresViewFormatsList) {
+ // Multi-planar image needs to have VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT in order to be able
+ // to create per-plane view. See
+ // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCreateFlagBits.html
+ //
+ // Note: we cannot include R8 & RG8 in the viewFormats list of
+ // G8_B8R8_2PLANE_420_UNORM. The Vulkan validation layer will disallow that.
createInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
+ }
+
+ if (requiresViewFormatsList) {
if (device->GetDeviceInfo().HasExt(DeviceExt::ImageFormatList)) {
createInfoChain.Add(&imageFormatListInfo,
VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO);
@@ -1272,15 +1281,17 @@
imageRange.levelCount = 1;
imageRange.layerCount = 1;
- if (GetFormat().isCompressed) {
+ if (GetFormat().isCompressed || GetFormat().IsMultiPlanar()) {
if (range.aspects == Aspect::None) {
return {};
}
// need to clear the texture with a copy from buffer
- ASSERT(range.aspects == Aspect::Color);
+ ASSERT(range.aspects == Aspect::Color || range.aspects == Aspect::Plane0 ||
+ range.aspects == Aspect::Plane1);
const TexelBlockInfo& blockInfo = GetFormat().GetAspectInfo(range.aspects).block;
Extent3D largestMipSize = GetMipLevelSingleSubresourcePhysicalSize(range.baseMipLevel);
+ largestMipSize = GetFormat().GetAspectSize(range.aspects, largestMipSize);
uint32_t bytesPerRow = Align((largestMipSize.width / blockInfo.width) * blockInfo.byteSize,
device->GetOptimalBytesPerRowAlignment());
@@ -1297,6 +1308,7 @@
for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount;
++level) {
Extent3D copySize = GetMipLevelSingleSubresourcePhysicalSize(level);
+ copySize = GetFormat().GetAspectSize(range.aspects, copySize);
imageRange.baseMipLevel = level;
for (uint32_t layer = range.baseArrayLayer;
layer < range.baseArrayLayer + range.layerCount; ++layer) {
diff --git a/src/dawn/tests/end2end/VideoViewsTests.cpp b/src/dawn/tests/end2end/VideoViewsTests.cpp
index 97942e5..0e8771f 100644
--- a/src/dawn/tests/end2end/VideoViewsTests.cpp
+++ b/src/dawn/tests/end2end/VideoViewsTests.cpp
@@ -12,10 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include "dawn/tests/end2end/VideoViewsTests.h"
+
#include <utility>
-#include "dawn/tests/end2end/VideoViewsTests.h"
+#include "dawn/common/Math.h"
#include "dawn/utils/ComboRenderPipelineDescriptor.h"
+#include "dawn/utils/TestUtils.h"
+#include "dawn/utils/TextureUtils.h"
#include "dawn/utils/WGPUHelpers.h"
namespace dawn {
@@ -26,28 +30,18 @@
VideoViewsTestBackend::~VideoViewsTestBackend() = default;
-constexpr std::array<utils::RGBA8, 2> VideoViewsTests::kYellowYUVColor;
-constexpr std::array<utils::RGBA8, 2> VideoViewsTests::kWhiteYUVColor;
-constexpr std::array<utils::RGBA8, 2> VideoViewsTests::kBlueYUVColor;
-constexpr std::array<utils::RGBA8, 2> VideoViewsTests::kRedYUVColor;
+constexpr std::array<utils::RGBA8, 2> VideoViewsTestsBase::kYellowYUVColor;
+constexpr std::array<utils::RGBA8, 2> VideoViewsTestsBase::kWhiteYUVColor;
+constexpr std::array<utils::RGBA8, 2> VideoViewsTestsBase::kBlueYUVColor;
+constexpr std::array<utils::RGBA8, 2> VideoViewsTestsBase::kRedYUVColor;
-void VideoViewsTests::SetUp() {
+void VideoViewsTestsBase::SetUp() {
DawnTest::SetUp();
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
DAWN_TEST_UNSUPPORTED_IF(!IsMultiPlanarFormatsSupported());
-
- mBackend = VideoViewsTestBackend::Create();
- mBackend->OnSetUp(device.Get());
}
-void VideoViewsTests::TearDown() {
- if (!UsesWire() && IsMultiPlanarFormatsSupported()) {
- mBackend->OnTearDown();
- }
- DawnTest::TearDown();
-}
-
-std::vector<wgpu::FeatureName> VideoViewsTests::GetRequiredFeatures() {
+std::vector<wgpu::FeatureName> VideoViewsTestsBase::GetRequiredFeatures() {
std::vector<wgpu::FeatureName> requiredFeatures = {};
mIsMultiPlanarFormatsSupported = SupportsFeatures({wgpu::FeatureName::DawnMultiPlanarFormats});
if (mIsMultiPlanarFormatsSupported) {
@@ -57,7 +51,7 @@
return requiredFeatures;
}
-bool VideoViewsTests::IsMultiPlanarFormatsSupported() const {
+bool VideoViewsTestsBase::IsMultiPlanarFormatsSupported() const {
return mIsMultiPlanarFormatsSupported;
}
@@ -67,8 +61,8 @@
// blue block, and bottom left is a 2x2 white block. When |isCheckerboard| is false, the
// image is converted from a solid yellow 4x4 block.
// static
-std::vector<uint8_t> VideoViewsTests::GetTestTextureData(wgpu::TextureFormat format,
- bool isCheckerboard) {
+std::vector<uint8_t> VideoViewsTestsBase::GetTestTextureData(wgpu::TextureFormat format,
+ bool isCheckerboard) {
constexpr uint8_t Yy = kYellowYUVColor[kYUVLumaPlaneIndex].r;
constexpr uint8_t Yu = kYellowYUVColor[kYUVChromaPlaneIndex].r;
constexpr uint8_t Yv = kYellowYUVColor[kYUVChromaPlaneIndex].g;
@@ -132,7 +126,7 @@
}
}
-uint32_t VideoViewsTests::NumPlanes(wgpu::TextureFormat format) {
+uint32_t VideoViewsTestsBase::NumPlanes(wgpu::TextureFormat format) {
switch (format) {
case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
return 2;
@@ -141,11 +135,11 @@
return 0;
}
}
-std::vector<uint8_t> VideoViewsTests::GetTestTextureDataWithPlaneIndex(size_t planeIndex,
- size_t bytesPerRow,
- size_t height,
- bool isCheckerboard) {
- std::vector<uint8_t> texelData = VideoViewsTests::GetTestTextureData(
+std::vector<uint8_t> VideoViewsTestsBase::GetTestTextureDataWithPlaneIndex(size_t planeIndex,
+ size_t bytesPerRow,
+ size_t height,
+ bool isCheckerboard) {
+ std::vector<uint8_t> texelData = VideoViewsTestsBase::GetTestTextureData(
wgpu::TextureFormat::R8BG8Biplanar420Unorm, isCheckerboard);
const uint32_t texelDataRowBytes = kYUVImageDataWidthInTexels;
const uint32_t texelDataHeight =
@@ -155,7 +149,7 @@
uint32_t plane_first_texel_offset = 0;
// The size of the test video frame is 4 x 4
switch (planeIndex) {
- case VideoViewsTests::kYUVLumaPlaneIndex:
+ case VideoViewsTestsBase::kYUVLumaPlaneIndex:
for (uint32_t i = 0; i < texelDataHeight; ++i) {
if (i < texelDataHeight) {
for (uint32_t j = 0; j < texelDataRowBytes; ++j) {
@@ -165,7 +159,7 @@
}
}
return texels;
- case VideoViewsTests::kYUVChromaPlaneIndex:
+ case VideoViewsTestsBase::kYUVChromaPlaneIndex:
// TexelData is 4 * 6 size, first 4 * 4 is Y plane, UV plane started
// at index 16.
plane_first_texel_offset = 16;
@@ -185,7 +179,7 @@
}
// Vertex shader used to render a sampled texture into a quad.
-wgpu::ShaderModule VideoViewsTests::GetTestVertexShaderModule() const {
+wgpu::ShaderModule VideoViewsTestsBase::GetTestVertexShaderModule() const {
return utils::CreateShaderModule(device, R"(
struct VertexOut {
@location(0) texCoord : vec2 <f32>,
@@ -209,6 +203,28 @@
})");
}
+class VideoViewsTests : public VideoViewsTestsBase {
+ protected:
+ void SetUp() override {
+ VideoViewsTestsBase::SetUp();
+ DAWN_TEST_UNSUPPORTED_IF(UsesWire());
+ DAWN_TEST_UNSUPPORTED_IF(!IsMultiPlanarFormatsSupported());
+
+ mBackend = VideoViewsTestBackend::Create();
+ mBackend->OnSetUp(device.Get());
+ }
+
+ void TearDown() override {
+ if (!UsesWire() && IsMultiPlanarFormatsSupported()) {
+ mBackend->OnTearDown();
+ }
+ VideoViewsTestsBase::TearDown();
+ }
+ std::unique_ptr<VideoViewsTestBackend> mBackend;
+};
+
+namespace {
+
// Create video texture uninitialized.
TEST_P(VideoViewsTests, CreateVideoTextureWithoutInitializedData) {
ASSERT_DEVICE_ERROR(
@@ -220,8 +236,6 @@
mBackend->DestroyVideoTextureForTest(std::move(platformTexture));
}
-namespace {
-
// Samples the luminance (Y) plane from an imported NV12 texture into a single channel of an RGBA
// output attachment and checks for the expected pixel value in the rendered quad.
TEST_P(VideoViewsTests, NV12SampleYtoR) {
@@ -887,8 +901,245 @@
mBackend->DestroyVideoTextureForTest(std::move(platformTexture));
}
+class VideoViewsExtendedUsagesTests : public VideoViewsTestsBase {
+ protected:
+ void SetUp() override {
+ VideoViewsTestsBase::SetUp();
+
+ DAWN_TEST_UNSUPPORTED_IF(!IsMultiPlanarFormatsSupported());
+
+ DAWN_TEST_UNSUPPORTED_IF(
+ !device.HasFeature(wgpu::FeatureName::MultiPlanarFormatExtendedUsages));
+ }
+
+ std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
+ std::vector<wgpu::FeatureName> requiredFeatures =
+ VideoViewsTestsBase::GetRequiredFeatures();
+ if (SupportsFeatures({wgpu::FeatureName::MultiPlanarFormatExtendedUsages})) {
+ requiredFeatures.push_back(wgpu::FeatureName::MultiPlanarFormatExtendedUsages);
+ }
+ return requiredFeatures;
+ }
+
+ wgpu::Texture CreateMultiPlanarTexture(wgpu::TextureFormat format,
+ wgpu::TextureUsage usage,
+ bool isCheckerboard = false,
+ bool initialized = true) {
+ wgpu::TextureDescriptor desc;
+ desc.format = format;
+ desc.size = {VideoViewsTestsBase::kYUVImageDataWidthInTexels,
+ VideoViewsTestsBase::kYUVImageDataHeightInTexels, 1};
+ desc.usage = usage;
+
+ wgpu::DawnTextureInternalUsageDescriptor internalDesc;
+ internalDesc.internalUsage = wgpu::TextureUsage::CopyDst;
+ desc.nextInChain = &internalDesc;
+
+ auto texture = device.CreateTexture(&desc);
+ if (texture == nullptr) {
+ return nullptr;
+ }
+
+ if (initialized) {
+ size_t numPlanes = VideoViewsTestsBase::NumPlanes(format);
+
+ wgpu::DawnEncoderInternalUsageDescriptor encoderInternalDesc;
+ encoderInternalDesc.useInternalUsages = true;
+ wgpu::CommandEncoderDescriptor encoderDesc;
+ encoderDesc.nextInChain = &encoderInternalDesc;
+
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder(&encoderDesc);
+
+ for (size_t plane = 0; plane < numPlanes; ++plane) {
+ size_t bytesPerRow = VideoViewsTestsBase::kYUVImageDataWidthInTexels;
+ bytesPerRow = Align(bytesPerRow, 256);
+
+ wgpu::ImageCopyTexture copyDst =
+ utils::CreateImageCopyTexture(texture, 0, {0, 0, 0});
+
+ wgpu::Extent3D copySize{VideoViewsTestsBase::kYUVImageDataWidthInTexels,
+ VideoViewsTestsBase::kYUVImageDataHeightInTexels, 1};
+ switch (plane) {
+ case VideoViewsTestsBase::kYUVLumaPlaneIndex:
+ copyDst.aspect = wgpu::TextureAspect::Plane0Only;
+ break;
+ case VideoViewsTestsBase::kYUVChromaPlaneIndex:
+ copyDst.aspect = wgpu::TextureAspect::Plane1Only;
+ copySize.width /= 2;
+ copySize.height /= 2;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ // Staging buffer.
+ wgpu::BufferDescriptor bufferDesc;
+ bufferDesc.size = bytesPerRow * copySize.height;
+ bufferDesc.mappedAtCreation = true;
+ bufferDesc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::MapWrite;
+
+ auto buffer = device.CreateBuffer(&bufferDesc);
+
+ std::vector<uint8_t> data = VideoViewsTestsBase::GetTestTextureDataWithPlaneIndex(
+ plane, bytesPerRow, VideoViewsTestsBase::kYUVImageDataHeightInTexels,
+ isCheckerboard);
+
+ memcpy(buffer.GetMappedRange(), data.data(), bufferDesc.size);
+ buffer.Unmap();
+
+ wgpu::ImageCopyBuffer copySrc =
+ utils::CreateImageCopyBuffer(buffer, 0, bytesPerRow);
+
+ encoder.CopyBufferToTexture(©Src, ©Dst, ©Size);
+ } // for plane
+
+ auto cmdBuffer = encoder.Finish();
+ device.GetQueue().Submit(1, &cmdBuffer);
+ } // initialized
+
+ return texture;
+ }
+};
+
+// Test that creating multi-planar texture should success if device is created with
+// MultiPlanarFormatExtendedUsages feature enabled.
+TEST_P(VideoViewsExtendedUsagesTests, CreateTextureSucceeds) {
+ auto texture = CreateMultiPlanarTexture(wgpu::TextureFormat::R8BG8Biplanar420Unorm,
+ wgpu::TextureUsage::TextureBinding);
+ EXPECT_NE(texture, nullptr);
+}
+
+// Tests sampling a multi-planar texture.
+TEST_P(VideoViewsExtendedUsagesTests, SamplingMultiPlanarTexture) {
+ // TODO(crbug.com/dawn/1998): Failure on Intel's Vulkan device.
+ DAWN_SUPPRESS_TEST_IF(IsWindows() && IsVulkan() && IsIntel());
+
+ auto texture = CreateMultiPlanarTexture(wgpu::TextureFormat::R8BG8Biplanar420Unorm,
+ wgpu::TextureUsage::TextureBinding,
+ /*isCheckerboard*/ true,
+ /*initialized*/ true);
+ EXPECT_NE(texture, nullptr);
+
+ wgpu::TextureViewDescriptor lumaViewDesc;
+ lumaViewDesc.format = wgpu::TextureFormat::R8Unorm;
+ lumaViewDesc.aspect = wgpu::TextureAspect::Plane0Only;
+ wgpu::TextureView lumaTextureView = texture.CreateView(&lumaViewDesc);
+
+ wgpu::TextureViewDescriptor chromaViewDesc;
+ chromaViewDesc.format = wgpu::TextureFormat::RG8Unorm;
+ chromaViewDesc.aspect = wgpu::TextureAspect::Plane1Only;
+ wgpu::TextureView chromaTextureView = texture.CreateView(&chromaViewDesc);
+
+ utils::ComboRenderPipelineDescriptor renderPipelineDescriptor;
+ renderPipelineDescriptor.vertex.module = GetTestVertexShaderModule();
+
+ renderPipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
+ @group(0) @binding(0) var sampler0 : sampler;
+ @group(0) @binding(1) var lumaTexture : texture_2d<f32>;
+ @group(0) @binding(2) var chromaTexture : texture_2d<f32>;
+
+ @fragment
+ fn main(@location(0) texCoord : vec2f) -> @location(0) vec4f {
+ let y : f32 = textureSample(lumaTexture, sampler0, texCoord).r;
+ let u : f32 = textureSample(chromaTexture, sampler0, texCoord).r;
+ let v : f32 = textureSample(chromaTexture, sampler0, texCoord).g;
+ return vec4f(y, u, v, 1.0);
+ })");
+
+ utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(
+ device, kYUVImageDataWidthInTexels, kYUVImageDataHeightInTexels);
+ renderPipelineDescriptor.cTargets[0].format = renderPass.colorFormat;
+
+ wgpu::RenderPipeline renderPipeline = device.CreateRenderPipeline(&renderPipelineDescriptor);
+
+ wgpu::Sampler sampler = device.CreateSampler();
+
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+ {
+ wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
+ pass.SetPipeline(renderPipeline);
+ pass.SetBindGroup(
+ 0, utils::MakeBindGroup(device, renderPipeline.GetBindGroupLayout(0),
+ {{0, sampler}, {1, lumaTextureView}, {2, chromaTextureView}}));
+ pass.Draw(6);
+ pass.End();
+ }
+
+ wgpu::CommandBuffer commands = encoder.Finish();
+ queue.Submit(1, &commands);
+
+ std::vector<uint8_t> expectedData =
+ GetTestTextureData(wgpu::TextureFormat::RGBA8Unorm, /*isCheckerboard*/ true);
+ std::vector<utils::RGBA8> expectedRGBA;
+ for (uint8_t i = 0; i < expectedData.size(); i += 3) {
+ expectedRGBA.push_back({expectedData[i], expectedData[i + 1], expectedData[i + 2], 0xFF});
+ }
+
+ EXPECT_TEXTURE_EQ(expectedRGBA.data(), renderPass.color, {0, 0},
+ {kYUVImageDataWidthInTexels, kYUVImageDataHeightInTexels});
+}
+
+// Test copying from multi-planar format per plane to a buffer succeeds.
+TEST_P(VideoViewsExtendedUsagesTests, T2BCopyPlaneAspectsSucceeds) {
+ const std::vector<uint8_t> expectedData =
+ GetTestTextureData(wgpu::TextureFormat::RGBA8Unorm, /*isCheckerboard*/ false);
+
+ auto srcTexture =
+ CreateMultiPlanarTexture(wgpu::TextureFormat::R8BG8Biplanar420Unorm,
+ wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopySrc,
+ /*isCheckerboard*/ false,
+ /*initialized*/ true);
+ EXPECT_NE(srcTexture, nullptr);
+
+ wgpu::BufferDescriptor bufferDescriptor;
+ bufferDescriptor.size = 256;
+ bufferDescriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
+ wgpu::Buffer dstBuffer = device.CreateBuffer(&bufferDescriptor);
+
+ wgpu::ImageCopyBuffer copyDst = utils::CreateImageCopyBuffer(dstBuffer, 0, 256);
+
+ wgpu::Extent3D copySize = {1, 1, 1};
+
+ // Plane0
+ wgpu::ImageCopyTexture copySrc =
+ utils::CreateImageCopyTexture(srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::Plane0Only);
+
+ {
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+ encoder.CopyTextureToBuffer(©Src, ©Dst, ©Size);
+
+ auto cmdBuffer = encoder.Finish();
+ device.GetQueue().Submit(1, &cmdBuffer);
+
+ EXPECT_BUFFER_U8_EQ(expectedData[0], dstBuffer, 0);
+ }
+
+ // Plane1
+ copySrc =
+ utils::CreateImageCopyTexture(srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::Plane1Only);
+ {
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+ encoder.CopyTextureToBuffer(©Src, ©Dst, ©Size);
+
+ auto cmdBuffer = encoder.Finish();
+ device.GetQueue().Submit(1, &cmdBuffer);
+
+ uint16_t expectedUVData;
+ memcpy(&expectedUVData, expectedData.data() + 1, sizeof(expectedUVData));
+ EXPECT_BUFFER_U16_EQ(expectedUVData, dstBuffer, 0);
+ }
+}
+
DAWN_INSTANTIATE_TEST_V(VideoViewsTests, VideoViewsTestBackend::Backends());
DAWN_INSTANTIATE_TEST_V(VideoViewsValidationTests, VideoViewsTestBackend::Backends());
+DAWN_INSTANTIATE_TEST(VideoViewsExtendedUsagesTests,
+ D3D11Backend(),
+ D3D12Backend(),
+ MetalBackend(),
+ OpenGLBackend(),
+ OpenGLESBackend(),
+ VulkanBackend());
+
} // anonymous namespace
} // namespace dawn
diff --git a/src/dawn/tests/end2end/VideoViewsTests.h b/src/dawn/tests/end2end/VideoViewsTests.h
index 0e1b894..bfc24ad 100644
--- a/src/dawn/tests/end2end/VideoViewsTests.h
+++ b/src/dawn/tests/end2end/VideoViewsTests.h
@@ -53,7 +53,7 @@
virtual void DestroyVideoTextureForTest(std::unique_ptr<PlatformTexture>&& platformTexture) = 0;
};
-class VideoViewsTests : public DawnTest {
+class VideoViewsTestsBase : public DawnTest {
public:
// The width and height in texels are 4 for all YUV formats.
static constexpr uint32_t kYUVImageDataWidthInTexels = 4;
@@ -90,12 +90,10 @@
protected:
void SetUp() override;
- void TearDown() override;
std::vector<wgpu::FeatureName> GetRequiredFeatures() override;
bool IsMultiPlanarFormatsSupported() const;
wgpu::ShaderModule GetTestVertexShaderModule() const;
- std::unique_ptr<VideoViewsTestBackend> mBackend;
bool mIsMultiPlanarFormatsSupported = false;
};
diff --git a/src/dawn/tests/end2end/VideoViewsTests_gbm.cpp b/src/dawn/tests/end2end/VideoViewsTests_gbm.cpp
index 8de4141..08461c5 100644
--- a/src/dawn/tests/end2end/VideoViewsTests_gbm.cpp
+++ b/src/dawn/tests/end2end/VideoViewsTests_gbm.cpp
@@ -143,8 +143,8 @@
// of I915_FORMAT_MOD_Y_TILED.
flags |= GBM_BO_USE_SW_WRITE_RARELY;
}
- gbm_bo* gbmBo = gbm_bo_create(mGbmDevice, VideoViewsTests::kYUVImageDataWidthInTexels,
- VideoViewsTests::kYUVImageDataHeightInTexels,
+ gbm_bo* gbmBo = gbm_bo_create(mGbmDevice, VideoViewsTestsBase::kYUVImageDataWidthInTexels,
+ VideoViewsTestsBase::kYUVImageDataHeightInTexels,
GetGbmBoFormat(format), flags);
if (gbmBo == nullptr) {
return nullptr;
@@ -153,18 +153,18 @@
if (initialized) {
void* mapHandle = nullptr;
uint32_t strideBytes = 0;
- void* addr = gbm_bo_map(gbmBo, 0, 0, VideoViewsTests::kYUVImageDataWidthInTexels,
- VideoViewsTests::kYUVImageDataHeightInTexels,
+ void* addr = gbm_bo_map(gbmBo, 0, 0, VideoViewsTestsBase::kYUVImageDataWidthInTexels,
+ VideoViewsTestsBase::kYUVImageDataHeightInTexels,
GBM_BO_TRANSFER_WRITE, &strideBytes, &mapHandle);
EXPECT_NE(addr, nullptr);
std::vector<uint8_t> initialData =
- VideoViewsTests::GetTestTextureData(format, isCheckerboard);
+ VideoViewsTestsBase::GetTestTextureData(format, isCheckerboard);
uint8_t* srcBegin = initialData.data();
uint8_t* srcEnd = srcBegin + initialData.size();
uint8_t* dstBegin = static_cast<uint8_t*>(addr);
- for (; srcBegin < srcEnd;
- srcBegin += VideoViewsTests::kYUVImageDataWidthInTexels, dstBegin += strideBytes) {
- std::memcpy(dstBegin, srcBegin, VideoViewsTests::kYUVImageDataWidthInTexels);
+ for (; srcBegin < srcEnd; srcBegin += VideoViewsTestsBase::kYUVImageDataWidthInTexels,
+ dstBegin += strideBytes) {
+ std::memcpy(dstBegin, srcBegin, VideoViewsTestsBase::kYUVImageDataWidthInTexels);
}
gbm_bo_unmap(gbmBo, mapHandle);
@@ -174,8 +174,8 @@
textureDesc.format = format;
textureDesc.dimension = wgpu::TextureDimension::e2D;
textureDesc.usage = usage;
- textureDesc.size = {VideoViewsTests::kYUVImageDataWidthInTexels,
- VideoViewsTests::kYUVImageDataHeightInTexels, 1};
+ textureDesc.size = {VideoViewsTestsBase::kYUVImageDataWidthInTexels,
+ VideoViewsTestsBase::kYUVImageDataHeightInTexels, 1};
wgpu::DawnTextureInternalUsageDescriptor internalDesc;
internalDesc.internalUsage = wgpu::TextureUsage::CopySrc;
diff --git a/src/dawn/tests/end2end/VideoViewsTests_mac.cpp b/src/dawn/tests/end2end/VideoViewsTests_mac.cpp
index b1eb583..1d58acd 100644
--- a/src/dawn/tests/end2end/VideoViewsTests_mac.cpp
+++ b/src/dawn/tests/end2end/VideoViewsTests_mac.cpp
@@ -66,7 +66,7 @@
size_t GetSubSamplingFactorPerPlane(wgpu::TextureFormat format, size_t plane) {
switch (format) {
case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
- return plane == VideoViewsTests::kYUVLumaPlaneIndex ? 1 : 2;
+ return plane == VideoViewsTestsBase::kYUVLumaPlaneIndex ? 1 : 2;
default:
UNREACHABLE();
return 0;
@@ -76,7 +76,7 @@
size_t BytesPerElement(wgpu::TextureFormat format, size_t plane) {
switch (format) {
case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
- return plane == VideoViewsTests::kYUVLumaPlaneIndex ? 1 : 2;
+ return plane == VideoViewsTestsBase::kYUVLumaPlaneIndex ? 1 : 2;
default:
UNREACHABLE();
return 0;
@@ -91,19 +91,19 @@
CFMutableDictionaryRef dict(CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
- AddIntegerValue(dict, kIOSurfaceWidth, VideoViewsTests::kYUVImageDataWidthInTexels);
- AddIntegerValue(dict, kIOSurfaceHeight, VideoViewsTests::kYUVImageDataHeightInTexels);
+ AddIntegerValue(dict, kIOSurfaceWidth, VideoViewsTestsBase::kYUVImageDataWidthInTexels);
+ AddIntegerValue(dict, kIOSurfaceHeight, VideoViewsTestsBase::kYUVImageDataHeightInTexels);
AddIntegerValue(dict, kIOSurfacePixelFormat, ToCVFormat(format));
- size_t num_planes = VideoViewsTests::NumPlanes(format);
+ size_t num_planes = VideoViewsTestsBase::NumPlanes(format);
CFMutableArrayRef planes(
CFArrayCreateMutable(kCFAllocatorDefault, num_planes, &kCFTypeArrayCallBacks));
size_t total_bytes_alloc = 0;
for (size_t plane = 0; plane < num_planes; ++plane) {
const size_t factor = GetSubSamplingFactorPerPlane(format, plane);
- const size_t plane_width = VideoViewsTests::kYUVImageDataWidthInTexels / factor;
- const size_t plane_height = VideoViewsTests::kYUVImageDataHeightInTexels / factor;
+ const size_t plane_width = VideoViewsTestsBase::kYUVImageDataWidthInTexels / factor;
+ const size_t plane_height = VideoViewsTestsBase::kYUVImageDataHeightInTexels / factor;
const size_t plane_bytes_per_element = BytesPerElement(format, plane);
const size_t plane_bytes_per_row = IOSurfaceAlignProperty(
kIOSurfacePlaneBytesPerRow, plane_width * plane_bytes_per_element);
@@ -138,7 +138,7 @@
if (initialized) {
IOSurfaceLock(surface, 0, nullptr);
for (size_t plane = 0; plane < num_planes; ++plane) {
- std::vector<uint8_t> data = VideoViewsTests::GetTestTextureDataWithPlaneIndex(
+ std::vector<uint8_t> data = VideoViewsTestsBase::GetTestTextureDataWithPlaneIndex(
plane, IOSurfaceGetBytesPerRowOfPlane(surface, plane),
IOSurfaceGetHeightOfPlane(surface, plane), isCheckerboard);
void* pointer = IOSurfaceGetBaseAddressOfPlane(surface, plane);
@@ -151,8 +151,8 @@
textureDesc.format = format;
textureDesc.dimension = wgpu::TextureDimension::e2D;
textureDesc.usage = usage;
- textureDesc.size = {VideoViewsTests::kYUVImageDataWidthInTexels,
- VideoViewsTests::kYUVImageDataHeightInTexels, 1};
+ textureDesc.size = {VideoViewsTestsBase::kYUVImageDataWidthInTexels,
+ VideoViewsTestsBase::kYUVImageDataHeightInTexels, 1};
wgpu::DawnTextureInternalUsageDescriptor internalDesc;
internalDesc.internalUsage = wgpu::TextureUsage::CopySrc;
diff --git a/src/dawn/tests/end2end/VideoViewsTests_win.cpp b/src/dawn/tests/end2end/VideoViewsTests_win.cpp
index f505716..c30235c 100644
--- a/src/dawn/tests/end2end/VideoViewsTests_win.cpp
+++ b/src/dawn/tests/end2end/VideoViewsTests_win.cpp
@@ -99,13 +99,13 @@
textureDesc.format = format;
textureDesc.dimension = wgpu::TextureDimension::e2D;
textureDesc.usage = usage;
- textureDesc.size = {VideoViewsTests::kYUVImageDataWidthInTexels,
- VideoViewsTests::kYUVImageDataHeightInTexels, 1};
+ textureDesc.size = {VideoViewsTestsBase::kYUVImageDataWidthInTexels,
+ VideoViewsTestsBase::kYUVImageDataHeightInTexels, 1};
// Create a DX11 texture with data then wrap it in a shared handle.
D3D11_TEXTURE2D_DESC d3dDescriptor;
- d3dDescriptor.Width = VideoViewsTests::kYUVImageDataWidthInTexels;
- d3dDescriptor.Height = VideoViewsTests::kYUVImageDataHeightInTexels;
+ d3dDescriptor.Width = VideoViewsTestsBase::kYUVImageDataWidthInTexels;
+ d3dDescriptor.Height = VideoViewsTestsBase::kYUVImageDataHeightInTexels;
d3dDescriptor.MipLevels = 1;
d3dDescriptor.ArraySize = 1;
d3dDescriptor.Format = GetDXGITextureFormat(format);
@@ -117,11 +117,11 @@
d3dDescriptor.MiscFlags = D3D11_RESOURCE_MISC_SHARED_NTHANDLE | D3D11_RESOURCE_MISC_SHARED;
std::vector<uint8_t> initialData =
- VideoViewsTests::GetTestTextureData(format, isCheckerboard);
+ VideoViewsTestsBase::GetTestTextureData(format, isCheckerboard);
D3D11_SUBRESOURCE_DATA subres;
subres.pSysMem = initialData.data();
- subres.SysMemPitch = VideoViewsTests::kYUVImageDataWidthInTexels;
+ subres.SysMemPitch = VideoViewsTestsBase::kYUVImageDataWidthInTexels;
ComPtr<ID3D11Texture2D> d3d11Texture;
HRESULT hr = mD3d11Device->CreateTexture2D(
diff --git a/src/dawn/wire/SupportedFeatures.cpp b/src/dawn/wire/SupportedFeatures.cpp
index f5fbeed..bbbb8bb 100644
--- a/src/dawn/wire/SupportedFeatures.cpp
+++ b/src/dawn/wire/SupportedFeatures.cpp
@@ -52,6 +52,7 @@
case WGPUFeatureName_DepthClipControl:
case WGPUFeatureName_DawnInternalUsages:
case WGPUFeatureName_DawnMultiPlanarFormats:
+ case WGPUFeatureName_MultiPlanarFormatExtendedUsages:
case WGPUFeatureName_ChromiumExperimentalDp4a:
case WGPUFeatureName_ShaderF16:
case WGPUFeatureName_RG11B10UfloatRenderable: