Support NV12 via ExternalImageDescriptorDmaBuf on CROS
On Intel platforms, all planes in fact have same dma-buf, so the
DISJOINT bit shouldn't be used to create the vkimage.
For multi-planar formats, VkImageDrmFormatModifierListCreateInfoEXT
has to be used instead of
VkImageDrmFormatModifierExplicitCreateInfoEXT.
Bug: chromium:1258986
Change-Id: I25306a438e7ba9fd981848e63068e486bbddf11d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/68961
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Jie A Chen <jie.a.chen@intel.com>
diff --git a/src/dawn_native/Texture.cpp b/src/dawn_native/Texture.cpp
index 97a6a9d..cbc81c1 100644
--- a/src/dawn_native/Texture.cpp
+++ b/src/dawn_native/Texture.cpp
@@ -250,8 +250,9 @@
"The texture usage (%s) includes %s, which is incompatible with the format (%s).",
usage, wgpu::TextureUsage::StorageBinding, format->format);
+ // Only allows simple readonly texture usages.
constexpr wgpu::TextureUsage kValidMultiPlanarUsages =
- wgpu::TextureUsage::TextureBinding;
+ wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopySrc;
DAWN_INVALID_IF(
format->IsMultiPlanar() && !IsSubset(usage, kValidMultiPlanarUsages),
"The texture usage (%s) is incompatible with the multi-planar format (%s).", usage,
diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp
index dfdef5b..7a913a2 100644
--- a/src/dawn_native/d3d12/TextureD3D12.cpp
+++ b/src/dawn_native/d3d12/TextureD3D12.cpp
@@ -1228,7 +1228,8 @@
if (texture->GetFormat().IsMultiPlanar()) {
const Aspect planeAspect = ConvertViewAspect(GetFormat(), descriptor->aspect);
planeSlice = GetAspectIndex(planeAspect);
- mSrvDesc.Format = D3D12TextureFormat(GetFormat().GetAspectInfo(planeAspect).format);
+ mSrvDesc.Format =
+ D3D12TextureFormat(texture->GetFormat().GetAspectInfo(planeAspect).format);
}
// Currently we always use D3D12_TEX2D_ARRAY_SRV because we cannot specify base array layer
diff --git a/src/dawn_native/vulkan/AdapterVk.cpp b/src/dawn_native/vulkan/AdapterVk.cpp
index 1e6595f..816efff 100644
--- a/src/dawn_native/vulkan/AdapterVk.cpp
+++ b/src/dawn_native/vulkan/AdapterVk.cpp
@@ -151,6 +151,12 @@
mSupportedFeatures.EnableFeature(Feature::TimestampQuery);
}
+#if defined(DAWN_USE_SYNC_FDS)
+ // TODO(chromium:1258986): Precisely enable the feature by querying the device's format
+ // features.
+ mSupportedFeatures.EnableFeature(Feature::MultiPlanarFormats);
+#endif
+
return {};
}
diff --git a/src/dawn_native/vulkan/TextureVk.cpp b/src/dawn_native/vulkan/TextureVk.cpp
index 73519c8..9a5000a 100644
--- a/src/dawn_native/vulkan/TextureVk.cpp
+++ b/src/dawn_native/vulkan/TextureVk.cpp
@@ -420,6 +420,8 @@
return VK_FORMAT_ASTC_12x12_SRGB_BLOCK;
case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
+ return VK_FORMAT_G8_B8R8_2PLANE_420_UNORM;
+
// TODO(dawn:666): implement stencil8
case wgpu::TextureFormat::Stencil8:
// TODO(dawn:690): implement depth24unorm-stencil8
@@ -624,10 +626,12 @@
: TextureBase(device, descriptor, state),
// A usage of none will make sure the texture is transitioned before its first use as
// required by the Vulkan spec.
- mSubresourceLastUsages(ComputeAspectsForSubresourceStorage(),
- GetArrayLayers(),
- GetNumMipLevels(),
- wgpu::TextureUsage::None) {
+ mSubresourceLastUsages(std::make_unique<SubresourceStorage<wgpu::TextureUsage>>(
+ (ShouldCombineDepthStencilBarriers() ? Aspect::CombinedDepthStencil
+ : GetFormat().aspects),
+ GetArrayLayers(),
+ GetNumMipLevels(),
+ wgpu::TextureUsage::None)) {
}
MaybeError Texture::InitializeAsInternalTexture(VkImageUsageFlags extraUsages) {
@@ -693,8 +697,16 @@
external_memory::Service* externalMemoryService) {
VkFormat format = VulkanImageFormat(ToBackend(GetDevice()), GetFormat().format);
VkImageUsageFlags usage = VulkanImageUsage(GetInternalUsage(), GetFormat());
- DAWN_INVALID_IF(!externalMemoryService->SupportsCreateImage(descriptor, format, usage),
+ DAWN_INVALID_IF(!externalMemoryService->SupportsCreateImage(descriptor, format, usage,
+ &mSupportsDisjointVkImage),
"Creating an image from external memory is not supported.");
+ // mSubresourceLastUsages was initialized with Plane0/Plane1 in the constructor for
+ // multiplanar formats, so we need to correct it to Color here.
+ if (ShouldCombineMultiPlaneBarriers()) {
+ mSubresourceLastUsages = std::make_unique<SubresourceStorage<wgpu::TextureUsage>>(
+ ComputeAspectsForSubresourceStorage(), GetArrayLayers(), GetNumMipLevels(),
+ wgpu::TextureUsage::None);
+ }
mExternalState = ExternalState::PendingAcquire;
@@ -768,14 +780,15 @@
// Release the texture
mExternalState = ExternalState::Released;
+ Aspect aspects = ComputeAspectsForSubresourceStorage();
ASSERT(GetNumMipLevels() == 1 && GetArrayLayers() == 1);
- wgpu::TextureUsage usage = mSubresourceLastUsages.Get(Aspect::Color, 0, 0);
+ wgpu::TextureUsage usage = mSubresourceLastUsages->Get(aspects, 0, 0);
VkImageMemoryBarrier barrier;
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.pNext = nullptr;
barrier.image = GetHandle();
- barrier.subresourceRange.aspectMask = VulkanAspectMask(GetFormat().aspects);
+ barrier.subresourceRange.aspectMask = VulkanAspectMask(aspects);
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
@@ -867,24 +880,6 @@
return mHandle;
}
- VkImageAspectFlags Texture::GetVkAspectMask(wgpu::TextureAspect aspect) const {
- // TODO(enga): These masks could be precomputed.
- switch (aspect) {
- case wgpu::TextureAspect::All:
- return VulkanAspectMask(GetFormat().aspects);
- case wgpu::TextureAspect::DepthOnly:
- ASSERT(GetFormat().aspects & Aspect::Depth);
- return VulkanAspectMask(Aspect::Depth);
- case wgpu::TextureAspect::StencilOnly:
- ASSERT(GetFormat().aspects & Aspect::Stencil);
- return VulkanAspectMask(Aspect::Stencil);
- case wgpu::TextureAspect::Plane0Only:
- case wgpu::TextureAspect::Plane1Only:
- break;
- }
- UNREACHABLE();
- }
-
void Texture::TweakTransitionForExternalUsage(CommandRecordingContext* recordingContext,
std::vector<VkImageMemoryBarrier>* barriers,
size_t transitionBarrierStart) {
@@ -897,9 +892,10 @@
if (mExternalState == ExternalState::PendingAcquire) {
if (barriers->size() == transitionBarrierStart) {
- barriers->push_back(BuildMemoryBarrier(
- this, wgpu::TextureUsage::None, wgpu::TextureUsage::None,
- SubresourceRange::SingleMipAndLayer(0, 0, GetFormat().aspects)));
+ barriers->push_back(
+ BuildMemoryBarrier(this, wgpu::TextureUsage::None, wgpu::TextureUsage::None,
+ SubresourceRange::SingleMipAndLayer(
+ 0, 0, ComputeAspectsForSubresourceStorage())));
}
VkImageMemoryBarrier* barrier = &(*barriers)[transitionBarrierStart];
@@ -972,14 +968,32 @@
return false;
}
+ // Base Vulkan doesn't support transitioning depth and stencil separately. We work around
+ // this limitation by combining the usages in the two planes of `textureUsages` into a
+ // single plane in a new SubresourceStorage<TextureUsage>. The barriers will be produced
+ // for DEPTH | STENCIL since the SubresourceRange uses Aspect::CombinedDepthStencil.
bool Texture::ShouldCombineDepthStencilBarriers() const {
return GetFormat().aspects == (Aspect::Depth | Aspect::Stencil);
}
+ // The Vulkan spec requires:
+ // "If image has a single-plane color format or is not disjoint, then the aspectMask member of
+ // subresourceRange must be VK_IMAGE_ASPECT_COLOR_BIT.".
+ // For multi-planar formats, we currently only support import them in non-disjoint way.
+ bool Texture::ShouldCombineMultiPlaneBarriers() const {
+ // TODO(chromium:1258986): Figure out how to support disjoint vkImage.
+ ASSERT(!mSupportsDisjointVkImage);
+ return GetFormat().aspects == (Aspect::Plane0 | Aspect::Plane1);
+ }
+
Aspect Texture::ComputeAspectsForSubresourceStorage() const {
if (ShouldCombineDepthStencilBarriers()) {
return Aspect::CombinedDepthStencil;
}
+ // Force to use Aspect::Color for Aspect::Plane0/1.
+ if (ShouldCombineMultiPlaneBarriers()) {
+ return Aspect::Color;
+ }
return GetFormat().aspects;
}
@@ -988,16 +1002,13 @@
std::vector<VkImageMemoryBarrier>* imageBarriers,
VkPipelineStageFlags* srcStages,
VkPipelineStageFlags* dstStages) {
- // Base Vulkan doesn't support transitioning depth and stencil separately. We work around
- // this limitation by combining the usages in the two planes of `textureUsages` into a
- // single plane in a new SubresourceStorage<TextureUsage>. The barriers will be produced
- // for DEPTH | STENCIL since the SubresourceRange uses Aspect::CombinedDepthStencil.
- if (ShouldCombineDepthStencilBarriers()) {
- SubresourceStorage<wgpu::TextureUsage> combinedUsages(
- Aspect::CombinedDepthStencil, GetArrayLayers(), GetNumMipLevels());
+ if (ShouldCombineBarriers()) {
+ Aspect combinedAspect = ComputeAspectsForSubresourceStorage();
+ SubresourceStorage<wgpu::TextureUsage> combinedUsages(combinedAspect, GetArrayLayers(),
+ GetNumMipLevels());
textureUsages.Iterate([&](const SubresourceRange& range, wgpu::TextureUsage usage) {
SubresourceRange updateRange = range;
- updateRange.aspects = Aspect::CombinedDepthStencil;
+ updateRange.aspects = combinedAspect;
combinedUsages.Update(
updateRange, [&](const SubresourceRange&, wgpu::TextureUsage* combinedUsage) {
@@ -1028,7 +1039,7 @@
// TODO(crbug.com/dawn/814): support 1D textures.
ASSERT(GetDimension() != wgpu::TextureDimension::e1D);
- mSubresourceLastUsages.Merge(
+ mSubresourceLastUsages->Merge(
subresourceUsages, [&](const SubresourceRange& range, wgpu::TextureUsage* lastUsage,
const wgpu::TextureUsage& newUsage) {
if (newUsage == wgpu::TextureUsage::None ||
@@ -1081,15 +1092,9 @@
std::vector<VkImageMemoryBarrier>* imageBarriers,
VkPipelineStageFlags* srcStages,
VkPipelineStageFlags* dstStages) {
- // Base Vulkan doesn't support transitioning depth and stencil separately. We work around
- // this limitation by modifying the range to be on CombinedDepthStencil. The barriers will
- // be produced for DEPTH | STENCIL since the SubresourceRange uses
- // Aspect::CombinedDepthStencil.
- if (ShouldCombineDepthStencilBarriers()) {
+ if (ShouldCombineBarriers()) {
SubresourceRange updatedRange = range;
- updatedRange.aspects = Aspect::CombinedDepthStencil;
-
- std::vector<VkImageMemoryBarrier> newBarriers;
+ updatedRange.aspects = ComputeAspectsForSubresourceStorage();
TransitionUsageAndGetResourceBarrierImpl(usage, updatedRange, imageBarriers, srcStages,
dstStages);
} else {
@@ -1108,7 +1113,7 @@
const Format& format = GetFormat();
wgpu::TextureUsage allLastUsages = wgpu::TextureUsage::None;
- mSubresourceLastUsages.Update(
+ mSubresourceLastUsages->Update(
range, [&](const SubresourceRange& range, wgpu::TextureUsage* lastUsage) {
if (CanReuseWithoutBarrier(*lastUsage, usage)) {
return;
@@ -1281,7 +1286,8 @@
}
VkImageLayout Texture::GetCurrentLayoutForSwapChain() const {
- return VulkanImageLayout(this, mSubresourceLastUsages.Get(Aspect::Color, 0, 0));
+ ASSERT(GetFormat().aspects == Aspect::Color);
+ return VulkanImageLayout(this, mSubresourceLastUsages->Get(Aspect::Color, 0, 0));
}
// static
diff --git a/src/dawn_native/vulkan/TextureVk.h b/src/dawn_native/vulkan/TextureVk.h
index 4a1745d..6083275 100644
--- a/src/dawn_native/vulkan/TextureVk.h
+++ b/src/dawn_native/vulkan/TextureVk.h
@@ -62,7 +62,6 @@
VkImage nativeImage);
VkImage GetHandle() const;
- VkImageAspectFlags GetVkAspectMask(wgpu::TextureAspect aspect) const;
// Transitions the texture to be used as `usage`, recording any necessary barrier in
// `commands`.
@@ -139,8 +138,18 @@
// indicates whether we should combine depth and stencil barriers to accommodate this
// limitation.
bool ShouldCombineDepthStencilBarriers() const;
+
+ // This indicates whether the VK_IMAGE_ASPECT_COLOR_BIT instead of
+ // VK_IMAGE_ASPECT_PLANE_n_BIT must be used.
+ bool ShouldCombineMultiPlaneBarriers() const;
+
+ bool ShouldCombineBarriers() const {
+ return ShouldCombineDepthStencilBarriers() || ShouldCombineMultiPlaneBarriers();
+ }
+
// Compute the Aspects of the SubresourceStoage for this texture depending on whether we're
- // doing the workaround for combined depth and stencil barriers.
+ // doing the workaround for combined depth and stencil barriers, or combining multi-plane
+ // barriers.
Aspect ComputeAspectsForSubresourceStorage() const;
VkImage mHandle = VK_NULL_HANDLE;
@@ -165,7 +174,9 @@
// Note that in early Vulkan versions it is not possible to transition depth and stencil
// separately so textures with Depth|Stencil aspects will have a single Depth aspect in the
// storage.
- SubresourceStorage<wgpu::TextureUsage> mSubresourceLastUsages;
+ std::unique_ptr<SubresourceStorage<wgpu::TextureUsage>> mSubresourceLastUsages;
+
+ bool mSupportsDisjointVkImage = false;
};
class TextureView final : public TextureViewBase {
diff --git a/src/dawn_native/vulkan/UtilsVulkan.cpp b/src/dawn_native/vulkan/UtilsVulkan.cpp
index 6e316c5..f7f7e4b 100644
--- a/src/dawn_native/vulkan/UtilsVulkan.cpp
+++ b/src/dawn_native/vulkan/UtilsVulkan.cpp
@@ -71,7 +71,12 @@
break;
case Aspect::Plane0:
+ flags |= VK_IMAGE_ASPECT_PLANE_0_BIT;
+ break;
case Aspect::Plane1:
+ flags |= VK_IMAGE_ASPECT_PLANE_1_BIT;
+ break;
+
case Aspect::None:
UNREACHABLE();
}
diff --git a/src/dawn_native/vulkan/external_memory/MemoryService.h b/src/dawn_native/vulkan/external_memory/MemoryService.h
index f0653f2..5844d8f 100644
--- a/src/dawn_native/vulkan/external_memory/MemoryService.h
+++ b/src/dawn_native/vulkan/external_memory/MemoryService.h
@@ -49,7 +49,8 @@
// True if the device reports it supports creating VkImages from external memory.
bool SupportsCreateImage(const ExternalImageDescriptor* descriptor,
VkFormat format,
- VkImageUsageFlags usage);
+ VkImageUsageFlags usage,
+ bool* supportsDisjoint);
// Returns the parameters required for importing memory
ResultOrError<MemoryImportParams> GetMemoryImportParams(
diff --git a/src/dawn_native/vulkan/external_memory/MemoryServiceDmaBuf.cpp b/src/dawn_native/vulkan/external_memory/MemoryServiceDmaBuf.cpp
index 90b6373..06e0bf9 100644
--- a/src/dawn_native/vulkan/external_memory/MemoryServiceDmaBuf.cpp
+++ b/src/dawn_native/vulkan/external_memory/MemoryServiceDmaBuf.cpp
@@ -17,6 +17,7 @@
#include "dawn_native/vulkan/BackendVk.h"
#include "dawn_native/vulkan/DeviceVk.h"
#include "dawn_native/vulkan/ResourceMemoryAllocatorVk.h"
+#include "dawn_native/vulkan/UtilsVulkan.h"
#include "dawn_native/vulkan/VulkanError.h"
#include "dawn_native/vulkan/external_memory/MemoryService.h"
@@ -24,40 +25,92 @@
namespace {
- // Some modifiers use multiple planes (for example, see the comment for
- // I915_FORMAT_MOD_Y_TILED_CCS in drm/drm_fourcc.h), but dma-buf import in Dawn only
- // supports single-plane formats.
- ResultOrError<uint32_t> GetModifierPlaneCount(const VulkanFunctions& fn,
- VkPhysicalDevice physicalDevice,
- VkFormat format,
- uint64_t modifier) {
- VkDrmFormatModifierPropertiesListEXT formatModifierPropsList;
- formatModifierPropsList.sType =
- VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT;
- formatModifierPropsList.pNext = nullptr;
+ bool GetFormatModifierProps(const VulkanFunctions& fn,
+ VkPhysicalDevice physicalDevice,
+ VkFormat format,
+ uint64_t modifier,
+ VkDrmFormatModifierPropertiesEXT* formatModifierProps) {
+ std::vector<VkDrmFormatModifierPropertiesEXT> formatModifierPropsVector;
+ VkFormatProperties2 formatProps = {};
+ formatProps.sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2;
+ PNextChainBuilder formatPropsChain(&formatProps);
+
+ VkDrmFormatModifierPropertiesListEXT formatModifierPropsList = {};
formatModifierPropsList.drmFormatModifierCount = 0;
formatModifierPropsList.pDrmFormatModifierProperties = nullptr;
-
- VkFormatProperties2 formatProps;
- formatProps.sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2;
- formatProps.pNext = &formatModifierPropsList;
+ formatPropsChain.Add(&formatModifierPropsList,
+ VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT);
fn.GetPhysicalDeviceFormatProperties2(physicalDevice, format, &formatProps);
uint32_t modifierCount = formatModifierPropsList.drmFormatModifierCount;
- std::vector<VkDrmFormatModifierPropertiesEXT> formatModifierProps(modifierCount);
- formatModifierPropsList.pDrmFormatModifierProperties = formatModifierProps.data();
+ formatModifierPropsVector.resize(modifierCount);
+ formatModifierPropsList.pDrmFormatModifierProperties = formatModifierPropsVector.data();
fn.GetPhysicalDeviceFormatProperties2(physicalDevice, format, &formatProps);
- for (const auto& props : formatModifierProps) {
+ for (const auto& props : formatModifierPropsVector) {
if (props.drmFormatModifier == modifier) {
- uint32_t count = props.drmFormatModifierPlaneCount;
- return count;
+ *formatModifierProps = props;
+ return true;
}
}
+ return false;
+ }
+
+ // Some modifiers use multiple planes (for example, see the comment for
+ // I915_FORMAT_MOD_Y_TILED_CCS in drm/drm_fourcc.h).
+ ResultOrError<uint32_t> GetModifierPlaneCount(const VulkanFunctions& fn,
+ VkPhysicalDevice physicalDevice,
+ VkFormat format,
+ uint64_t modifier) {
+ VkDrmFormatModifierPropertiesEXT props;
+ if (GetFormatModifierProps(fn, physicalDevice, format, modifier, &props)) {
+ return static_cast<uint32_t>(props.drmFormatModifierPlaneCount);
+ }
return DAWN_FORMAT_VALIDATION_ERROR("DRM format modifier not supported.");
}
+ bool IsMultiPlanarVkFormat(VkFormat format) {
+ switch (format) {
+ case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM:
+ case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
+ case VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM:
+ case VK_FORMAT_G8_B8R8_2PLANE_422_UNORM:
+ case VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM:
+ case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16:
+ case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16:
+ case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16:
+ case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16:
+ case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16:
+ case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16:
+ case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16:
+ case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16:
+ case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16:
+ case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16:
+ case VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM:
+ case VK_FORMAT_G16_B16R16_2PLANE_420_UNORM:
+ case VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM:
+ case VK_FORMAT_G16_B16R16_2PLANE_422_UNORM:
+ case VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ bool SupportsDisjoint(const VulkanFunctions& fn,
+ VkPhysicalDevice physicalDevice,
+ VkFormat format,
+ uint64_t modifier) {
+ if (IsMultiPlanarVkFormat(format)) {
+ VkDrmFormatModifierPropertiesEXT props;
+ return (GetFormatModifierProps(fn, physicalDevice, format, modifier, &props) &&
+ (props.drmFormatModifierTilingFeatures & VK_FORMAT_FEATURE_DISJOINT_BIT));
+ }
+ return false;
+ }
+
} // anonymous namespace
Service::Service(Device* device)
@@ -77,12 +130,16 @@
VkImageTiling tiling,
VkImageUsageFlags usage,
VkImageCreateFlags flags) {
- return mSupported;
+ return mSupported && (!IsMultiPlanarVkFormat(format) ||
+ (format == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM &&
+ mDevice->GetDeviceInfo().HasExt(DeviceExt::ImageFormatList)));
}
bool Service::SupportsCreateImage(const ExternalImageDescriptor* descriptor,
VkFormat format,
- VkImageUsageFlags usage) {
+ VkImageUsageFlags usage,
+ bool* supportsDisjoint) {
+ *supportsDisjoint = false;
// Early out before we try using extension functions
if (!mSupported) {
return false;
@@ -104,42 +161,57 @@
if (planeCount == 0) {
return false;
}
- // TODO(hob): Support multi-plane formats like I915_FORMAT_MOD_Y_TILED_CCS.
- if (planeCount > 1) {
+ // Only support the NV12 multi-planar format for now.
+ if (planeCount > 1 && format != VK_FORMAT_G8_B8R8_2PLANE_420_UNORM) {
return false;
}
+ *supportsDisjoint =
+ SupportsDisjoint(mDevice->fn, physicalDevice, format, dmaBufDescriptor->drmModifier);
// Verify that the format modifier of the external memory and the requested Vulkan format
// are actually supported together in a dma-buf import.
- VkPhysicalDeviceImageDrmFormatModifierInfoEXT drmModifierInfo;
- drmModifierInfo.sType =
- VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT;
- drmModifierInfo.pNext = nullptr;
- drmModifierInfo.drmFormatModifier = dmaBufDescriptor->drmModifier;
- drmModifierInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
-
- VkPhysicalDeviceExternalImageFormatInfo externalImageFormatInfo;
- externalImageFormatInfo.sType =
- VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO;
- externalImageFormatInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT;
- externalImageFormatInfo.pNext = &drmModifierInfo;
-
- VkPhysicalDeviceImageFormatInfo2 imageFormatInfo;
+ VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = {};
imageFormatInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2;
imageFormatInfo.format = format;
imageFormatInfo.type = VK_IMAGE_TYPE_2D;
imageFormatInfo.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT;
imageFormatInfo.usage = usage;
imageFormatInfo.flags = 0;
- imageFormatInfo.pNext = &externalImageFormatInfo;
+ PNextChainBuilder imageFormatInfoChain(&imageFormatInfo);
- VkExternalImageFormatProperties externalImageFormatProps;
- externalImageFormatProps.sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES;
- externalImageFormatProps.pNext = nullptr;
+ VkPhysicalDeviceExternalImageFormatInfo externalImageFormatInfo = {};
+ externalImageFormatInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT;
+ imageFormatInfoChain.Add(&externalImageFormatInfo,
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO);
- VkImageFormatProperties2 imageFormatProps;
+ VkPhysicalDeviceImageDrmFormatModifierInfoEXT drmModifierInfo = {};
+ drmModifierInfo.drmFormatModifier = dmaBufDescriptor->drmModifier;
+ drmModifierInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ imageFormatInfoChain.Add(
+ &drmModifierInfo, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT);
+
+ // For mutable vkimage of multi-planar format, we also need to make sure the each
+ // plane's view format can be supported.
+ std::array<VkFormat, 2> viewFormats;
+ VkImageFormatListCreateInfo imageFormatListInfo = {};
+
+ if (planeCount > 1) {
+ ASSERT(format == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM);
+ viewFormats = {VK_FORMAT_R8_UNORM, VK_FORMAT_R8G8_UNORM};
+ imageFormatListInfo.viewFormatCount = 2;
+ imageFormatListInfo.pViewFormats = viewFormats.data();
+ imageFormatInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
+ imageFormatInfoChain.Add(&imageFormatListInfo,
+ VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO);
+ }
+
+ VkImageFormatProperties2 imageFormatProps = {};
imageFormatProps.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2;
- imageFormatProps.pNext = &externalImageFormatProps;
+ PNextChainBuilder imageFormatPropsChain(&imageFormatProps);
+
+ VkExternalImageFormatProperties externalImageFormatProps = {};
+ imageFormatPropsChain.Add(&externalImageFormatProps,
+ VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES);
VkResult result = VkResult::WrapUnsafe(mDevice->fn.GetPhysicalDeviceImageFormatProperties2(
physicalDevice, &imageFormatInfo, &imageFormatProps));
@@ -172,8 +244,8 @@
// Get the valid memory types that the external memory can be imported as.
mDevice->fn.GetMemoryFdPropertiesKHR(device, VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
dmaBufDescriptor->memoryFD, &fdProperties);
- // Choose the best memory type that satisfies both the image's constraint and the import's
- // constraint.
+ // Choose the best memory type that satisfies both the image's constraint and the
+ // import's constraint.
memoryRequirements.memoryTypeBits &= fdProperties.memoryTypeBits;
int memoryTypeIndex = mDevice->GetResourceMemoryAllocator()->FindBestTypeIndex(
memoryRequirements, MemoryKind::Opaque);
@@ -190,23 +262,23 @@
VkImage image) {
DAWN_INVALID_IF(handle < 0, "Importing memory with an invalid handle.");
- VkMemoryDedicatedAllocateInfo memoryDedicatedAllocateInfo;
- memoryDedicatedAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
- memoryDedicatedAllocateInfo.pNext = nullptr;
- memoryDedicatedAllocateInfo.image = image;
- memoryDedicatedAllocateInfo.buffer = VkBuffer{};
-
- VkImportMemoryFdInfoKHR importMemoryFdInfo;
- importMemoryFdInfo.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR;
- importMemoryFdInfo.pNext = &memoryDedicatedAllocateInfo;
- importMemoryFdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
- importMemoryFdInfo.fd = handle;
-
- VkMemoryAllocateInfo memoryAllocateInfo;
+ VkMemoryAllocateInfo memoryAllocateInfo = {};
memoryAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
- memoryAllocateInfo.pNext = &importMemoryFdInfo;
memoryAllocateInfo.allocationSize = importParams.allocationSize;
memoryAllocateInfo.memoryTypeIndex = importParams.memoryTypeIndex;
+ PNextChainBuilder memoryAllocateInfoChain(&memoryAllocateInfo);
+
+ VkImportMemoryFdInfoKHR importMemoryFdInfo;
+ importMemoryFdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
+ importMemoryFdInfo.fd = handle;
+ memoryAllocateInfoChain.Add(&importMemoryFdInfo,
+ VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR);
+
+ VkMemoryDedicatedAllocateInfo memoryDedicatedAllocateInfo;
+ memoryDedicatedAllocateInfo.image = image;
+ memoryDedicatedAllocateInfo.buffer = VkBuffer{};
+ memoryAllocateInfoChain.Add(&memoryDedicatedAllocateInfo,
+ VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO);
VkDeviceMemory allocatedMemory = VK_NULL_HANDLE;
DAWN_TRY(
@@ -226,39 +298,54 @@
VkPhysicalDevice physicalDevice = ToBackend(mDevice->GetAdapter())->GetPhysicalDevice();
VkDevice device = mDevice->GetVkDevice();
- // Dawn currently doesn't support multi-plane formats, so we only need to create a single
- // VkSubresourceLayout here.
- VkSubresourceLayout planeLayout;
+ uint32_t planeCount;
+ DAWN_TRY_ASSIGN(planeCount,
+ GetModifierPlaneCount(mDevice->fn, physicalDevice, baseCreateInfo.format,
+ dmaBufDescriptor->drmModifier));
+
+ VkImageCreateInfo createInfo = baseCreateInfo;
+ createInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ createInfo.flags = 0;
+ createInfo.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT;
+ PNextChainBuilder createInfoChain(&createInfo);
+
+ VkExternalMemoryImageCreateInfo externalMemoryImageCreateInfo = {};
+ externalMemoryImageCreateInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT;
+ createInfoChain.Add(&externalMemoryImageCreateInfo,
+ VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO);
+
+ // For single plane formats.
+ VkSubresourceLayout planeLayout = {};
planeLayout.offset = 0;
planeLayout.size = 0; // VK_EXT_image_drm_format_modifier mandates size = 0.
planeLayout.rowPitch = dmaBufDescriptor->stride;
planeLayout.arrayPitch = 0; // Not an array texture
planeLayout.depthPitch = 0; // Not a depth texture
- uint32_t planeCount;
- DAWN_TRY_ASSIGN(planeCount,
- GetModifierPlaneCount(mDevice->fn, physicalDevice, baseCreateInfo.format,
- dmaBufDescriptor->drmModifier));
- ASSERT(planeCount == 1);
-
- VkImageDrmFormatModifierExplicitCreateInfoEXT explicitCreateInfo;
- explicitCreateInfo.sType =
- VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT;
- explicitCreateInfo.pNext = NULL;
+ VkImageDrmFormatModifierExplicitCreateInfoEXT explicitCreateInfo = {};
explicitCreateInfo.drmFormatModifier = dmaBufDescriptor->drmModifier;
- explicitCreateInfo.drmFormatModifierPlaneCount = planeCount;
+ explicitCreateInfo.drmFormatModifierPlaneCount = 1;
explicitCreateInfo.pPlaneLayouts = &planeLayout;
- VkExternalMemoryImageCreateInfo externalMemoryImageCreateInfo;
- externalMemoryImageCreateInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
- externalMemoryImageCreateInfo.pNext = &explicitCreateInfo;
- externalMemoryImageCreateInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT;
+ // For multi-planar formats, we can't explicitly specify VkSubresourceLayout for each plane
+ // due to the lack of knowledge about the required 'offset'. Alternatively
+ // VkImageDrmFormatModifierListCreateInfoEXT can be used to create image with the DRM format
+ // modifier.
+ VkImageDrmFormatModifierListCreateInfoEXT listCreateInfo = {};
+ listCreateInfo.drmFormatModifierCount = 1;
+ listCreateInfo.pDrmFormatModifiers = &dmaBufDescriptor->drmModifier;
- VkImageCreateInfo createInfo = baseCreateInfo;
- createInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
- createInfo.pNext = &externalMemoryImageCreateInfo;
- createInfo.flags = 0;
- createInfo.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT;
+ if (planeCount > 1) {
+ // For multi-planar formats, VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT specifies that a
+ // VkImageView can be plane's format which might differ from the image's format.
+ createInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
+ createInfoChain.Add(&listCreateInfo,
+ VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_LIST_CREATE_INFO_EXT);
+ } else {
+ createInfoChain.Add(
+ &explicitCreateInfo,
+ VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT);
+ }
// Create a new VkImage with tiling equal to the DRM format modifier.
VkImage image;
diff --git a/src/dawn_native/vulkan/external_memory/MemoryServiceNull.cpp b/src/dawn_native/vulkan/external_memory/MemoryServiceNull.cpp
index 1bb4f38..9994951 100644
--- a/src/dawn_native/vulkan/external_memory/MemoryServiceNull.cpp
+++ b/src/dawn_native/vulkan/external_memory/MemoryServiceNull.cpp
@@ -39,7 +39,9 @@
bool Service::SupportsCreateImage(const ExternalImageDescriptor* descriptor,
VkFormat format,
- VkImageUsageFlags usage) {
+ VkImageUsageFlags usage,
+ bool* supportsDisjoint) {
+ *supportsDisjoint = false;
return false;
}
diff --git a/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp b/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp
index ad2c4c2..55969f7 100644
--- a/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp
+++ b/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp
@@ -81,7 +81,9 @@
bool Service::SupportsCreateImage(const ExternalImageDescriptor* descriptor,
VkFormat format,
- VkImageUsageFlags usage) {
+ VkImageUsageFlags usage,
+ bool* supportsDisjoint) {
+ *supportsDisjoint = false;
return mSupported;
}
diff --git a/src/dawn_native/vulkan/external_memory/MemoryServiceZirconHandle.cpp b/src/dawn_native/vulkan/external_memory/MemoryServiceZirconHandle.cpp
index 9eb5b70..3b1a031 100644
--- a/src/dawn_native/vulkan/external_memory/MemoryServiceZirconHandle.cpp
+++ b/src/dawn_native/vulkan/external_memory/MemoryServiceZirconHandle.cpp
@@ -81,7 +81,9 @@
bool Service::SupportsCreateImage(const ExternalImageDescriptor* descriptor,
VkFormat format,
- VkImageUsageFlags usage) {
+ VkImageUsageFlags usage,
+ bool* supportsDisjoint) {
+ *supportsDisjoint = false;
return mSupported;
}
diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn
index ef33d9a..04ff218 100644
--- a/src/tests/BUILD.gn
+++ b/src/tests/BUILD.gn
@@ -423,7 +423,7 @@
sources += [
"end2end/D3D12CachingTests.cpp",
"end2end/D3D12ResourceWrappingTests.cpp",
- "end2end/D3D12VideoViewsTests.cpp",
+ "end2end/VideoViewsTests_win.cpp",
]
libs += [
"d3d11.lib",
@@ -448,6 +448,17 @@
]
deps += [ "${dawn_root}/src/utils:dawn_glfw" ]
}
+
+ if (dawn_enable_d3d12 || (dawn_enable_vulkan && is_chromeos)) {
+ sources += [
+ "end2end/VideoViewsTests.cpp",
+ "end2end/VideoViewsTests.h",
+ ]
+ }
+
+ if (dawn_enable_vulkan && is_chromeos) {
+ sources += [ "end2end/VideoViewsTests_gbm.cpp" ]
+ }
}
source_set("dawn_white_box_tests_sources") {
diff --git a/src/tests/end2end/D3D12VideoViewsTests.cpp b/src/tests/end2end/D3D12VideoViewsTests.cpp
deleted file mode 100644
index 28a7e39..0000000
--- a/src/tests/end2end/D3D12VideoViewsTests.cpp
+++ /dev/null
@@ -1,470 +0,0 @@
-// 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_TEST_UNSUPPORTED_IF(UsesWire());
- DAWN_TEST_UNSUPPORTED_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*> GetRequiredFeatures() override {
- mIsMultiPlanarFormatsSupported = SupportsFeatures({"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 top left is a 2x2 yellow block, bottom right is a 2x2 red block, top right is a 2x2
- // 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> 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;
-
- 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 Wy = kWhiteYUVColor[kYUVLumaPlaneIndex].r;
- constexpr uint8_t Wu = kWhiteYUVColor[kYUVChromaPlaneIndex].r;
- constexpr uint8_t Wv = kWhiteYUVColor[kYUVChromaPlaneIndex].g;
-
- constexpr uint8_t Ry = kRedYUVColor[kYUVLumaPlaneIndex].r;
- constexpr uint8_t Ru = kRedYUVColor[kYUVChromaPlaneIndex].r;
- constexpr uint8_t Rv = kRedYUVColor[kYUVChromaPlaneIndex].g;
-
- constexpr uint8_t By = kBlueYUVColor[kYUVLumaPlaneIndex].r;
- constexpr uint8_t Bu = kBlueYUVColor[kYUVChromaPlaneIndex].r;
- constexpr uint8_t Bv = kBlueYUVColor[kYUVChromaPlaneIndex].g;
-
- // clang-format off
- return {
- Wy, Wy, Ry, Ry, // plane 0, start + 0
- Wy, Wy, Ry, Ry,
- Yy, Yy, By, By,
- Yy, Yy, By, By,
- Wu, Wv, Ru, Rv, // plane 1, start + 16
- Yu, Yv, Bu, Bv,
- };
- // clang-format on
- } else {
- // clang-format off
- return {
- Yy, Yy, Yy, Yy, // plane 0, start + 0
- Yy, Yy, Yy, Yy,
- Yy, Yy, Yy, Yy,
- Yy, Yy, Yy, Yy,
- Yu, Yv, Yu, Yv, // plane 1, start + 16
- Yu, Yv, Yu, Yv,
- };
- // clang-format on
- }
- default:
- UNREACHABLE();
- return {};
- }
- }
-
- void CreateVideoTextureForTest(wgpu::TextureFormat format,
- wgpu::TextureUsage usage,
- bool isCheckerboard,
- wgpu::Texture* dawnTextureOut) {
- 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);
- ASSERT_EQ(hr, S_OK);
-
- ComPtr<IDXGIResource1> dxgiResource;
- hr = d3d11Texture.As(&dxgiResource);
- ASSERT_EQ(hr, S_OK);
-
- HANDLE sharedHandle;
- hr = dxgiResource->CreateSharedHandle(
- nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, nullptr,
- &sharedHandle);
- ASSERT_EQ(hr, S_OK);
-
- // 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);
- ASSERT_EQ(hr, S_OK);
-
- hr = dxgiKeyedMutex->AcquireSync(0, INFINITE);
- ASSERT_EQ(hr, S_OK);
-
- hr = dxgiKeyedMutex->ReleaseSync(1);
- ASSERT_EQ(hr, S_OK);
-
- // Open the DX11 texture in Dawn from the shared handle and return it as a WebGPU
- // texture.
- dawn_native::d3d12::ExternalImageDescriptorDXGISharedHandle externalImageDesc;
- externalImageDesc.cTextureDescriptor =
- reinterpret_cast<const WGPUTextureDescriptor*>(&textureDesc);
- externalImageDesc.sharedHandle = sharedHandle;
-
- std::unique_ptr<dawn_native::d3d12::ExternalImageDXGI> externalImage =
- dawn_native::d3d12::ExternalImageDXGI::Create(device.Get(), &externalImageDesc);
-
- // Handle is no longer needed once resources are created.
- ::CloseHandle(sharedHandle);
-
- dawn_native::d3d12::ExternalImageAccessDescriptorDXGIKeyedMutex externalAccessDesc;
- externalAccessDesc.acquireMutexKey = 1;
- externalAccessDesc.releaseMutexKey = 2;
- externalAccessDesc.isInitialized = true;
- externalAccessDesc.usage = static_cast<WGPUTextureUsageFlags>(textureDesc.usage);
-
- *dawnTextureOut = wgpu::Texture::Acquire(
- externalImage->ProduceTexture(device.Get(), &externalAccessDesc));
- }
-
- // Vertex shader used to render a sampled texture into a quad.
- wgpu::ShaderModule GetTestVertexShaderModule() const {
- return utils::CreateShaderModule(device, R"(
- struct VertexOut {
- [[location(0)]] texCoord : vec2 <f32>;
- [[builtin(position)]] position : vec4<f32>;
- };
-
- [[stage(vertex)]]
- fn main([[builtin(vertex_index)]] VertexIndex : u32) -> VertexOut {
- var pos = 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)
- );
- var output : VertexOut;
- output.position = vec4<f32>(pos[VertexIndex], 0.0, 1.0);
- output.texCoord = vec2<f32>(output.position.xy * 0.5) + vec2<f32>(0.5, 0.5);
- return output;
- })");
- }
-
- // 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.
- // RGB colors are mapped to the BT.601 definition of luma.
- // https://docs.microsoft.com/en-us/windows/win32/medfound/about-yuv-video
- static constexpr std::array<RGBA8, 2> kYellowYUVColor = {RGBA8{210, 0, 0, 0xFF}, // Y
- RGBA8{16, 146, 0, 0xFF}}; // UV
-
- static constexpr std::array<RGBA8, 2> kWhiteYUVColor = {RGBA8{235, 0, 0, 0xFF}, // Y
- RGBA8{128, 128, 0, 0xFF}}; // UV
-
- static constexpr std::array<RGBA8, 2> kBlueYUVColor = {RGBA8{41, 0, 0, 0xFF}, // Y
- RGBA8{240, 110, 0, 0xFF}}; // UV
-
- static constexpr std::array<RGBA8, 2> kRedYUVColor = {RGBA8{81, 0, 0, 0xFF}, // Y
- RGBA8{90, 240, 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::TextureBinding, /*isCheckerboard*/ false,
- &wgpuTexture);
- ASSERT_NE(wgpuTexture.Get(), nullptr);
-
- wgpu::TextureViewDescriptor viewDesc;
- viewDesc.aspect = wgpu::TextureAspect::Plane0Only;
- wgpu::TextureView textureView = wgpuTexture.CreateView(&viewDesc);
-
- 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 texture : texture_2d<f32>;
-
- [[stage(fragment)]]
- fn main([[location(0)]] texCoord : vec2<f32>) -> [[location(0)]] vec4<f32> {
- let y : f32 = textureSample(texture, sampler0, texCoord).r;
- return vec4<f32>(y, 0.0, 0.0, 1.0);
- })");
-
- utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(
- device, kYUVImageDataWidthInTexels, kYUVImageDataHeightInTexels);
- renderPipelineDescriptor.cTargets[0].format = renderPass.colorFormat;
- renderPipelineDescriptor.primitive.topology = 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 RGB image.
- EXPECT_PIXEL_RGBA8_EQ(kYellowYUVColor[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::TextureBinding, /*isCheckerboard*/ false,
- &wgpuTexture);
- ASSERT_NE(wgpuTexture.Get(), nullptr);
-
- wgpu::TextureViewDescriptor viewDesc;
- viewDesc.aspect = wgpu::TextureAspect::Plane1Only;
- wgpu::TextureView textureView = wgpuTexture.CreateView(&viewDesc);
-
- 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 texture : texture_2d<f32>;
-
- [[stage(fragment)]]
- fn main([[location(0)]] texCoord : vec2<f32>) -> [[location(0)]] vec4<f32> {
- let u : f32 = textureSample(texture, sampler0, texCoord).r;
- let v : f32 = textureSample(texture, sampler0, texCoord).g;
- return vec4<f32>(u, v, 0.0, 1.0);
- })");
-
- utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(
- device, kYUVImageDataWidthInTexels, kYUVImageDataHeightInTexels);
- renderPipelineDescriptor.cTargets[0].format = renderPass.colorFormat;
- renderPipelineDescriptor.primitive.topology = 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 RGB image.
- EXPECT_PIXEL_RGBA8_EQ(kYellowYUVColor[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) {
- // TODO(https://crbug.com/dawn/733): Figure out why Nvidia bot occasionally fails testing all
- // four corners.
- DAWN_SUPPRESS_TEST_IF(IsNvidia());
-
- wgpu::Texture wgpuTexture;
- CreateVideoTextureForTest(wgpu::TextureFormat::R8BG8Biplanar420Unorm,
- wgpu::TextureUsage::TextureBinding, /*isCheckerboard*/ true,
- &wgpuTexture);
- ASSERT_NE(wgpuTexture.Get(), nullptr);
-
- 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;
- 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>;
-
- [[stage(fragment)]]
- fn main([[location(0)]] texCoord : vec2<f32>) -> [[location(0)]] vec4<f32> {
- 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 vec4<f32>(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.EndPass();
- }
-
- wgpu::CommandBuffer commands = encoder.Finish();
- queue.Submit(1, &commands);
-
- // Test four corners of the checkerboard image (YUV color space).
- RGBA8 yellowYUV(kYellowYUVColor[kYUVLumaPlaneIndex].r, kYellowYUVColor[kYUVChromaPlaneIndex].r,
- kYellowYUVColor[kYUVChromaPlaneIndex].g, 0xFF);
- EXPECT_PIXEL_RGBA8_EQ(yellowYUV, renderPass.color, 0, 0); // top left
-
- RGBA8 redYUV(kRedYUVColor[kYUVLumaPlaneIndex].r, kRedYUVColor[kYUVChromaPlaneIndex].r,
- kRedYUVColor[kYUVChromaPlaneIndex].g, 0xFF);
- EXPECT_PIXEL_RGBA8_EQ(redYUV, renderPass.color, kYUVImageDataWidthInTexels - 1,
- kYUVImageDataHeightInTexels - 1); // bottom right
-
- RGBA8 blueYUV(kBlueYUVColor[kYUVLumaPlaneIndex].r, kBlueYUVColor[kYUVChromaPlaneIndex].r,
- kBlueYUVColor[kYUVChromaPlaneIndex].g, 0xFF);
- EXPECT_PIXEL_RGBA8_EQ(blueYUV, renderPass.color, kYUVImageDataWidthInTexels - 1,
- 0); // top right
-
- RGBA8 whiteYUV(kWhiteYUVColor[kYUVLumaPlaneIndex].r, kWhiteYUVColor[kYUVChromaPlaneIndex].r,
- kWhiteYUVColor[kYUVChromaPlaneIndex].g, 0xFF);
- EXPECT_PIXEL_RGBA8_EQ(whiteYUV, renderPass.color, 0,
- kYUVImageDataHeightInTexels - 1); // bottom left
-}
-
-DAWN_INSTANTIATE_TEST(D3D12VideoViewsTests, D3D12Backend());
diff --git a/src/tests/end2end/VideoViewsTests.cpp b/src/tests/end2end/VideoViewsTests.cpp
new file mode 100644
index 0000000..b640ea0
--- /dev/null
+++ b/src/tests/end2end/VideoViewsTests.cpp
@@ -0,0 +1,348 @@
+// 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 "VideoViewsTests.h"
+
+#include "utils/ComboRenderPipelineDescriptor.h"
+#include "utils/WGPUHelpers.h"
+
+VideoViewsTestBackend::PlatformTexture::PlatformTexture(wgpu::Texture&& texture)
+ : wgpuTexture(texture) {
+}
+VideoViewsTestBackend::PlatformTexture::~PlatformTexture() = default;
+
+VideoViewsTestBackend::~VideoViewsTestBackend() = default;
+
+constexpr std::array<RGBA8, 2> VideoViewsTests::kYellowYUVColor;
+constexpr std::array<RGBA8, 2> VideoViewsTests::kWhiteYUVColor;
+constexpr std::array<RGBA8, 2> VideoViewsTests::kBlueYUVColor;
+constexpr std::array<RGBA8, 2> VideoViewsTests::kRedYUVColor;
+
+void VideoViewsTests::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<const char*> VideoViewsTests::GetRequiredFeatures() {
+ std::vector<const char*> requiredFeatures = {};
+ mIsMultiPlanarFormatsSupported = SupportsFeatures({"multiplanar-formats"});
+ if (mIsMultiPlanarFormatsSupported) {
+ requiredFeatures.push_back("multiplanar-formats");
+ }
+ requiredFeatures.push_back("dawn-internal-usages");
+ return requiredFeatures;
+}
+
+bool VideoViewsTests::IsMultiPlanarFormatsSupported() const {
+ return mIsMultiPlanarFormatsSupported;
+}
+
+// Returns a pre-prepared multi-planar formatted texture
+// The encoded texture data represents a 4x4 converted image. When |isCheckerboard| is true,
+// the top left is a 2x2 yellow block, bottom right is a 2x2 red block, top right is a 2x2
+// 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) {
+ constexpr uint8_t Yy = kYellowYUVColor[kYUVLumaPlaneIndex].r;
+ constexpr uint8_t Yu = kYellowYUVColor[kYUVChromaPlaneIndex].r;
+ constexpr uint8_t Yv = kYellowYUVColor[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 Wy = kWhiteYUVColor[kYUVLumaPlaneIndex].r;
+ constexpr uint8_t Wu = kWhiteYUVColor[kYUVChromaPlaneIndex].r;
+ constexpr uint8_t Wv = kWhiteYUVColor[kYUVChromaPlaneIndex].g;
+
+ constexpr uint8_t Ry = kRedYUVColor[kYUVLumaPlaneIndex].r;
+ constexpr uint8_t Ru = kRedYUVColor[kYUVChromaPlaneIndex].r;
+ constexpr uint8_t Rv = kRedYUVColor[kYUVChromaPlaneIndex].g;
+
+ constexpr uint8_t By = kBlueYUVColor[kYUVLumaPlaneIndex].r;
+ constexpr uint8_t Bu = kBlueYUVColor[kYUVChromaPlaneIndex].r;
+ constexpr uint8_t Bv = kBlueYUVColor[kYUVChromaPlaneIndex].g;
+
+ // clang-format off
+ return {
+ Wy, Wy, Ry, Ry, // plane 0, start + 0
+ Wy, Wy, Ry, Ry,
+ Yy, Yy, By, By,
+ Yy, Yy, By, By,
+ Wu, Wv, Ru, Rv, // plane 1, start + 16
+ Yu, Yv, Bu, Bv,
+ };
+ // clang-format on
+ } else {
+ // clang-format off
+ return {
+ Yy, Yy, Yy, Yy, // plane 0, start + 0
+ Yy, Yy, Yy, Yy,
+ Yy, Yy, Yy, Yy,
+ Yy, Yy, Yy, Yy,
+ Yu, Yv, Yu, Yv, // plane 1, start + 16
+ Yu, Yv, Yu, Yv,
+ };
+ // clang-format on
+ }
+ default:
+ UNREACHABLE();
+ return {};
+ }
+}
+
+// Vertex shader used to render a sampled texture into a quad.
+wgpu::ShaderModule VideoViewsTests::GetTestVertexShaderModule() const {
+ return utils::CreateShaderModule(device, R"(
+ struct VertexOut {
+ [[location(0)]] texCoord : vec2 <f32>;
+ [[builtin(position)]] position : vec4<f32>;
+ };
+
+ [[stage(vertex)]]
+ fn main([[builtin(vertex_index)]] VertexIndex : u32) -> VertexOut {
+ var pos = 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)
+ );
+ var output : VertexOut;
+ output.position = vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ output.texCoord = vec2<f32>(output.position.xy * 0.5) + vec2<f32>(0.5, 0.5);
+ return output;
+ })");
+}
+
+// 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) {
+ std::unique_ptr<VideoViewsTestBackend::PlatformTexture> platformTexture =
+ mBackend->CreateVideoTextureForTest(wgpu::TextureFormat::R8BG8Biplanar420Unorm,
+ wgpu::TextureUsage::TextureBinding,
+ /*isCheckerboard*/ false);
+ ASSERT_NE(platformTexture.get(), nullptr);
+ if (!platformTexture->CanWrapAsWGPUTexture()) {
+ mBackend->DestroyVideoTextureForTest(std::move(platformTexture));
+ GTEST_SKIP() << "Skipped because not supported.";
+ }
+ wgpu::TextureViewDescriptor viewDesc;
+ viewDesc.format = wgpu::TextureFormat::R8Unorm;
+ viewDesc.aspect = wgpu::TextureAspect::Plane0Only;
+ wgpu::TextureView textureView = platformTexture->wgpuTexture.CreateView(&viewDesc);
+
+ 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 texture : texture_2d<f32>;
+
+ [[stage(fragment)]]
+ fn main([[location(0)]] texCoord : vec2<f32>) -> [[location(0)]] vec4<f32> {
+ let y : f32 = textureSample(texture, sampler0, texCoord).r;
+ return vec4<f32>(y, 0.0, 0.0, 1.0);
+ })");
+
+ utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(
+ device, kYUVImageDataWidthInTexels, kYUVImageDataHeightInTexels);
+ renderPipelineDescriptor.cTargets[0].format = renderPass.colorFormat;
+ renderPipelineDescriptor.primitive.topology = 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 RGB image.
+ EXPECT_PIXEL_RGBA8_EQ(kYellowYUVColor[kYUVLumaPlaneIndex], renderPass.color, 0, 0);
+ mBackend->DestroyVideoTextureForTest(std::move(platformTexture));
+}
+
+// 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(VideoViewsTests, NV12SampleUVtoRG) {
+ std::unique_ptr<VideoViewsTestBackend::PlatformTexture> platformTexture =
+ mBackend->CreateVideoTextureForTest(wgpu::TextureFormat::R8BG8Biplanar420Unorm,
+ wgpu::TextureUsage::TextureBinding,
+ /*isCheckerboard*/ false);
+ ASSERT_NE(platformTexture.get(), nullptr);
+ if (!platformTexture->CanWrapAsWGPUTexture()) {
+ mBackend->DestroyVideoTextureForTest(std::move(platformTexture));
+ GTEST_SKIP() << "Skipped because not supported.";
+ }
+
+ wgpu::TextureViewDescriptor viewDesc;
+ viewDesc.format = wgpu::TextureFormat::RG8Unorm;
+ viewDesc.aspect = wgpu::TextureAspect::Plane1Only;
+ wgpu::TextureView textureView = platformTexture->wgpuTexture.CreateView(&viewDesc);
+
+ 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 texture : texture_2d<f32>;
+
+ [[stage(fragment)]]
+ fn main([[location(0)]] texCoord : vec2<f32>) -> [[location(0)]] vec4<f32> {
+ let u : f32 = textureSample(texture, sampler0, texCoord).r;
+ let v : f32 = textureSample(texture, sampler0, texCoord).g;
+ return vec4<f32>(u, v, 0.0, 1.0);
+ })");
+
+ utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(
+ device, kYUVImageDataWidthInTexels, kYUVImageDataHeightInTexels);
+ renderPipelineDescriptor.cTargets[0].format = renderPass.colorFormat;
+ renderPipelineDescriptor.primitive.topology = 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 RGB image.
+ EXPECT_PIXEL_RGBA8_EQ(kYellowYUVColor[kYUVChromaPlaneIndex], renderPass.color, 0, 0);
+ mBackend->DestroyVideoTextureForTest(std::move(platformTexture));
+}
+
+// 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(VideoViewsTests, NV12SampleYUVtoRGB) {
+ // TODO(https://crbug.com/dawn/733): Figure out why Nvidia bot occasionally fails testing all
+ // four corners.
+ DAWN_SUPPRESS_TEST_IF(IsNvidia());
+
+ std::unique_ptr<VideoViewsTestBackend::PlatformTexture> platformTexture =
+ mBackend->CreateVideoTextureForTest(wgpu::TextureFormat::R8BG8Biplanar420Unorm,
+ wgpu::TextureUsage::TextureBinding,
+ /*isCheckerboard*/ true);
+ ASSERT_NE(platformTexture.get(), nullptr);
+ if (!platformTexture->CanWrapAsWGPUTexture()) {
+ mBackend->DestroyVideoTextureForTest(std::move(platformTexture));
+ GTEST_SKIP() << "Skipped because not supported.";
+ }
+
+ wgpu::TextureViewDescriptor lumaViewDesc;
+ lumaViewDesc.format = wgpu::TextureFormat::R8Unorm;
+ lumaViewDesc.aspect = wgpu::TextureAspect::Plane0Only;
+ wgpu::TextureView lumaTextureView = platformTexture->wgpuTexture.CreateView(&lumaViewDesc);
+
+ wgpu::TextureViewDescriptor chromaViewDesc;
+ chromaViewDesc.format = wgpu::TextureFormat::RG8Unorm;
+ chromaViewDesc.aspect = wgpu::TextureAspect::Plane1Only;
+ wgpu::TextureView chromaTextureView = platformTexture->wgpuTexture.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>;
+
+ [[stage(fragment)]]
+ fn main([[location(0)]] texCoord : vec2<f32>) -> [[location(0)]] vec4<f32> {
+ 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 vec4<f32>(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.EndPass();
+ }
+
+ wgpu::CommandBuffer commands = encoder.Finish();
+ queue.Submit(1, &commands);
+
+ // Test four corners of the checkerboard image (YUV color space).
+ RGBA8 yellowYUV(kYellowYUVColor[kYUVLumaPlaneIndex].r, kYellowYUVColor[kYUVChromaPlaneIndex].r,
+ kYellowYUVColor[kYUVChromaPlaneIndex].g, 0xFF);
+ EXPECT_PIXEL_RGBA8_EQ(yellowYUV, renderPass.color, 0, 0); // top left
+
+ RGBA8 redYUV(kRedYUVColor[kYUVLumaPlaneIndex].r, kRedYUVColor[kYUVChromaPlaneIndex].r,
+ kRedYUVColor[kYUVChromaPlaneIndex].g, 0xFF);
+ EXPECT_PIXEL_RGBA8_EQ(redYUV, renderPass.color, kYUVImageDataWidthInTexels - 1,
+ kYUVImageDataHeightInTexels - 1); // bottom right
+
+ RGBA8 blueYUV(kBlueYUVColor[kYUVLumaPlaneIndex].r, kBlueYUVColor[kYUVChromaPlaneIndex].r,
+ kBlueYUVColor[kYUVChromaPlaneIndex].g, 0xFF);
+ EXPECT_PIXEL_RGBA8_EQ(blueYUV, renderPass.color, kYUVImageDataWidthInTexels - 1,
+ 0); // top right
+
+ RGBA8 whiteYUV(kWhiteYUVColor[kYUVLumaPlaneIndex].r, kWhiteYUVColor[kYUVChromaPlaneIndex].r,
+ kWhiteYUVColor[kYUVChromaPlaneIndex].g, 0xFF);
+ EXPECT_PIXEL_RGBA8_EQ(whiteYUV, renderPass.color, 0,
+ kYUVImageDataHeightInTexels - 1); // bottom left
+ mBackend->DestroyVideoTextureForTest(std::move(platformTexture));
+}
+
+DAWN_INSTANTIATE_TEST(VideoViewsTests, VideoViewsTestBackend::Backend());
diff --git a/src/tests/end2end/VideoViewsTests.h b/src/tests/end2end/VideoViewsTests.h
new file mode 100644
index 0000000..c286d37
--- /dev/null
+++ b/src/tests/end2end/VideoViewsTests.h
@@ -0,0 +1,89 @@
+// 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.
+
+#ifndef TESTS_VIDEOVIEWSTESTS_H_
+#define TESTS_VIDEOVIEWSTESTS_H_
+
+#include "tests/DawnTest.h"
+
+#include <array>
+#include <memory>
+
+class VideoViewsTestBackend {
+ public:
+ static BackendTestConfig Backend();
+ static std::unique_ptr<VideoViewsTestBackend> Create();
+
+ virtual ~VideoViewsTestBackend();
+
+ virtual void OnSetUp(WGPUDevice device) = 0;
+ virtual void OnTearDown() = 0;
+
+ class PlatformTexture {
+ public:
+ PlatformTexture() = delete;
+ virtual ~PlatformTexture();
+
+ virtual bool CanWrapAsWGPUTexture() = 0;
+
+ protected:
+ explicit PlatformTexture(wgpu::Texture&& texture);
+
+ public:
+ wgpu::Texture wgpuTexture;
+ };
+ virtual std::unique_ptr<PlatformTexture> CreateVideoTextureForTest(wgpu::TextureFormat format,
+ wgpu::TextureUsage usage,
+ bool isCheckerboard) = 0;
+ virtual void DestroyVideoTextureForTest(std::unique_ptr<PlatformTexture>&& platformTexture) = 0;
+};
+
+class VideoViewsTests : public DawnTest {
+ public:
+ // 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.
+ // RGB colors are mapped to the BT.601 definition of luma.
+ // https://docs.microsoft.com/en-us/windows/win32/medfound/about-yuv-video
+ static constexpr std::array<RGBA8, 2> kYellowYUVColor = {RGBA8{210, 0, 0, 0xFF}, // Y
+ RGBA8{16, 146, 0, 0xFF}}; // UV
+
+ static constexpr std::array<RGBA8, 2> kWhiteYUVColor = {RGBA8{235, 0, 0, 0xFF}, // Y
+ RGBA8{128, 128, 0, 0xFF}}; // UV
+
+ static constexpr std::array<RGBA8, 2> kBlueYUVColor = {RGBA8{41, 0, 0, 0xFF}, // Y
+ RGBA8{240, 110, 0, 0xFF}}; // UV
+
+ static constexpr std::array<RGBA8, 2> kRedYUVColor = {RGBA8{81, 0, 0, 0xFF}, // Y
+ RGBA8{90, 240, 0, 0xFF}}; // UV
+
+ static std::vector<uint8_t> GetTestTextureData(wgpu::TextureFormat format, bool isCheckerboard);
+
+ protected:
+ void SetUp() override;
+ void TearDown() override;
+ std::vector<const char*> GetRequiredFeatures() override;
+ bool IsMultiPlanarFormatsSupported() const;
+ wgpu::ShaderModule GetTestVertexShaderModule() const;
+
+ std::unique_ptr<VideoViewsTestBackend> mBackend;
+ bool mIsMultiPlanarFormatsSupported = false;
+};
+
+#endif // TESTS_VIDEOVIEWSTESTS_H_
diff --git a/src/tests/end2end/VideoViewsTests_gbm.cpp b/src/tests/end2end/VideoViewsTests_gbm.cpp
new file mode 100644
index 0000000..946eb84
--- /dev/null
+++ b/src/tests/end2end/VideoViewsTests_gbm.cpp
@@ -0,0 +1,203 @@
+// 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 "VideoViewsTests.h"
+
+#include "common/Assert.h"
+#include "dawn_native/VulkanBackend.h"
+
+#include <fcntl.h>
+#include <gbm.h>
+
+// "linux-chromeos-rel"'s gbm.h is too old to compile, missing this change at least:
+// https://chromium-review.googlesource.com/c/chromiumos/platform/minigbm/+/1963001/10/gbm.h#244
+#ifndef MINIGBM
+# define GBM_BO_USE_TEXTURING (1 << 5)
+# define GBM_BO_USE_SW_WRITE_RARELY (1 << 12)
+# define GBM_BO_USE_HW_VIDEO_DECODER (1 << 13)
+#endif
+
+class PlatformTextureGbm : public VideoViewsTestBackend::PlatformTexture {
+ public:
+ PlatformTextureGbm(wgpu::Texture&& texture, gbm_bo* gbmBo)
+ : PlatformTexture(std::move(texture)), mGbmBo(gbmBo) {
+ }
+ ~PlatformTextureGbm() override = default;
+
+ // TODO(chromium:1258986): Add DISJOINT vkImage support for multi-plannar formats.
+ bool CanWrapAsWGPUTexture() override {
+ ASSERT(mGbmBo != nullptr);
+ // Checks if all plane handles of a multi-planar gbm_bo are same.
+ gbm_bo_handle plane0Handle = gbm_bo_get_handle_for_plane(mGbmBo, 0);
+ for (int plane = 1; plane < gbm_bo_get_plane_count(mGbmBo); ++plane) {
+ if (gbm_bo_get_handle_for_plane(mGbmBo, plane).u32 != plane0Handle.u32) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ gbm_bo* GetGbmBo() {
+ return mGbmBo;
+ }
+
+ private:
+ gbm_bo* mGbmBo = nullptr;
+};
+
+class VideoViewsTestBackendGbm : public VideoViewsTestBackend {
+ public:
+ void OnSetUp(WGPUDevice device) override {
+ mWGPUDevice = device;
+ mGbmDevice = CreateGbmDevice();
+ }
+
+ void OnTearDown() override {
+ gbm_device_destroy(mGbmDevice);
+ }
+
+ private:
+ gbm_device* CreateGbmDevice() {
+ // Render nodes [1] are the primary interface for communicating with the GPU on
+ // devices that support DRM. The actual filename of the render node is
+ // implementation-specific, so we must scan through all possible filenames to find
+ // one that we can use [2].
+ //
+ // [1] https://dri.freedesktop.org/docs/drm/gpu/drm-uapi.html#render-nodes
+ // [2]
+ // https://cs.chromium.org/chromium/src/ui/ozone/platform/wayland/gpu/drm_render_node_path_finder.cc
+ const uint32_t kRenderNodeStart = 128;
+ const uint32_t kRenderNodeEnd = kRenderNodeStart + 16;
+ const std::string kRenderNodeTemplate = "/dev/dri/renderD";
+
+ int renderNodeFd = -1;
+ for (uint32_t i = kRenderNodeStart; i < kRenderNodeEnd; i++) {
+ std::string renderNode = kRenderNodeTemplate + std::to_string(i);
+ renderNodeFd = open(renderNode.c_str(), O_RDWR);
+ if (renderNodeFd >= 0)
+ break;
+ }
+ ASSERT(renderNodeFd > 0);
+
+ gbm_device* gbmDevice = gbm_create_device(renderNodeFd);
+ ASSERT(gbmDevice != nullptr);
+ return gbmDevice;
+ }
+
+ static uint32_t GetGbmBoFormat(wgpu::TextureFormat format) {
+ switch (format) {
+ case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
+ return GBM_FORMAT_NV12;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ WGPUTextureFormat ToWGPUTextureFormat(wgpu::TextureFormat format) {
+ switch (format) {
+ case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
+ return WGPUTextureFormat_R8BG8Biplanar420Unorm;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ WGPUTextureUsage ToWGPUTextureUsage(wgpu::TextureUsage usage) {
+ switch (usage) {
+ case wgpu::TextureUsage::TextureBinding:
+ return WGPUTextureUsage_TextureBinding;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ std::unique_ptr<VideoViewsTestBackend::PlatformTexture> CreateVideoTextureForTest(
+ wgpu::TextureFormat format,
+ wgpu::TextureUsage usage,
+ bool isCheckerboard) override {
+ uint32_t flags = GBM_BO_USE_SCANOUT | GBM_BO_USE_TEXTURING | GBM_BO_USE_HW_VIDEO_DECODER |
+ GBM_BO_USE_SW_WRITE_RARELY;
+ gbm_bo* gbmBo = gbm_bo_create(mGbmDevice, VideoViewsTests::kYUVImageDataWidthInTexels,
+ VideoViewsTests::kYUVImageDataHeightInTexels,
+ GetGbmBoFormat(format), flags);
+ if (gbmBo == nullptr) {
+ return nullptr;
+ }
+
+ void* mapHandle = nullptr;
+ uint32_t strideBytes = 0;
+ void* addr = gbm_bo_map(gbmBo, 0, 0, VideoViewsTests::kYUVImageDataWidthInTexels,
+ VideoViewsTests::kYUVImageDataHeightInTexels, GBM_BO_TRANSFER_WRITE,
+ &strideBytes, &mapHandle);
+ EXPECT_NE(addr, nullptr);
+ std::vector<uint8_t> initialData =
+ VideoViewsTests::GetTestTextureData(format, isCheckerboard);
+ std::memcpy(addr, initialData.data(), initialData.size());
+
+ gbm_bo_unmap(gbmBo, mapHandle);
+
+ wgpu::TextureDescriptor textureDesc;
+ textureDesc.format = format;
+ textureDesc.dimension = wgpu::TextureDimension::e2D;
+ textureDesc.usage = usage;
+ textureDesc.size = {VideoViewsTests::kYUVImageDataWidthInTexels,
+ VideoViewsTests::kYUVImageDataHeightInTexels, 1};
+
+ wgpu::DawnTextureInternalUsageDescriptor internalDesc;
+ internalDesc.internalUsage = wgpu::TextureUsage::CopySrc;
+ textureDesc.nextInChain = &internalDesc;
+
+ dawn_native::vulkan::ExternalImageDescriptorDmaBuf descriptor = {};
+ descriptor.cTextureDescriptor =
+ reinterpret_cast<const WGPUTextureDescriptor*>(&textureDesc);
+ descriptor.isInitialized = true;
+
+ descriptor.memoryFD = gbm_bo_get_fd(gbmBo);
+ descriptor.stride = gbm_bo_get_stride(gbmBo);
+ descriptor.drmModifier = gbm_bo_get_modifier(gbmBo);
+ descriptor.waitFDs = {};
+
+ return std::make_unique<PlatformTextureGbm>(
+ wgpu::Texture::Acquire(dawn_native::vulkan::WrapVulkanImage(mWGPUDevice, &descriptor)),
+ gbmBo);
+ }
+
+ void DestroyVideoTextureForTest(
+ std::unique_ptr<VideoViewsTestBackend::PlatformTexture>&& platformTexture) override {
+ // Exports the signal and ignores it.
+ dawn_native::vulkan::ExternalImageExportInfoDmaBuf exportInfo;
+ dawn_native::vulkan::ExportVulkanImage(platformTexture->wgpuTexture.Get(),
+ VK_IMAGE_LAYOUT_GENERAL, &exportInfo);
+ for (int fd : exportInfo.semaphoreHandles) {
+ ASSERT_NE(fd, -1);
+ close(fd);
+ }
+ gbm_bo* gbmBo = static_cast<PlatformTextureGbm*>(platformTexture.get())->GetGbmBo();
+ ASSERT_NE(gbmBo, nullptr);
+ gbm_bo_destroy(gbmBo);
+ }
+
+ WGPUDevice mWGPUDevice = nullptr;
+ gbm_device* mGbmDevice = nullptr;
+};
+
+// static
+BackendTestConfig VideoViewsTestBackend::Backend() {
+ return VulkanBackend();
+}
+
+// static
+std::unique_ptr<VideoViewsTestBackend> VideoViewsTestBackend::Create() {
+ return std::make_unique<VideoViewsTestBackendGbm>();
+}
diff --git a/src/tests/end2end/VideoViewsTests_win.cpp b/src/tests/end2end/VideoViewsTests_win.cpp
new file mode 100644
index 0000000..9439609
--- /dev/null
+++ b/src/tests/end2end/VideoViewsTests_win.cpp
@@ -0,0 +1,191 @@
+// 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 "VideoViewsTests.h"
+
+#include "common/Assert.h"
+#include "dawn_native/D3D12Backend.h"
+
+#include <d3d11.h>
+#include <d3d12.h>
+#include <dxgi1_4.h>
+#include <wrl/client.h>
+
+using Microsoft::WRL::ComPtr;
+
+class PlatformTextureWin : public VideoViewsTestBackend::PlatformTexture {
+ public:
+ explicit PlatformTextureWin(wgpu::Texture&& texture) : PlatformTexture(std::move(texture)) {
+ }
+ ~PlatformTextureWin() override = default;
+
+ bool CanWrapAsWGPUTexture() override {
+ return true;
+ }
+};
+
+class VideoViewsTestBackendWin : public VideoViewsTestBackend {
+ public:
+ ~VideoViewsTestBackendWin() override = default;
+
+ void OnSetUp(WGPUDevice device) override {
+ mWGPUDevice = device;
+
+ // Create the D3D11 device/contexts that will be used in subsequent tests
+ ComPtr<ID3D12Device> d3d12Device = dawn_native::d3d12::GetD3D12Device(device);
+
+ 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);
+ }
+
+ void OnTearDown() override {
+ }
+
+ protected:
+ static DXGI_FORMAT GetDXGITextureFormat(wgpu::TextureFormat format) {
+ switch (format) {
+ case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
+ return DXGI_FORMAT_NV12;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ std::unique_ptr<VideoViewsTestBackend::PlatformTexture> CreateVideoTextureForTest(
+ wgpu::TextureFormat format,
+ wgpu::TextureUsage usage,
+ bool isCheckerboard) override {
+ wgpu::TextureDescriptor textureDesc;
+ textureDesc.format = format;
+ textureDesc.dimension = wgpu::TextureDimension::e2D;
+ textureDesc.usage = usage;
+ textureDesc.size = {VideoViewsTests::kYUVImageDataWidthInTexels,
+ VideoViewsTests::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.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 =
+ VideoViewsTests::GetTestTextureData(format, isCheckerboard);
+
+ D3D11_SUBRESOURCE_DATA subres;
+ subres.pSysMem = initialData.data();
+ subres.SysMemPitch = VideoViewsTests::kYUVImageDataWidthInTexels;
+
+ ComPtr<ID3D11Texture2D> d3d11Texture;
+ HRESULT hr = mD3d11Device->CreateTexture2D(&d3dDescriptor, &subres, &d3d11Texture);
+ ASSERT(hr == S_OK);
+
+ ComPtr<IDXGIResource1> dxgiResource;
+ hr = d3d11Texture.As(&dxgiResource);
+ ASSERT(hr == S_OK);
+
+ HANDLE sharedHandle;
+ hr = dxgiResource->CreateSharedHandle(
+ nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, nullptr,
+ &sharedHandle);
+ ASSERT(hr == S_OK);
+
+ // 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);
+ ASSERT(hr == S_OK);
+
+ hr = dxgiKeyedMutex->AcquireSync(0, INFINITE);
+ ASSERT(hr == S_OK);
+
+ hr = dxgiKeyedMutex->ReleaseSync(1);
+ ASSERT(hr == S_OK);
+
+ // Open the DX11 texture in Dawn from the shared handle and return it as a WebGPU
+ // texture.
+ dawn_native::d3d12::ExternalImageDescriptorDXGISharedHandle externalImageDesc;
+ externalImageDesc.cTextureDescriptor =
+ reinterpret_cast<const WGPUTextureDescriptor*>(&textureDesc);
+ externalImageDesc.sharedHandle = sharedHandle;
+
+ std::unique_ptr<dawn_native::d3d12::ExternalImageDXGI> externalImage =
+ dawn_native::d3d12::ExternalImageDXGI::Create(mWGPUDevice, &externalImageDesc);
+
+ // Handle is no longer needed once resources are created.
+ ::CloseHandle(sharedHandle);
+
+ dawn_native::d3d12::ExternalImageAccessDescriptorDXGIKeyedMutex externalAccessDesc;
+ externalAccessDesc.acquireMutexKey = 1;
+ externalAccessDesc.releaseMutexKey = 2;
+ externalAccessDesc.isInitialized = true;
+ externalAccessDesc.usage = static_cast<WGPUTextureUsageFlags>(textureDesc.usage);
+
+ return std::make_unique<PlatformTextureWin>(wgpu::Texture::Acquire(
+ externalImage->ProduceTexture(mWGPUDevice, &externalAccessDesc)));
+ }
+
+ void DestroyVideoTextureForTest(
+ std::unique_ptr<VideoViewsTestBackend::PlatformTexture>&& PlatformTexture) override {
+ }
+
+ WGPUDevice mWGPUDevice = nullptr;
+ ComPtr<ID3D11Device> mD3d11Device;
+};
+
+// static
+BackendTestConfig VideoViewsTestBackend::Backend() {
+ return D3D12Backend();
+}
+// static
+std::unique_ptr<VideoViewsTestBackend> VideoViewsTestBackend::Create() {
+ return std::make_unique<VideoViewsTestBackendWin>();
+}