D3D12: Support per plane views with NV12 textures

Adds support for NV12 texture format and per plane view aspects.
Only allows planar sampling of imported DX11 textures. See usage
tests for examples and formats.h for rules.

Bug: dawn:551
Change-Id: I44b89d2c07bb9969638e77ce7c756ef367167f0c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/38781
Commit-Queue: Bryan Bernhart <bryan.bernhart@intel.com>
Reviewed-by: Austin Eng <enga@chromium.org>
This commit is contained in:
Bryan Bernhart 2021-02-05 20:11:24 +00:00 committed by Commit Bot service account
parent 185c6a5b0f
commit 14a2398e71
20 changed files with 1008 additions and 13 deletions

View File

@ -817,7 +817,8 @@
{"name": "texture compression BC", "type": "bool", "default": "false"},
{"name": "shader float16", "type": "bool", "default": "false"},
{"name": "pipeline statistics query", "type": "bool", "default": "false"},
{"name": "timestamp query", "type": "bool", "default": "false"}
{"name": "timestamp query", "type": "bool", "default": "false"},
{"name": "multi planar formats", "type": "bool", "default": "false"}
]
},
"depth stencil state descriptor": {
@ -1709,7 +1710,9 @@
"values": [
{"value": 0, "name": "all"},
{"value": 1, "name": "stencil only"},
{"value": 2, "name": "depth only"}
{"value": 2, "name": "depth only"},
{"value": 3, "name": "plane 0 only"},
{"value": 4, "name": "plane 1 only"}
]
},
"texture component type": {
@ -1823,7 +1826,8 @@
{"value": 50, "name": "BC6H RGB ufloat"},
{"value": 51, "name": "BC6H RGB float"},
{"value": 52, "name": "BC7 RGBA unorm"},
{"value": 53, "name": "BC7 RGBA unorm srgb"}
{"value": 53, "name": "BC7 RGBA unorm srgb"},
{"value": 54, "name": "R8 BG8 Biplanar 420 unorm"}
]
},
"texture usage": {

View File

@ -355,7 +355,8 @@ namespace dawn_native {
return {};
}
// Always returns a single aspect (color, stencil, or depth).
// Always returns a single aspect (color, stencil, depth, or ith plane for multi-planar
// formats).
ResultOrError<Aspect> SingleAspectUsedByTextureCopyView(const TextureCopyView& view) {
const Format& format = view.texture->GetFormat();
switch (view.aspect) {
@ -375,6 +376,9 @@ namespace dawn_native {
case wgpu::TextureAspect::StencilOnly:
ASSERT(format.aspects & Aspect::Stencil);
return Aspect::Stencil;
case wgpu::TextureAspect::Plane0Only:
case wgpu::TextureAspect::Plane1Only:
UNREACHABLE();
}
}

View File

@ -47,7 +47,12 @@ namespace dawn_native {
{Extension::TimestampQuery,
{"timestamp_query", "Support Timestamp Query",
"https://bugs.chromium.org/p/dawn/issues/detail?id=434"},
&WGPUDeviceProperties::timestampQuery}}};
&WGPUDeviceProperties::timestampQuery},
{Extension::MultiPlanarFormats,
{"multiplanar_formats",
"Import and use multi-planar texture formats with per plane views",
"https://bugs.chromium.org/p/dawn/issues/detail?id=551"},
&WGPUDeviceProperties::multiPlanarFormats}}};
} // anonymous namespace

View File

@ -28,6 +28,7 @@ namespace dawn_native {
ShaderFloat16,
PipelineStatisticsQuery,
TimestampQuery,
MultiPlanarFormats,
EnumCount,
InvalidEnum = EnumCount,

View File

@ -27,6 +27,15 @@ namespace dawn_native {
static const AspectInfo kStencil8AspectInfo = {{1, 1, 1},
wgpu::TextureComponentType::Uint,
ComponentTypeBit::Uint};
// R8BG8Biplanar420Unorm must be specialized since it represents planar data and cannot be
// used without a per plane format. In particular, the component type is float since
// Dawn does not allow texture format reinterpretion (ex. using R8BG82plane420 with Uint or
// Unorm). Block size is always zero since the format is not renderable or copyable.
static const AspectInfo kR8BG8Biplanar420UnormAspectInfo = {
{0, 0, 0},
wgpu::TextureComponentType::Float,
ComponentTypeBit::Float};
}
// Format
@ -99,6 +108,10 @@ namespace dawn_native {
return (aspects & (Aspect::Depth | Aspect::Stencil)) != 0;
}
bool Format::IsMultiPlanar() const {
return (aspects & (Aspect::Plane0 | Aspect::Plane1)) != 0;
}
const AspectInfo& Format::GetAspectInfo(wgpu::TextureAspect aspect) const {
return GetAspectInfo(ConvertAspect(*this, aspect));
}
@ -111,6 +124,12 @@ namespace dawn_native {
// same aspect information, special case it to return a constant AspectInfo.
if (aspect == Aspect::Stencil) {
return kStencil8AspectInfo;
// multi-planar formats are specified per plane aspect. Since it does not support
// non-planar access, it can always be the same aspect information, special cased to
// return a constant AspectInfo.
// TODO(dawn:551): Refactor and remove GetAspectFormat.
} else if (format == wgpu::TextureFormat::R8BG8Biplanar420Unorm) {
return kR8BG8Biplanar420UnormAspectInfo;
} else {
return firstAspect;
}
@ -120,6 +139,24 @@ namespace dawn_native {
return ComputeFormatIndex(format);
}
wgpu::TextureFormat Format::GetAspectFormat(wgpu::TextureAspect aspect) const {
switch (format) {
case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
switch (aspect) {
case wgpu::TextureAspect::Plane0Only:
return wgpu::TextureFormat::R8Unorm;
case wgpu::TextureAspect::Plane1Only:
return wgpu::TextureFormat::RG8Unorm;
default:
return wgpu::TextureFormat::Undefined;
}
break;
// TODO(dawn:551): Consider using for depth-stencil formats.
default:
return format;
}
}
// Implementation details of the format table of the DeviceBase
// For the enum for formats are packed but this might change when we have a broader extension
@ -148,7 +185,9 @@ namespace dawn_native {
// Vulkan describes bytesPerRow in units of texels. If there's any format for which this
// ASSERT isn't true, then additional validation on bytesPerRow must be added.
ASSERT((kTextureBytesPerRowAlignment % format.firstAspect.block.byteSize) == 0);
// Multi-planar formats are not copyable and have no first aspect.
ASSERT(format.IsMultiPlanar() ||
(kTextureBytesPerRowAlignment % format.firstAspect.block.byteSize) == 0);
table[index] = format;
formatsSet.set(index);
@ -207,6 +246,18 @@ namespace dawn_native {
AddFormat(internalFormat);
};
auto AddMultiPlanarFormat = [&AddFormat](wgpu::TextureFormat format, Aspect aspects,
bool isSupported) {
Format internalFormat;
internalFormat.format = format;
internalFormat.isRenderable = false;
internalFormat.isCompressed = false;
internalFormat.isSupported = isSupported;
internalFormat.supportsStorageUsage = false;
internalFormat.aspects = aspects;
AddFormat(internalFormat);
};
// clang-format off
// 1 byte color formats
@ -281,6 +332,10 @@ namespace dawn_native {
AddCompressedFormat(wgpu::TextureFormat::BC7RGBAUnorm, 16, 4, 4, isBCFormatSupported);
AddCompressedFormat(wgpu::TextureFormat::BC7RGBAUnormSrgb, 16, 4, 4, isBCFormatSupported);
// multi-planar formats
const bool isMultiPlanarFormatSupported = device->IsExtensionEnabled(Extension::MultiPlanarFormats);
AddMultiPlanarFormat(wgpu::TextureFormat::R8BG8Biplanar420Unorm, Aspect::Plane0 | Aspect::Plane1, isMultiPlanarFormatSupported);
// clang-format on
// This checks that each format is set at least once, the second part of checking that all

View File

@ -23,6 +23,22 @@
#include <array>
// About multi-planar formats.
//
// Dawn supports additional multi-planar formats when the multiplanar_formats extension is enabled.
// When enabled, Dawn treats planar data as sub-resources (ie. 1 sub-resource == 1 view == 1 plane).
// A multi-planar format name encodes the channel mapping and order of planes. For example,
// R8BG8Biplanar420Unorm is YUV 4:2:0 where Plane 0 = R8, and Plane 1 = BG8.
//
// Requirements:
// * Plane aspects cannot be combined with color, depth, or stencil aspects.
// * Only compatible multi-planar formats of planes can be used with multi-planar texture
// formats.
// * Can't access multiple planes without creating per plane views (no color conversion).
// * Multi-planar format cannot be written or read without a per plane view.
//
// TODO(dawn:551): Consider moving this comment.
namespace dawn_native {
enum class Aspect : uint8_t;
@ -56,7 +72,7 @@ namespace dawn_native {
// The number of formats Dawn knows about. Asserts in BuildFormatTable ensure that this is the
// exact number of known format.
static constexpr size_t kKnownFormatCount = 53;
static constexpr size_t kKnownFormatCount = 54;
struct Format;
using FormatTable = std::array<Format, kKnownFormatCount>;
@ -76,6 +92,10 @@ namespace dawn_native {
bool HasStencil() const;
bool HasDepthOrStencil() const;
// IsMultiPlanar() returns true if the format allows selecting a plane index. This is only
// allowed by multi-planar formats (ex. NV12).
bool IsMultiPlanar() const;
const AspectInfo& GetAspectInfo(wgpu::TextureAspect aspect) const;
const AspectInfo& GetAspectInfo(Aspect aspect) const;
@ -83,6 +103,10 @@ namespace dawn_native {
// in [0, kKnownFormatCount)
size_t GetIndex() const;
// Used to lookup the compatible view format using an aspect which corresponds to the
// plane index. Returns Undefined if the wrong plane aspect is requested.
wgpu::TextureFormat GetAspectFormat(wgpu::TextureAspect aspect) const;
private:
// The most common aspect: the color aspect for color texture, the depth aspect for
// depth[-stencil] textures.

View File

@ -31,6 +31,21 @@ namespace dawn_native {
return aspectMask;
}
Aspect ConvertViewAspect(const Format& format, wgpu::TextureAspect aspect) {
// Color view |format| must be treated as the same plane |aspect|.
if (format.aspects == Aspect::Color) {
switch (aspect) {
case wgpu::TextureAspect::Plane0Only:
return Aspect::Plane0;
case wgpu::TextureAspect::Plane1Only:
return Aspect::Plane1;
default:
break;
}
}
return ConvertAspect(format, aspect);
}
Aspect SelectFormatAspects(const Format& format, wgpu::TextureAspect aspect) {
switch (aspect) {
case wgpu::TextureAspect::All:
@ -39,6 +54,10 @@ namespace dawn_native {
return format.aspects & Aspect::Depth;
case wgpu::TextureAspect::StencilOnly:
return format.aspects & Aspect::Stencil;
case wgpu::TextureAspect::Plane0Only:
return format.aspects & Aspect::Plane0;
case wgpu::TextureAspect::Plane1Only:
return format.aspects & Aspect::Plane1;
}
}
@ -47,8 +66,10 @@ namespace dawn_native {
switch (aspect) {
case Aspect::Color:
case Aspect::Depth:
case Aspect::Plane0:
case Aspect::CombinedDepthStencil:
return 0;
case Aspect::Plane1:
case Aspect::Stencil:
return 1;
default:
@ -63,6 +84,8 @@ namespace dawn_native {
if (aspects == Aspect::Color || aspects == Aspect::Depth ||
aspects == Aspect::CombinedDepthStencil) {
return 1;
} else if (aspects == (Aspect::Plane0 | Aspect::Plane1)) {
return 2;
} else {
ASSERT(aspects == (Aspect::Depth | Aspect::Stencil));
return 2;

View File

@ -29,14 +29,18 @@ namespace dawn_native {
Depth = 0x2,
Stencil = 0x4,
// Aspects used to select individual planes in a multi-planar format.
Plane0 = 0x8,
Plane1 = 0x10,
// An aspect for that represents the combination of both the depth and stencil aspects. It
// can be ignored outside of the Vulkan backend.
CombinedDepthStencil = 0x8,
CombinedDepthStencil = 0x20,
};
template <>
struct EnumBitmaskSize<Aspect> {
static constexpr unsigned value = 4;
static constexpr unsigned value = 6;
};
// Convert the TextureAspect to an Aspect mask for the format. ASSERTs if the aspect
@ -53,6 +57,10 @@ namespace dawn_native {
// selected aspects.
Aspect SelectFormatAspects(const Format& format, wgpu::TextureAspect aspect);
// Convert TextureAspect to the aspect which corresponds to the view format. This
// special cases per plane view formats before calling ConvertAspect.
Aspect ConvertViewAspect(const Format& format, wgpu::TextureAspect aspect);
// Helper struct to make it clear that what the parameters of a range mean.
template <typename T>
struct FirstAndCountRange {

View File

@ -29,7 +29,7 @@ namespace dawn_native {
// TODO(jiawei.shao@intel.com): implement texture view format compatibility rule
MaybeError ValidateTextureViewFormatCompatibility(const TextureBase* texture,
const TextureViewDescriptor* descriptor) {
if (texture->GetFormat().format != descriptor->format) {
if (texture->GetFormat().GetAspectFormat(descriptor->aspect) != descriptor->format) {
return DAWN_VALIDATION_ERROR(
"The format of texture view is not compatible to the original texture");
}
@ -227,6 +227,11 @@ namespace dawn_native {
return DAWN_VALIDATION_ERROR("Format cannot be used in storage textures");
}
constexpr wgpu::TextureUsage kValidMultiPlanarUsages = wgpu::TextureUsage::Sampled;
if (format->IsMultiPlanar() && !IsSubset(descriptor->usage, kValidMultiPlanarUsages)) {
return DAWN_VALIDATION_ERROR("Multi-planar format doesn't have valid usage.");
}
return {};
}
@ -352,7 +357,7 @@ namespace dawn_native {
}
if (desc.format == wgpu::TextureFormat::Undefined) {
desc.format = texture->GetFormat().format;
desc.format = texture->GetFormat().GetAspectFormat(desc.aspect);
}
if (desc.arrayLayerCount == 0) {
desc.arrayLayerCount = texture->GetArrayLayers() - desc.baseArrayLayer;
@ -608,7 +613,7 @@ namespace dawn_native {
mTexture(texture),
mFormat(GetDevice()->GetValidInternalFormat(descriptor->format)),
mDimension(descriptor->dimension),
mRange({ConvertAspect(mFormat, descriptor->aspect),
mRange({ConvertViewAspect(mFormat, descriptor->aspect),
{descriptor->baseArrayLayer, descriptor->arrayLayerCount},
{descriptor->baseMipLevel, descriptor->mipLevelCount}}) {
}

View File

@ -122,6 +122,7 @@ namespace dawn_native { namespace d3d12 {
if (mDeviceInfo.supportsShaderFloat16 && GetBackend()->GetFunctions()->IsDXCAvailable()) {
mSupportedExtensions.EnableExtension(Extension::ShaderFloat16);
}
mSupportedExtensions.EnableExtension(Extension::MultiPlanarFormats);
}
MaybeError Adapter::InitializeDebugLayerFilters() {

View File

@ -59,6 +59,20 @@ namespace dawn_native { namespace d3d12 {
}
}
// Used to share resources cross-API. If we query CheckFeatureSupport for
// D3D12_FEATURE_D3D12_OPTIONS4 successfully, then we can use cross-API sharing.
info.supportsSharedResourceCapabilityTier1 = false;
D3D12_FEATURE_DATA_D3D12_OPTIONS4 featureOptions4 = {};
if (SUCCEEDED(adapter.GetDevice()->CheckFeatureSupport(
D3D12_FEATURE_D3D12_OPTIONS4, &featureOptions4, sizeof(featureOptions4)))) {
// Tier 1 support additionally enables the NV12 format. Since only the NV12 format
// is used by Dawn, check for Tier 1.
if (featureOptions4.SharedResourceCompatibilityTier >=
D3D12_SHARED_RESOURCE_COMPATIBILITY_TIER_1) {
info.supportsSharedResourceCapabilityTier1 = true;
}
}
D3D12_FEATURE_DATA_SHADER_MODEL knownShaderModels[] = {{D3D_SHADER_MODEL_6_2},
{D3D_SHADER_MODEL_6_1},
{D3D_SHADER_MODEL_6_0},

View File

@ -32,6 +32,7 @@ namespace dawn_native { namespace d3d12 {
// indicates that current driver supports the maximum shader model is shader model 6.2.
uint32_t shaderModel;
PerStage<std::wstring> shaderProfiles;
bool supportsSharedResourceCapabilityTier1;
};
ResultOrError<D3D12DeviceInfo> GatherDeviceInfo(const Adapter& adapter);

View File

@ -202,6 +202,7 @@ namespace dawn_native { namespace d3d12 {
case wgpu::TextureFormat::BC7RGBAUnormSrgb:
return DXGI_FORMAT_BC7_TYPELESS;
case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
case wgpu::TextureFormat::Undefined:
UNREACHABLE();
}
@ -324,6 +325,9 @@ namespace dawn_native { namespace d3d12 {
case wgpu::TextureFormat::BC7RGBAUnormSrgb:
return DXGI_FORMAT_BC7_UNORM_SRGB;
case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
return DXGI_FORMAT_NV12;
case wgpu::TextureFormat::Undefined:
UNREACHABLE();
}
@ -379,11 +383,34 @@ namespace dawn_native { namespace d3d12 {
return {};
}
// https://docs.microsoft.com/en-us/windows/win32/api/d3d12/ne-d3d12-d3d12_shared_resource_compatibility_tier
MaybeError ValidateD3D12VideoTextureCanBeShared(Device* device, DXGI_FORMAT textureFormat) {
const bool supportsSharedResourceCapabilityTier1 =
device->GetDeviceInfo().supportsSharedResourceCapabilityTier1;
switch (textureFormat) {
// MSDN docs are not correct, NV12 requires at-least tier 1.
case DXGI_FORMAT_NV12:
if (supportsSharedResourceCapabilityTier1) {
return {};
}
break;
default:
break;
}
return DAWN_VALIDATION_ERROR("DXGI format does not support cross-API sharing.");
}
// static
ResultOrError<Ref<Texture>> Texture::Create(Device* device,
const TextureDescriptor* descriptor) {
Ref<Texture> dawnTexture =
AcquireRef(new Texture(device, descriptor, TextureState::OwnedInternal));
if (dawnTexture->GetFormat().IsMultiPlanar()) {
return DAWN_VALIDATION_ERROR("Cannot create a multi-planar formatted texture directly");
}
DAWN_TRY(dawnTexture->InitializeAsInternalTexture());
return std::move(dawnTexture);
}
@ -401,6 +428,14 @@ namespace dawn_native { namespace d3d12 {
AcquireRef(new Texture(device, textureDescriptor, TextureState::OwnedExternal));
DAWN_TRY(dawnTexture->InitializeAsExternalTexture(textureDescriptor, sharedHandle,
acquireMutexKey, isSwapChainTexture));
// Importing a multi-planar format must be initialized. This is required because
// a shared multi-planar format cannot be initialized by Dawn.
if (!descriptor->isInitialized && dawnTexture->GetFormat().IsMultiPlanar()) {
return DAWN_VALIDATION_ERROR(
"Cannot create a multi-planar formatted texture without being initialized");
}
dawnTexture->SetIsSubresourceContentInitialized(descriptor->isInitialized,
dawnTexture->GetAllSubresources());
return std::move(dawnTexture);
@ -431,6 +466,13 @@ namespace dawn_native { namespace d3d12 {
DAWN_TRY(ValidateD3D12TextureCanBeWrapped(d3d12Resource.Get(), descriptor));
// Shared handle is assumed to support resource sharing capability. The resource
// shared capability tier must agree to share resources between D3D devices.
if (GetFormat().IsMultiPlanar()) {
DAWN_TRY(ValidateD3D12VideoTextureCanBeShared(ToBackend(GetDevice()),
D3D12TextureFormat(descriptor->format)));
}
ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex;
DAWN_TRY_ASSIGN(dxgiKeyedMutex,
dawnDevice->CreateKeyedMutexForTexture(d3d12Resource.Get()));
@ -1039,6 +1081,12 @@ namespace dawn_native { namespace d3d12 {
// sampled.
mSrvDesc.Format = DXGI_FORMAT_UNKNOWN;
break;
// Depth formats cannot use plane aspects.
case wgpu::TextureAspect::Plane0Only:
case wgpu::TextureAspect::Plane1Only:
UNREACHABLE();
break;
}
break;
default:
@ -1047,6 +1095,12 @@ namespace dawn_native { namespace d3d12 {
}
}
// Per plane view formats must have the plane slice number be the index of the plane in the
// array of textures.
if (texture->GetFormat().IsMultiPlanar()) {
planeSlice = GetAspectIndex(ConvertViewAspect(GetFormat(), descriptor->aspect));
}
// Currently we always use D3D12_TEX2D_ARRAY_SRV because we cannot specify base array layer
// and layer count in D3D12_TEX2D_SRV. For 2D texture views, we treat them as 1-layer 2D
// array textures.

View File

@ -307,6 +307,8 @@ namespace dawn_native { namespace opengl {
case Aspect::None:
case Aspect::Color:
case Aspect::CombinedDepthStencil:
case Aspect::Plane0:
case Aspect::Plane1:
UNREACHABLE();
case Aspect::Depth:
gl.TexParameteri(target, GL_DEPTH_STENCIL_TEXTURE_MODE,
@ -483,6 +485,8 @@ namespace dawn_native { namespace opengl {
break;
case Aspect::CombinedDepthStencil:
case Aspect::None:
case Aspect::Plane0:
case Aspect::Plane1:
UNREACHABLE();
}
if (srcTexture->GetArrayLayers() == 1) {
@ -795,6 +799,8 @@ namespace dawn_native { namespace opengl {
case Aspect::CombinedDepthStencil:
case Aspect::None:
case Aspect::Plane0:
case Aspect::Plane1:
UNREACHABLE();
}

View File

@ -338,7 +338,7 @@ namespace dawn_native { namespace vulkan {
return VK_FORMAT_BC7_UNORM_BLOCK;
case wgpu::TextureFormat::BC7RGBAUnormSrgb:
return VK_FORMAT_BC7_SRGB_BLOCK;
case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
case wgpu::TextureFormat::Undefined:
UNREACHABLE();
}
@ -757,6 +757,9 @@ namespace dawn_native { namespace vulkan {
case wgpu::TextureAspect::StencilOnly:
ASSERT(GetFormat().aspects & Aspect::Stencil);
return VulkanAspectMask(Aspect::Stencil);
case wgpu::TextureAspect::Plane0Only:
case wgpu::TextureAspect::Plane1Only:
UNREACHABLE();
}
}

View File

@ -65,6 +65,8 @@ namespace dawn_native { namespace vulkan {
flags |= VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
break;
case Aspect::Plane0:
case Aspect::Plane1:
case Aspect::None:
UNREACHABLE();
}

View File

@ -215,6 +215,7 @@ test("dawn_unittests") {
"unittests/validation/ValidationTest.h",
"unittests/validation/VertexBufferValidationTests.cpp",
"unittests/validation/VertexStateValidationTests.cpp",
"unittests/validation/VideoViewsValidationTests.cpp",
"unittests/wire/WireArgumentTests.cpp",
"unittests/wire/WireBasicTests.cpp",
"unittests/wire/WireBufferMappingTests.cpp",
@ -345,6 +346,7 @@ source_set("dawn_end2end_tests_sources") {
sources += [
"end2end/D3D12CachingTests.cpp",
"end2end/D3D12ResourceWrappingTests.cpp",
"end2end/D3D12VideoViewsTests.cpp",
]
libs += [
"d3d11.lib",

View File

@ -0,0 +1,432 @@
// Copyright 2021 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "tests/DawnTest.h"
#include <d3d11.h>
#include <d3d12.h>
#include <dxgi1_4.h>
#include <wrl/client.h>
#include "dawn_native/D3D12Backend.h"
#include "utils/ComboRenderPipelineDescriptor.h"
#include "utils/WGPUHelpers.h"
using Microsoft::WRL::ComPtr;
namespace {
class D3D12VideoViewsTests : public DawnTest {
protected:
void SetUp() override {
DawnTest::SetUp();
DAWN_SKIP_TEST_IF(UsesWire());
DAWN_SKIP_TEST_IF(!IsMultiPlanarFormatsSupported());
// Create the D3D11 device/contexts that will be used in subsequent tests
ComPtr<ID3D12Device> d3d12Device = dawn_native::d3d12::GetD3D12Device(device.Get());
const LUID adapterLuid = d3d12Device->GetAdapterLuid();
ComPtr<IDXGIFactory4> dxgiFactory;
HRESULT hr = ::CreateDXGIFactory2(0, IID_PPV_ARGS(&dxgiFactory));
ASSERT_EQ(hr, S_OK);
ComPtr<IDXGIAdapter> dxgiAdapter;
hr = dxgiFactory->EnumAdapterByLuid(adapterLuid, IID_PPV_ARGS(&dxgiAdapter));
ASSERT_EQ(hr, S_OK);
ComPtr<ID3D11Device> d3d11Device;
D3D_FEATURE_LEVEL d3dFeatureLevel;
ComPtr<ID3D11DeviceContext> d3d11DeviceContext;
hr = ::D3D11CreateDevice(dxgiAdapter.Get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr, 0,
nullptr, 0, D3D11_SDK_VERSION, &d3d11Device, &d3dFeatureLevel,
&d3d11DeviceContext);
ASSERT_EQ(hr, S_OK);
// Runtime of the created texture (D3D11 device) and OpenSharedHandle runtime (Dawn's
// D3D12 device) must agree on resource sharing capability. For NV12 formats, D3D11
// requires at-least D3D11_SHARED_RESOURCE_TIER_2 support.
// https://docs.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_shared_resource_tier
D3D11_FEATURE_DATA_D3D11_OPTIONS5 featureOptions5{};
hr = d3d11Device->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS5, &featureOptions5,
sizeof(featureOptions5));
ASSERT_EQ(hr, S_OK);
ASSERT_GE(featureOptions5.SharedResourceTier, D3D11_SHARED_RESOURCE_TIER_2);
mD3d11Device = std::move(d3d11Device);
}
std::vector<const char*> GetRequiredExtensions() override {
mIsMultiPlanarFormatsSupported = SupportsExtensions({"multiplanar_formats"});
if (!mIsMultiPlanarFormatsSupported) {
return {};
}
return {"multiplanar_formats"};
}
bool IsMultiPlanarFormatsSupported() const {
return mIsMultiPlanarFormatsSupported;
}
static DXGI_FORMAT GetDXGITextureFormat(wgpu::TextureFormat format) {
switch (format) {
case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
return DXGI_FORMAT_NV12;
default:
UNREACHABLE();
return DXGI_FORMAT_UNKNOWN;
}
}
// Returns a pre-prepared multi-planar formatted texture
// The encoded texture data represents a 4x4 converted image. When |isCheckerboard| is true,
// the upper left and bottom right fill a 2x2 grey block, from RGB(128, 128, 128), while the
// upper right and bottom left fill a 2x2 white block, from RGB(255, 255, 255). When
// |isCheckerboard| is false, the image is converted from a solid grey 4x4 block.
static std::vector<uint8_t> GetTestTextureData(wgpu::TextureFormat format,
bool isCheckerboard) {
constexpr uint8_t Y1 = kGreyYUVColor[kYUVLumaPlaneIndex].r;
constexpr uint8_t U1 = kGreyYUVColor[kYUVChromaPlaneIndex].r;
constexpr uint8_t V1 = kGreyYUVColor[kYUVChromaPlaneIndex].g;
switch (format) {
// The first 16 bytes is the luma plane (Y), followed by the chroma plane (UV) which
// is half the number of bytes (subsampled by 2) but same bytes per line as luma
// plane.
case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
if (isCheckerboard) {
constexpr uint8_t Y2 = kWhiteYUVColor[kYUVLumaPlaneIndex].r;
constexpr uint8_t U2 = kWhiteYUVColor[kYUVChromaPlaneIndex].r;
constexpr uint8_t V2 = kWhiteYUVColor[kYUVChromaPlaneIndex].g;
// clang-format off
return {
Y2, Y2, Y1, Y1, // plane 0, start + 0
Y2, Y2, Y1, Y1,
Y1, Y1, Y2, Y2,
Y1, Y1, Y2, Y2,
U1, V1, U2, V2, // plane 1, start + 16
U2, V2, U1, V1,
};
// clang-format on
} else {
// clang-format off
return {
Y1, Y1, Y1, Y1, // plane 0, start + 0
Y1, Y1, Y1, Y1,
Y1, Y1, Y1, Y1,
Y1, Y1, Y1, Y1,
U1, V1, U1, V1, // plane 1, start + 16
U1, V1, U1, V1,
};
// clang-format on
}
default:
UNREACHABLE();
return {};
}
}
wgpu::Texture CreateVideoTextureForTest(wgpu::TextureFormat format,
wgpu::TextureUsage usage,
bool isCheckerboard = false) {
wgpu::TextureDescriptor textureDesc;
textureDesc.format = format;
textureDesc.dimension = wgpu::TextureDimension::e2D;
textureDesc.usage = usage;
textureDesc.size = {kYUVImageDataWidthInTexels, kYUVImageDataHeightInTexels, 1};
// Create a DX11 texture with data then wrap it in a shared handle.
D3D11_TEXTURE2D_DESC d3dDescriptor;
d3dDescriptor.Width = kYUVImageDataWidthInTexels;
d3dDescriptor.Height = kYUVImageDataHeightInTexels;
d3dDescriptor.MipLevels = 1;
d3dDescriptor.ArraySize = 1;
d3dDescriptor.Format = GetDXGITextureFormat(format);
d3dDescriptor.SampleDesc.Count = 1;
d3dDescriptor.SampleDesc.Quality = 0;
d3dDescriptor.Usage = D3D11_USAGE_DEFAULT;
d3dDescriptor.BindFlags = D3D11_BIND_SHADER_RESOURCE;
d3dDescriptor.CPUAccessFlags = 0;
d3dDescriptor.MiscFlags =
D3D11_RESOURCE_MISC_SHARED_NTHANDLE | D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
std::vector<uint8_t> initialData = GetTestTextureData(format, isCheckerboard);
D3D11_SUBRESOURCE_DATA subres;
subres.pSysMem = initialData.data();
subres.SysMemPitch = kYUVImageDataWidthInTexels;
ComPtr<ID3D11Texture2D> d3d11Texture;
HRESULT hr = mD3d11Device->CreateTexture2D(&d3dDescriptor, &subres, &d3d11Texture);
EXPECT_EQ(hr, S_OK);
ComPtr<IDXGIResource1> dxgiResource;
hr = d3d11Texture.As(&dxgiResource);
EXPECT_EQ(hr, S_OK);
HANDLE sharedHandle;
hr = dxgiResource->CreateSharedHandle(
nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, nullptr,
&sharedHandle);
EXPECT_EQ(hr, S_OK);
dawn_native::d3d12::ExternalImageDescriptorDXGISharedHandle externDesc;
externDesc.cTextureDescriptor =
reinterpret_cast<const WGPUTextureDescriptor*>(&textureDesc);
externDesc.sharedHandle = sharedHandle;
externDesc.acquireMutexKey = 1;
externDesc.isInitialized = true;
// DX11 texture should be initialized upon CreateTexture2D. However, if we do not
// acquire/release the keyed mutex before using the wrapped WebGPU texture, the WebGPU
// texture is left uninitialized. This is required for D3D11 and D3D12 interop.
ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex;
hr = d3d11Texture.As(&dxgiKeyedMutex);
EXPECT_EQ(hr, S_OK);
hr = dxgiKeyedMutex->AcquireSync(0, INFINITE);
EXPECT_EQ(hr, S_OK);
hr = dxgiKeyedMutex->ReleaseSync(1);
EXPECT_EQ(hr, S_OK);
// Open the DX11 texture in Dawn from the shared handle and return it as a WebGPU
// texture.
wgpu::Texture wgpuTexture = wgpu::Texture::Acquire(
dawn_native::d3d12::WrapSharedHandle(device.Get(), &externDesc));
// Handle is no longer needed once resources are created.
::CloseHandle(sharedHandle);
return wgpuTexture;
}
// Vertex shader used to render a sampled texture into a quad.
wgpu::ShaderModule GetTestVertexShaderModule() const {
return utils::CreateShaderModuleFromWGSL(device, R"(
[[builtin(position)]] var<out> Position : vec4<f32>;
[[location(0)]] var<out> texCoord : vec2 <f32>;
[[builtin(vertex_index)]] var<in> VertexIndex : u32;
[[stage(vertex)]] fn main() -> void {
const pos : array<vec2<f32>, 6> = array<vec2<f32>, 6>(
vec2<f32>(-1.0, 1.0),
vec2<f32>(-1.0, -1.0),
vec2<f32>(1.0, -1.0),
vec2<f32>(-1.0, 1.0),
vec2<f32>(1.0, -1.0),
vec2<f32>(1.0, 1.0)
);
Position = vec4<f32>(pos[VertexIndex], 0.0, 1.0);
texCoord = vec2<f32>(Position.xy * 0.5) + vec2<f32>(0.5, 0.5);
})");
}
// The width and height in texels are 4 for all YUV formats.
static constexpr uint32_t kYUVImageDataWidthInTexels = 4;
static constexpr uint32_t kYUVImageDataHeightInTexels = 4;
static constexpr size_t kYUVLumaPlaneIndex = 0;
static constexpr size_t kYUVChromaPlaneIndex = 1;
// RGB colors converted into YUV (per plane), for testing.
static constexpr std::array<RGBA8, 2> kGreyYUVColor = {RGBA8{126, 0, 0, 0xFF}, // Y
RGBA8{128, 128, 0, 0xFF}}; // UV
static constexpr std::array<RGBA8, 2> kWhiteYUVColor = {RGBA8{235, 0, 0, 0xFF}, // Y
RGBA8{128, 128, 0, 0xFF}}; // UV
ComPtr<ID3D11Device> mD3d11Device;
bool mIsMultiPlanarFormatsSupported = false;
};
} // 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(D3D12VideoViewsTests, NV12SampleYtoR) {
wgpu::Texture wgpuTexture = CreateVideoTextureForTest(
wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureUsage::Sampled);
wgpu::TextureViewDescriptor viewDesc;
viewDesc.aspect = wgpu::TextureAspect::Plane0Only;
wgpu::TextureView textureView = wgpuTexture.CreateView(&viewDesc);
utils::ComboRenderPipelineDescriptor renderPipelineDescriptor(device);
renderPipelineDescriptor.vertexStage.module = GetTestVertexShaderModule();
renderPipelineDescriptor.cFragmentStage.module = utils::CreateShaderModuleFromWGSL(device, R"(
[[set(0), binding(0)]] var<uniform_constant> sampler0 : sampler;
[[set(0), binding(1)]] var<uniform_constant> texture : texture_2d<f32>;
[[location(0)]] var<in> texCoord : vec2<f32>;
[[location(0)]] var<out> fragColor : vec4<f32>;
[[stage(fragment)]] fn main() -> void {
var y : f32 = textureSample(texture, sampler0, texCoord).r;
fragColor = vec4<f32>(y, 0.0, 0.0, 1.0);
})");
utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(
device, kYUVImageDataWidthInTexels, kYUVImageDataHeightInTexels);
renderPipelineDescriptor.cColorStates[0].format = renderPass.colorFormat;
renderPipelineDescriptor.primitiveTopology = wgpu::PrimitiveTopology::TriangleList;
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, textureView}}));
pass.Draw(6);
pass.EndPass();
}
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
// Test the luma plane in the top left corner of grey RGB image.
EXPECT_PIXEL_RGBA8_EQ(kGreyYUVColor[kYUVLumaPlaneIndex], renderPass.color, 0, 0);
}
// Samples the chrominance (UV) plane from an imported texture into two channels of an RGBA output
// attachment and checks for the expected pixel value in the rendered quad.
TEST_P(D3D12VideoViewsTests, NV12SampleUVtoRG) {
wgpu::Texture wgpuTexture = CreateVideoTextureForTest(
wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureUsage::Sampled);
wgpu::TextureViewDescriptor viewDesc;
viewDesc.aspect = wgpu::TextureAspect::Plane1Only;
wgpu::TextureView textureView = wgpuTexture.CreateView(&viewDesc);
utils::ComboRenderPipelineDescriptor renderPipelineDescriptor(device);
renderPipelineDescriptor.vertexStage.module = GetTestVertexShaderModule();
renderPipelineDescriptor.cFragmentStage.module = utils::CreateShaderModuleFromWGSL(device, R"(
[[set(0), binding(0)]] var<uniform_constant> sampler0 : sampler;
[[set(0), binding(1)]] var<uniform_constant> texture : texture_2d<f32>;
[[location(0)]] var<in> texCoord : vec2<f32>;
[[location(0)]] var<out> fragColor : vec4<f32>;
[[stage(fragment)]] fn main() -> void {
var u : f32 = textureSample(texture, sampler0, texCoord).r;
var v : f32 = textureSample(texture, sampler0, texCoord).g;
fragColor = vec4<f32>(u, v, 0.0, 1.0);
})");
utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(
device, kYUVImageDataWidthInTexels, kYUVImageDataHeightInTexels);
renderPipelineDescriptor.cColorStates[0].format = renderPass.colorFormat;
renderPipelineDescriptor.primitiveTopology = wgpu::PrimitiveTopology::TriangleList;
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, textureView}}));
pass.Draw(6);
pass.EndPass();
}
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
// Test the chroma plane in the top left corner of grey RGB image.
EXPECT_PIXEL_RGBA8_EQ(kGreyYUVColor[kYUVChromaPlaneIndex], renderPass.color, 0, 0);
}
// Renders a NV12 "checkerboard" texture into a RGB quad then checks the color at specific
// points to ensure the image has not been flipped.
TEST_P(D3D12VideoViewsTests, NV12SampleYUVtoRGB) {
wgpu::Texture wgpuTexture = CreateVideoTextureForTest(
wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureUsage::Sampled, true);
wgpu::TextureViewDescriptor lumaViewDesc;
lumaViewDesc.aspect = wgpu::TextureAspect::Plane0Only;
wgpu::TextureView lumaTextureView = wgpuTexture.CreateView(&lumaViewDesc);
wgpu::TextureViewDescriptor chromaViewDesc;
chromaViewDesc.aspect = wgpu::TextureAspect::Plane1Only;
wgpu::TextureView chromaTextureView = wgpuTexture.CreateView(&chromaViewDesc);
utils::ComboRenderPipelineDescriptor renderPipelineDescriptor(device);
renderPipelineDescriptor.vertexStage.module = GetTestVertexShaderModule();
renderPipelineDescriptor.cFragmentStage.module = utils::CreateShaderModuleFromWGSL(device, R"(
[[set(0), binding(0)]] var<uniform_constant> sampler0 : sampler;
[[set(0), binding(1)]] var<uniform_constant> lumaTexture : texture_2d<f32>;
[[set(0), binding(2)]] var<uniform_constant> chromaTexture : texture_2d<f32>;
[[location(0)]] var<in> texCoord : vec2<f32>;
[[location(0)]] var<out> fragColor : vec4<f32>;
[[stage(fragment)]] fn main() -> void {
var y : f32 = textureSample(lumaTexture, sampler0, texCoord).r;
var u : f32 = textureSample(chromaTexture, sampler0, texCoord).r;
var v : f32 = textureSample(chromaTexture, sampler0, texCoord).g;
fragColor = vec4<f32>(y, u, v, 1.0);
})");
utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(
device, kYUVImageDataWidthInTexels, kYUVImageDataHeightInTexels);
renderPipelineDescriptor.cColorStates[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.EndPass();
}
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
// Test four corners of the grey-white checkerboard image (YUV color space).
RGBA8 greyYUV(kGreyYUVColor[kYUVLumaPlaneIndex].r, kGreyYUVColor[kYUVChromaPlaneIndex].r,
kGreyYUVColor[kYUVChromaPlaneIndex].g, 0xFF);
EXPECT_PIXEL_RGBA8_EQ(greyYUV, renderPass.color, 0, 0); // top left
EXPECT_PIXEL_RGBA8_EQ(greyYUV, renderPass.color, kYUVImageDataWidthInTexels - 1,
kYUVImageDataHeightInTexels - 1); // bottom right
RGBA8 whiteYUV(kWhiteYUVColor[kYUVLumaPlaneIndex].r, kWhiteYUVColor[kYUVChromaPlaneIndex].r,
kWhiteYUVColor[kYUVChromaPlaneIndex].g, 0xFF);
EXPECT_PIXEL_RGBA8_EQ(whiteYUV, renderPass.color, kYUVImageDataWidthInTexels - 1,
0); // top right
EXPECT_PIXEL_RGBA8_EQ(whiteYUV, renderPass.color, 0,
kYUVImageDataHeightInTexels - 1); // bottom left
}
DAWN_INSTANTIATE_TEST(D3D12VideoViewsTests, D3D12Backend());

View File

@ -0,0 +1,341 @@
// Copyright 2021 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "tests/unittests/validation/ValidationTest.h"
#include "utils/WGPUHelpers.h"
namespace {
class VideoViewsValidation : public ValidationTest {
protected:
WGPUDevice CreateTestDevice() override {
dawn_native::DeviceDescriptor descriptor;
descriptor.requiredExtensions = {"multiplanar_formats"};
return adapter.CreateDevice(&descriptor);
}
wgpu::Texture CreateVideoTextureForTest(wgpu::TextureFormat format,
wgpu::TextureUsage usage) {
wgpu::TextureDescriptor descriptor;
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.size.width = 1;
descriptor.size.height = 1;
descriptor.format = format;
descriptor.usage = usage;
return device.CreateTexture(&descriptor);
}
};
// Test texture views compatibility rules.
TEST_F(VideoViewsValidation, CreateViewFails) {
wgpu::Texture videoTexture = CreateVideoTextureForTest(
wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureUsage::None);
// Create a default view with no plane selected.
ASSERT_DEVICE_ERROR(videoTexture.CreateView());
wgpu::TextureViewDescriptor viewDesc = {};
// Correct plane index but incompatible view format.
viewDesc.format = wgpu::TextureFormat::R8Uint;
viewDesc.aspect = wgpu::TextureAspect::Plane0Only;
ASSERT_DEVICE_ERROR(videoTexture.CreateView(&viewDesc));
// Compatible view format but wrong plane index.
viewDesc.format = wgpu::TextureFormat::R8Unorm;
viewDesc.aspect = wgpu::TextureAspect::Plane1Only;
ASSERT_DEVICE_ERROR(videoTexture.CreateView(&viewDesc));
// Compatible view format but wrong aspect.
viewDesc.format = wgpu::TextureFormat::R8Unorm;
viewDesc.aspect = wgpu::TextureAspect::All;
ASSERT_DEVICE_ERROR(videoTexture.CreateView(&viewDesc));
// Create a single plane texture.
wgpu::TextureDescriptor desc;
desc.format = wgpu::TextureFormat::RGBA8Unorm;
desc.dimension = wgpu::TextureDimension::e2D;
desc.usage = wgpu::TextureUsage::None;
desc.size = {1, 1, 1};
wgpu::Texture texture = device.CreateTexture(&desc);
// Plane aspect specified with non-planar texture.
viewDesc.aspect = wgpu::TextureAspect::Plane0Only;
ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
viewDesc.aspect = wgpu::TextureAspect::Plane1Only;
ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
// Planar views with non-planar texture.
viewDesc.aspect = wgpu::TextureAspect::Plane0Only;
viewDesc.format = wgpu::TextureFormat::R8Unorm;
ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
viewDesc.aspect = wgpu::TextureAspect::Plane1Only;
viewDesc.format = wgpu::TextureFormat::RG8Unorm;
ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
}
// Test texture views compatibility rules.
TEST_F(VideoViewsValidation, CreateViewSucceeds) {
wgpu::Texture yuvTexture = CreateVideoTextureForTest(
wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureUsage::None);
// Per plane view formats unspecified.
wgpu::TextureViewDescriptor planeViewDesc = {};
planeViewDesc.aspect = wgpu::TextureAspect::Plane0Only;
wgpu::TextureView plane0View = yuvTexture.CreateView(&planeViewDesc);
planeViewDesc.aspect = wgpu::TextureAspect::Plane1Only;
wgpu::TextureView plane1View = yuvTexture.CreateView(&planeViewDesc);
ASSERT_NE(plane0View.Get(), nullptr);
ASSERT_NE(plane1View.Get(), nullptr);
// Per plane view formats specified.
planeViewDesc.aspect = wgpu::TextureAspect::Plane0Only;
planeViewDesc.format = wgpu::TextureFormat::R8Unorm;
plane0View = yuvTexture.CreateView(&planeViewDesc);
planeViewDesc.aspect = wgpu::TextureAspect::Plane1Only;
planeViewDesc.format = wgpu::TextureFormat::RG8Unorm;
plane1View = yuvTexture.CreateView(&planeViewDesc);
ASSERT_NE(plane0View.Get(), nullptr);
ASSERT_NE(plane1View.Get(), nullptr);
}
// Test copying from one multi-planar format into another fails.
TEST_F(VideoViewsValidation, T2TCopyAllAspectsFails) {
wgpu::Texture srcTexture = CreateVideoTextureForTest(
wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureUsage::Sampled);
wgpu::Texture dstTexture = CreateVideoTextureForTest(
wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureUsage::Sampled);
wgpu::TextureCopyView srcCopyView = utils::CreateTextureCopyView(srcTexture, 0, {0, 0, 0});
wgpu::TextureCopyView dstCopyView = utils::CreateTextureCopyView(dstTexture, 0, {0, 0, 0});
wgpu::Extent3D copySize = {1, 1, 1};
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyTextureToTexture(&srcCopyView, &dstCopyView, &copySize);
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Test copying from one multi-planar format into another per plane fails.
TEST_F(VideoViewsValidation, T2TCopyPlaneAspectFails) {
wgpu::Texture srcTexture = CreateVideoTextureForTest(
wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureUsage::Sampled);
wgpu::Texture dstTexture = CreateVideoTextureForTest(
wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureUsage::Sampled);
wgpu::TextureCopyView srcCopyView =
utils::CreateTextureCopyView(srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::Plane0Only);
wgpu::TextureCopyView dstCopyView =
utils::CreateTextureCopyView(dstTexture, 0, {0, 0, 0}, wgpu::TextureAspect::Plane1Only);
wgpu::Extent3D copySize = {1, 1, 1};
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyTextureToTexture(&srcCopyView, &dstCopyView, &copySize);
ASSERT_DEVICE_ERROR(encoder.Finish());
}
srcCopyView =
utils::CreateTextureCopyView(srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::Plane1Only);
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyTextureToTexture(&srcCopyView, &dstCopyView, &copySize);
ASSERT_DEVICE_ERROR(encoder.Finish());
}
}
// Test copying from a multi-planar format to a buffer fails.
TEST_F(VideoViewsValidation, T2BCopyAllAspectsFails) {
wgpu::Texture srcTexture = CreateVideoTextureForTest(
wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureUsage::Sampled);
wgpu::BufferDescriptor bufferDescriptor;
bufferDescriptor.size = 1;
bufferDescriptor.usage = wgpu::BufferUsage::CopyDst;
wgpu::Buffer dstBuffer = device.CreateBuffer(&bufferDescriptor);
wgpu::TextureCopyView srcCopyView = utils::CreateTextureCopyView(srcTexture, 0, {0, 0, 0});
wgpu::BufferCopyView dstCopyView = utils::CreateBufferCopyView(dstBuffer, 0, 4);
wgpu::Extent3D copySize = {1, 1, 1};
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyTextureToBuffer(&srcCopyView, &dstCopyView, &copySize);
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Test copying from multi-planar format per plane to a buffer fails.
TEST_F(VideoViewsValidation, T2BCopyPlaneAspectsFails) {
wgpu::Texture srcTexture = CreateVideoTextureForTest(
wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureUsage::Sampled);
wgpu::BufferDescriptor bufferDescriptor;
bufferDescriptor.size = 1;
bufferDescriptor.usage = wgpu::BufferUsage::CopyDst;
wgpu::Buffer dstBuffer = device.CreateBuffer(&bufferDescriptor);
wgpu::TextureCopyView srcCopyView =
utils::CreateTextureCopyView(srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::Plane0Only);
wgpu::BufferCopyView dstCopyView = utils::CreateBufferCopyView(dstBuffer, 0, 4);
wgpu::Extent3D copySize = {1, 1, 1};
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyTextureToBuffer(&srcCopyView, &dstCopyView, &copySize);
ASSERT_DEVICE_ERROR(encoder.Finish());
}
srcCopyView =
utils::CreateTextureCopyView(srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::Plane1Only);
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyTextureToBuffer(&srcCopyView, &dstCopyView, &copySize);
ASSERT_DEVICE_ERROR(encoder.Finish());
}
}
// Test copying from a buffer to a multi-planar format fails.
TEST_F(VideoViewsValidation, B2TCopyAllAspectsFails) {
std::vector<uint8_t> dummyData(4, 0);
wgpu::Buffer srcBuffer = utils::CreateBufferFromData(
device, dummyData.data(), dummyData.size(), wgpu::BufferUsage::CopySrc);
wgpu::Texture dstTexture = CreateVideoTextureForTest(
wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureUsage::Sampled);
wgpu::BufferCopyView srcCopyView = utils::CreateBufferCopyView(srcBuffer, 0, 12, 4);
wgpu::TextureCopyView dstCopyView = utils::CreateTextureCopyView(dstTexture, 0, {0, 0, 0});
wgpu::Extent3D copySize = {1, 1, 1};
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyBufferToTexture(&srcCopyView, &dstCopyView, &copySize);
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Test copying from a buffer to a multi-planar format per plane fails.
TEST_F(VideoViewsValidation, B2TCopyPlaneAspectsFails) {
std::vector<uint8_t> dummyData(4, 0);
wgpu::Buffer srcBuffer = utils::CreateBufferFromData(
device, dummyData.data(), dummyData.size(), wgpu::BufferUsage::CopySrc);
wgpu::Texture dstTexture = CreateVideoTextureForTest(
wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureUsage::Sampled);
wgpu::BufferCopyView srcCopyView = utils::CreateBufferCopyView(srcBuffer, 0, 12, 4);
wgpu::TextureCopyView dstCopyView =
utils::CreateTextureCopyView(dstTexture, 0, {0, 0, 0}, wgpu::TextureAspect::Plane0Only);
wgpu::Extent3D copySize = {1, 1, 1};
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyBufferToTexture(&srcCopyView, &dstCopyView, &copySize);
ASSERT_DEVICE_ERROR(encoder.Finish());
}
dstCopyView =
utils::CreateTextureCopyView(dstTexture, 0, {0, 0, 0}, wgpu::TextureAspect::Plane1Only);
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyBufferToTexture(&srcCopyView, &dstCopyView, &copySize);
ASSERT_DEVICE_ERROR(encoder.Finish());
}
}
// Tests which multi-planar formats are allowed to be sampled.
TEST_F(VideoViewsValidation, SamplingMultiPlanarTexture) {
wgpu::BindGroupLayout layout = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float}});
// R8BG8Biplanar420Unorm is allowed to be sampled, if plane 0 or plane 1 is selected.
wgpu::Texture texture = CreateVideoTextureForTest(
wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureUsage::Sampled);
wgpu::TextureViewDescriptor desc = {};
desc.aspect = wgpu::TextureAspect::Plane0Only;
utils::MakeBindGroup(device, layout, {{0, texture.CreateView(&desc)}});
desc.aspect = wgpu::TextureAspect::Plane1Only;
utils::MakeBindGroup(device, layout, {{0, texture.CreateView(&desc)}});
}
// Tests creating a texture with a multi-plane format.
TEST_F(VideoViewsValidation, CreateTextureFails) {
// multi-planar formats are NOT allowed to be renderable.
ASSERT_DEVICE_ERROR(CreateVideoTextureForTest(wgpu::TextureFormat::R8BG8Biplanar420Unorm,
wgpu::TextureUsage::RenderAttachment));
}
// Tests writing into a multi-planar format fails.
TEST_F(VideoViewsValidation, WriteTextureAllAspectsFails) {
wgpu::Texture texture = CreateVideoTextureForTest(
wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureUsage::Sampled);
wgpu::TextureDataLayout textureDataLayout = utils::CreateTextureDataLayout(0, 4, 4);
wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(texture, 0, {0, 0, 0});
std::vector<uint8_t> dummyData(4, 0);
wgpu::Extent3D writeSize = {1, 1, 1};
wgpu::Queue queue = device.GetQueue();
ASSERT_DEVICE_ERROR(queue.WriteTexture(&textureCopyView, dummyData.data(), dummyData.size(),
&textureDataLayout, &writeSize));
}
// Tests writing into a multi-planar format per plane fails.
TEST_F(VideoViewsValidation, WriteTexturePlaneAspectsFails) {
wgpu::Texture texture = CreateVideoTextureForTest(
wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureUsage::Sampled);
wgpu::TextureDataLayout textureDataLayout = utils::CreateTextureDataLayout(0, 12, 4);
wgpu::TextureCopyView textureCopyView =
utils::CreateTextureCopyView(texture, 0, {0, 0, 0}, wgpu::TextureAspect::Plane0Only);
std::vector<uint8_t> dummmyData(4, 0);
wgpu::Extent3D writeSize = {1, 1, 1};
wgpu::Queue queue = device.GetQueue();
ASSERT_DEVICE_ERROR(queue.WriteTexture(&textureCopyView, dummmyData.data(),
dummmyData.size(), &textureDataLayout, &writeSize));
}
} // anonymous namespace

View File

@ -109,6 +109,10 @@ namespace utils {
case wgpu::TextureFormat::Depth24Plus:
case wgpu::TextureFormat::Depth24PlusStencil8:
// Block size of a multi-planar format depends on aspect.
case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
case wgpu::TextureFormat::Undefined:
UNREACHABLE();
}
@ -173,6 +177,9 @@ namespace utils {
case wgpu::TextureFormat::BC7RGBAUnormSrgb:
return 4u;
// Block size of a multi-planar format depends on aspect.
case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
case wgpu::TextureFormat::Undefined:
UNREACHABLE();
}
@ -237,6 +244,9 @@ namespace utils {
case wgpu::TextureFormat::BC7RGBAUnormSrgb:
return 4u;
// Block size of a multi-planar format depends on aspect.
case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
case wgpu::TextureFormat::Undefined:
UNREACHABLE();
}