D3D12: Enable external texture reuse

This change allows multiple Dawn textures to be created from the same
D3D11 resource. This avoids re-opening the shared handle by caching the
D3D12 resource outside of the Dawn texture.

Re-opening the handle costs 5-10% of CPU cycles per frame, which far
exceeded syncronization costs.

In a future change, WrapSharedHandle will be depreciated.

BUG=dawn:625

Change-Id: If0d2dc9b7445ec3ae718bc5305164db88057c4ea
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/42140
Commit-Queue: Bryan Bernhart <bryan.bernhart@intel.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
This commit is contained in:
Bryan Bernhart
2021-02-26 22:44:48 +00:00
committed by Commit Bot service account
parent 4d9f2ca07d
commit 9c3aefa4bd
9 changed files with 278 additions and 90 deletions

View File

@@ -86,7 +86,9 @@ namespace {
void WrapSharedHandle(const wgpu::TextureDescriptor* dawnDesc,
const D3D11_TEXTURE2D_DESC* baseD3dDescriptor,
wgpu::Texture* dawnTexture,
ID3D11Texture2D** d3d11TextureOut) const {
ID3D11Texture2D** d3d11TextureOut,
std::unique_ptr<dawn_native::d3d12::ExternalImageDXGI>*
externalImageOut = nullptr) const {
ComPtr<ID3D11Texture2D> d3d11Texture;
HRESULT hr = mD3d11Device->CreateTexture2D(baseD3dDescriptor, nullptr, &d3d11Texture);
ASSERT_EQ(hr, S_OK);
@@ -101,19 +103,33 @@ namespace {
&sharedHandle);
ASSERT_EQ(hr, S_OK);
dawn_native::d3d12::ExternalImageDescriptorDXGISharedHandle externDesc;
externDesc.cTextureDescriptor =
dawn_native::d3d12::ExternalImageDescriptorDXGISharedHandle externalImageDesc;
externalImageDesc.cTextureDescriptor =
reinterpret_cast<const WGPUTextureDescriptor*>(dawnDesc);
externDesc.sharedHandle = sharedHandle;
externDesc.acquireMutexKey = 0;
WGPUTexture texture = dawn_native::d3d12::WrapSharedHandle(device.Get(), &externDesc);
externalImageDesc.sharedHandle = sharedHandle;
std::unique_ptr<dawn_native::d3d12::ExternalImageDXGI> externalImage =
dawn_native::d3d12::ExternalImageDXGI::Create(device.Get(), &externalImageDesc);
// Now that we've created all of our resources, we can close the handle
// since we no longer need it.
::CloseHandle(sharedHandle);
*dawnTexture = wgpu::Texture::Acquire(texture);
// Cannot access a non-existent external image (ex. validation error).
if (externalImage == nullptr) {
return;
}
dawn_native::d3d12::ExternalImageAccessDescriptorDXGIKeyedMutex externalAccessDesc;
externalAccessDesc.acquireMutexKey = 0;
*dawnTexture = wgpu::Texture::Acquire(
externalImage->ProduceTexture(device.Get(), &externalAccessDesc));
*d3d11TextureOut = d3d11Texture.Detach();
if (externalImageOut != nullptr) {
*externalImageOut = std::move(externalImage);
}
}
static constexpr size_t kTestWidth = 10;
@@ -334,15 +350,20 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase {
hr = dxgiKeyedMutex->ReleaseSync(1);
ASSERT_EQ(hr, S_OK);
dawn_native::d3d12::ExternalImageDescriptorDXGISharedHandle externDesc;
externDesc.cTextureDescriptor =
dawn_native::d3d12::ExternalImageDescriptorDXGISharedHandle externalImageDesc = {};
externalImageDesc.sharedHandle = sharedHandle;
externalImageDesc.cTextureDescriptor =
reinterpret_cast<const WGPUTextureDescriptor*>(dawnDescriptor);
externDesc.sharedHandle = sharedHandle;
externDesc.acquireMutexKey = 1;
externDesc.isInitialized = isInitialized;
WGPUTexture dawnTexture = dawn_native::d3d12::WrapSharedHandle(device.Get(), &externDesc);
*dawnTextureOut = wgpu::Texture::Acquire(dawnTexture);
std::unique_ptr<dawn_native::d3d12::ExternalImageDXGI> externalImage =
dawn_native::d3d12::ExternalImageDXGI::Create(device.Get(), &externalImageDesc);
dawn_native::d3d12::ExternalImageAccessDescriptorDXGIKeyedMutex externalAccessDesc;
externalAccessDesc.acquireMutexKey = 1;
externalAccessDesc.isInitialized = isInitialized;
*dawnTextureOut = wgpu::Texture::Acquire(
externalImage->ProduceTexture(device.Get(), &externalAccessDesc));
*d3d11TextureOut = d3d11Texture.Detach();
*dxgiKeyedMutexOut = dxgiKeyedMutex.Detach();
}
@@ -519,5 +540,50 @@ TEST_P(D3D12SharedHandleUsageTests, UninitializedTextureIsCleared) {
EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 0, 0, 0), dawnTexture, 0, 0);
}
// 1. Create an external image from the DX11 texture.
// 2. Produce two Dawn textures from the external image.
// 3. Clear each Dawn texture and verify the texture was cleared to a unique color.
TEST_P(D3D12SharedHandleUsageTests, ReuseExternalImage) {
DAWN_SKIP_TEST_IF(UsesWire());
// Create the first Dawn texture then clear it to red.
wgpu::Texture texture;
ComPtr<ID3D11Texture2D> d3d11Texture;
std::unique_ptr<dawn_native::d3d12::ExternalImageDXGI> externalImage;
WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture,
&externalImage);
{
const wgpu::Color solidRed{1.0f, 0.0f, 0.0f, 1.0f};
ASSERT_NE(texture.Get(), nullptr);
ClearImage(texture.Get(), solidRed);
EXPECT_PIXEL_RGBA8_EQ(RGBA8(0xFF, 0, 0, 0xFF), texture.Get(), 0, 0);
}
// Once finished with the first texture, destroy it so we may re-acquire the external image
// again.
texture.Destroy();
// Create another Dawn texture then clear it with another color.
dawn_native::d3d12::ExternalImageAccessDescriptorDXGIKeyedMutex externalAccessDesc;
externalAccessDesc.acquireMutexKey = 1;
externalAccessDesc.isInitialized = true;
texture =
wgpu::Texture::Acquire(externalImage->ProduceTexture(device.Get(), &externalAccessDesc));
// Check again that the new texture is still red
EXPECT_PIXEL_RGBA8_EQ(RGBA8(0xFF, 0, 0, 0xFF), texture.Get(), 0, 0);
// Clear the new texture to blue
{
const wgpu::Color solidBlue{0.0f, 0.0f, 1.0f, 1.0f};
ASSERT_NE(texture.Get(), nullptr);
ClearImage(texture.Get(), solidBlue);
EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 0, 0xFF, 0xFF), texture.Get(), 0, 0);
}
}
DAWN_INSTANTIATE_TEST(D3D12SharedHandleValidation, D3D12Backend());
DAWN_INSTANTIATE_TEST(D3D12SharedHandleUsageTests, D3D12Backend());

View File

@@ -139,9 +139,10 @@ namespace {
}
}
wgpu::Texture CreateVideoTextureForTest(wgpu::TextureFormat format,
wgpu::TextureUsage usage,
bool isCheckerboard = false) {
void CreateVideoTextureForTest(wgpu::TextureFormat format,
wgpu::TextureUsage usage,
bool isCheckerboard,
wgpu::Texture* dawnTextureOut) {
wgpu::TextureDescriptor textureDesc;
textureDesc.format = format;
textureDesc.dimension = wgpu::TextureDimension::e2D;
@@ -171,24 +172,17 @@ namespace {
ComPtr<ID3D11Texture2D> d3d11Texture;
HRESULT hr = mD3d11Device->CreateTexture2D(&d3dDescriptor, &subres, &d3d11Texture);
EXPECT_EQ(hr, S_OK);
ASSERT_EQ(hr, S_OK);
ComPtr<IDXGIResource1> dxgiResource;
hr = d3d11Texture.As(&dxgiResource);
EXPECT_EQ(hr, S_OK);
ASSERT_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;
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
@@ -205,13 +199,23 @@ namespace {
// 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));
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);
return wgpuTexture;
dawn_native::d3d12::ExternalImageAccessDescriptorDXGIKeyedMutex externalAccessDesc;
externalAccessDesc.acquireMutexKey = 1;
externalAccessDesc.isInitialized = true;
*dawnTextureOut = wgpu::Texture::Acquire(
externalImage->ProduceTexture(device.Get(), &externalAccessDesc));
}
// Vertex shader used to render a sampled texture into a quad.
@@ -259,8 +263,9 @@ 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::Texture wgpuTexture;
CreateVideoTextureForTest(wgpu::TextureFormat::R8BG8Biplanar420Unorm,
wgpu::TextureUsage::Sampled, /*isCheckerboard*/ false, &wgpuTexture);
wgpu::TextureViewDescriptor viewDesc;
viewDesc.aspect = wgpu::TextureAspect::Plane0Only;
@@ -310,8 +315,9 @@ TEST_P(D3D12VideoViewsTests, NV12SampleYtoR) {
// 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::Texture wgpuTexture;
CreateVideoTextureForTest(wgpu::TextureFormat::R8BG8Biplanar420Unorm,
wgpu::TextureUsage::Sampled, /*isCheckerboard*/ false, &wgpuTexture);
wgpu::TextureViewDescriptor viewDesc;
viewDesc.aspect = wgpu::TextureAspect::Plane1Only;
@@ -362,8 +368,9 @@ TEST_P(D3D12VideoViewsTests, NV12SampleUVtoRG) {
// 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::Texture wgpuTexture;
CreateVideoTextureForTest(wgpu::TextureFormat::R8BG8Biplanar420Unorm,
wgpu::TextureUsage::Sampled, /*isCheckerboard*/ true, &wgpuTexture);
wgpu::TextureViewDescriptor lumaViewDesc;
lumaViewDesc.aspect = wgpu::TextureAspect::Plane0Only;