Implement D3D12 Native Render Passes
Uses D3D12 native render pass API when possible. On pre-RS5 builds of
Windows, Dawn will fall back to a software emulated render pass. A
toggle was added to provide test coverage to the emulated render pass
implementation and used in tests that test render pass functionality in
particular.
Bug: dawn:36
Change-Id: I297a3ec7655b68d28204db2d3ab78cb82bb4e7a5
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/13082
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Brandon Jones <brandon1.jones@intel.com>
diff --git a/BUILD.gn b/BUILD.gn
index 6f157fd..99742ed 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -291,6 +291,8 @@
"src/dawn_native/d3d12/PlatformFunctions.h",
"src/dawn_native/d3d12/QueueD3D12.cpp",
"src/dawn_native/d3d12/QueueD3D12.h",
+ "src/dawn_native/d3d12/RenderPassBuilderD3D12.cpp",
+ "src/dawn_native/d3d12/RenderPassBuilderD3D12.h",
"src/dawn_native/d3d12/RenderPipelineD3D12.cpp",
"src/dawn_native/d3d12/RenderPipelineD3D12.h",
"src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.cpp",
diff --git a/src/dawn_native/Toggles.cpp b/src/dawn_native/Toggles.cpp
index d3d0e8d..f69ccd1 100644
--- a/src/dawn_native/Toggles.cpp
+++ b/src/dawn_native/Toggles.cpp
@@ -75,8 +75,12 @@
{"use_d3d12_resource_heap_tier2",
"Enable support for resource heap tier 2. Resource heap tier 2 allows mixing of "
"texture and buffers in the same heap. This allows better heap re-use and reduces "
- "fragmentation."}}}};
-
+ "fragmentation."}},
+ {Toggle::UseD3D12RenderPass,
+ {"use_d3d12_render_pass",
+ "Use the D3D12 render pass API introduced in Windows build 1809 by default. On "
+ "versions of Windows prior to build 1809, or when this toggle is turned off, Dawn "
+ "will emulate a render pass."}}}};
} // anonymous namespace
void TogglesSet::SetToggle(Toggle toggle, bool enabled) {
diff --git a/src/dawn_native/Toggles.h b/src/dawn_native/Toggles.h
index 1238450..73db7f9 100644
--- a/src/dawn_native/Toggles.h
+++ b/src/dawn_native/Toggles.h
@@ -31,6 +31,7 @@
TurnOffVsync,
UseTemporaryBufferInCompressedTextureToTextureCopy,
UseD3D12ResourceHeapTier2,
+ UseD3D12RenderPass,
EnumCount,
InvalidEnum = EnumCount,
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
index 61ff14a..ab794f8 100644
--- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
@@ -28,6 +28,7 @@
#include "dawn_native/d3d12/DeviceD3D12.h"
#include "dawn_native/d3d12/PipelineLayoutD3D12.h"
#include "dawn_native/d3d12/PlatformFunctions.h"
+#include "dawn_native/d3d12/RenderPassBuilderD3D12.h"
#include "dawn_native/d3d12/RenderPipelineD3D12.h"
#include "dawn_native/d3d12/SamplerD3D12.h"
#include "dawn_native/d3d12/TextureCopySplitter.h"
@@ -65,12 +66,6 @@
return false;
}
- struct OMSetRenderTargetArgs {
- unsigned int numRTVs = 0;
- std::array<D3D12_CPU_DESCRIPTOR_HANDLE, kMaxColorAttachments> RTVs = {};
- D3D12_CPU_DESCRIPTOR_HANDLE dsv = {};
- };
-
} // anonymous namespace
class BindGroupStateTracker : public BindGroupAndStorageBarrierTrackerBase<false, uint64_t> {
@@ -601,11 +596,13 @@
// Records the necessary barriers for the resource usage pre-computed by the frontend
auto TransitionForPass = [](CommandRecordingContext* commandContext,
- const PassResourceUsage& usages) {
+ const PassResourceUsage& usages) -> bool {
std::vector<D3D12_RESOURCE_BARRIER> barriers;
ID3D12GraphicsCommandList* commandList = commandContext->GetCommandList();
+ wgpu::BufferUsage bufferUsages = wgpu::BufferUsage::None;
+
for (size_t i = 0; i < usages.buffers.size(); ++i) {
D3D12_RESOURCE_BARRIER barrier;
if (ToBackend(usages.buffers[i])
@@ -613,6 +610,7 @@
usages.bufferUsages[i])) {
barriers.push_back(barrier);
}
+ bufferUsages |= usages.bufferUsages[i];
}
for (size_t i = 0; i < usages.textures.size(); ++i) {
@@ -627,6 +625,8 @@
}
}
+ wgpu::TextureUsage textureUsages = wgpu::TextureUsage::None;
+
for (size_t i = 0; i < usages.textures.size(); ++i) {
D3D12_RESOURCE_BARRIER barrier;
if (ToBackend(usages.textures[i])
@@ -634,11 +634,15 @@
usages.textureUsages[i])) {
barriers.push_back(barrier);
}
+ textureUsages |= usages.textureUsages[i];
}
if (barriers.size()) {
commandList->ResourceBarrier(barriers.size(), barriers.data());
}
+
+ return (bufferUsages & wgpu::BufferUsage::Storage ||
+ textureUsages & wgpu::TextureUsage::Storage);
};
const std::vector<PassResourceUsage>& passResourceUsages = GetResourceUsages().perPass;
@@ -661,10 +665,11 @@
BeginRenderPassCmd* beginRenderPassCmd =
mCommands.NextCommand<BeginRenderPassCmd>();
- TransitionForPass(commandContext, passResourceUsages[nextPassNumber]);
+ const bool passHasUAV =
+ TransitionForPass(commandContext, passResourceUsages[nextPassNumber]);
bindingTracker.SetInComputePass(false);
RecordRenderPass(commandContext, &bindingTracker, &renderPassTracker,
- beginRenderPassCmd);
+ beginRenderPassCmd, passHasUAV);
nextPassNumber++;
} break;
@@ -912,126 +917,194 @@
}
}
- void CommandBuffer::RecordRenderPass(CommandRecordingContext* commandContext,
- BindGroupStateTracker* bindingTracker,
- RenderPassDescriptorHeapTracker* renderPassTracker,
- BeginRenderPassCmd* renderPass) {
- OMSetRenderTargetArgs args = renderPassTracker->GetSubpassOMSetRenderTargetArgs(renderPass);
+ void CommandBuffer::SetupRenderPass(CommandRecordingContext* commandContext,
+ BeginRenderPassCmd* renderPass,
+ RenderPassBuilder* renderPassBuilder) {
+ for (uint32_t i : IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) {
+ RenderPassColorAttachmentInfo& attachmentInfo = renderPass->colorAttachments[i];
+ TextureView* view = ToBackend(attachmentInfo.view.Get());
+ Texture* texture = ToBackend(view->GetTexture());
+
+ // Load operation is changed to clear when the texture is uninitialized.
+ if (!texture->IsSubresourceContentInitialized(view->GetBaseMipLevel(), 1,
+ view->GetBaseArrayLayer(), 1) &&
+ attachmentInfo.loadOp == wgpu::LoadOp::Load) {
+ attachmentInfo.loadOp = wgpu::LoadOp::Clear;
+ attachmentInfo.clearColor = {0.0f, 0.0f, 0.0f, 0.0f};
+ }
+
+ // Set color load operation.
+ renderPassBuilder->SetRenderTargetBeginningAccess(
+ i, attachmentInfo.loadOp, attachmentInfo.clearColor, view->GetD3D12Format());
+
+ // Set color store operation.
+ if (attachmentInfo.resolveTarget.Get() != nullptr) {
+ TextureView* resolveDestinationView = ToBackend(attachmentInfo.resolveTarget.Get());
+ Texture* resolveDestinationTexture =
+ ToBackend(resolveDestinationView->GetTexture());
+
+ resolveDestinationTexture->TransitionUsageNow(commandContext,
+ D3D12_RESOURCE_STATE_RESOLVE_DEST);
+
+ // Mark resolve target as initialized to prevent clearing later.
+ resolveDestinationTexture->SetIsSubresourceContentInitialized(
+ true, resolveDestinationView->GetBaseMipLevel(), 1,
+ resolveDestinationView->GetBaseArrayLayer(), 1);
+
+ renderPassBuilder->SetRenderTargetEndingAccessResolve(i, attachmentInfo.storeOp,
+ view, resolveDestinationView);
+ } else {
+ renderPassBuilder->SetRenderTargetEndingAccess(i, attachmentInfo.storeOp);
+ }
+
+ // Set whether or not the texture requires initialization after the pass.
+ bool isInitialized = attachmentInfo.storeOp == wgpu::StoreOp::Store;
+ texture->SetIsSubresourceContentInitialized(isInitialized, view->GetBaseMipLevel(), 1,
+ view->GetBaseArrayLayer(), 1);
+ }
+
+ if (renderPass->attachmentState->HasDepthStencilAttachment()) {
+ RenderPassDepthStencilAttachmentInfo& attachmentInfo =
+ renderPass->depthStencilAttachment;
+ TextureView* view = ToBackend(renderPass->depthStencilAttachment.view.Get());
+ Texture* texture = ToBackend(view->GetTexture());
+
+ const bool hasDepth = view->GetTexture()->GetFormat().HasDepth();
+ const bool hasStencil = view->GetTexture()->GetFormat().HasStencil();
+
+ // Load operations are changed to clear when the texture is uninitialized.
+ if (!view->GetTexture()->IsSubresourceContentInitialized(
+ view->GetBaseMipLevel(), view->GetLevelCount(), view->GetBaseArrayLayer(),
+ view->GetLayerCount())) {
+ if (hasDepth && attachmentInfo.depthLoadOp == wgpu::LoadOp::Load) {
+ attachmentInfo.clearDepth = 0.0f;
+ attachmentInfo.depthLoadOp = wgpu::LoadOp::Clear;
+ }
+ if (hasStencil && attachmentInfo.stencilLoadOp == wgpu::LoadOp::Load) {
+ attachmentInfo.clearStencil = 0u;
+ attachmentInfo.stencilLoadOp = wgpu::LoadOp::Clear;
+ }
+ }
+
+ // Set depth/stencil load operations.
+ if (hasDepth) {
+ renderPassBuilder->SetDepthAccess(
+ attachmentInfo.depthLoadOp, attachmentInfo.depthStoreOp,
+ attachmentInfo.clearDepth, view->GetD3D12Format());
+ } else {
+ renderPassBuilder->SetDepthNoAccess();
+ }
+
+ if (hasStencil) {
+ renderPassBuilder->SetStencilAccess(
+ attachmentInfo.stencilLoadOp, attachmentInfo.stencilStoreOp,
+ attachmentInfo.clearStencil, view->GetD3D12Format());
+ } else {
+ renderPassBuilder->SetStencilNoAccess();
+ }
+
+ // Set whether or not the texture requires initialization.
+ ASSERT(!hasDepth || !hasStencil ||
+ attachmentInfo.depthStoreOp == attachmentInfo.stencilStoreOp);
+ bool isInitialized = attachmentInfo.depthStoreOp == wgpu::StoreOp::Store;
+ texture->SetIsSubresourceContentInitialized(isInitialized, view->GetBaseMipLevel(), 1,
+ view->GetBaseArrayLayer(), 1);
+ } else {
+ renderPassBuilder->SetDepthStencilNoAccess();
+ }
+ }
+
+ void CommandBuffer::EmulateBeginRenderPass(CommandRecordingContext* commandContext,
+ const RenderPassBuilder* renderPassBuilder) const {
ID3D12GraphicsCommandList* commandList = commandContext->GetCommandList();
- // Clear framebuffer attachments as needed and transition to render target
+ // Clear framebuffer attachments as needed.
{
- for (uint32_t i :
- IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) {
- auto& attachmentInfo = renderPass->colorAttachments[i];
- TextureView* view = ToBackend(attachmentInfo.view.Get());
-
+ for (uint32_t i = 0; i < renderPassBuilder->GetColorAttachmentCount(); i++) {
// Load op - color
- ASSERT(view->GetLevelCount() == 1);
- ASSERT(view->GetLayerCount() == 1);
- if (attachmentInfo.loadOp == wgpu::LoadOp::Clear ||
- (attachmentInfo.loadOp == wgpu::LoadOp::Load &&
- !view->GetTexture()->IsSubresourceContentInitialized(
- view->GetBaseMipLevel(), 1, view->GetBaseArrayLayer(), 1))) {
- D3D12_CPU_DESCRIPTOR_HANDLE handle = args.RTVs[i];
- commandList->ClearRenderTargetView(handle, &attachmentInfo.clearColor.r, 0,
- nullptr);
- }
-
- TextureView* resolveView = ToBackend(attachmentInfo.resolveTarget.Get());
- if (resolveView != nullptr) {
- // We need to set the resolve target to initialized so that it does not get
- // cleared later in the pipeline. The texture will be resolved from the source
- // color attachment, which will be correctly initialized.
- ToBackend(resolveView->GetTexture())
- ->SetIsSubresourceContentInitialized(
- true, resolveView->GetBaseMipLevel(), resolveView->GetLevelCount(),
- resolveView->GetBaseArrayLayer(), resolveView->GetLayerCount());
- }
-
- switch (attachmentInfo.storeOp) {
- case wgpu::StoreOp::Store: {
- view->GetTexture()->SetIsSubresourceContentInitialized(
- true, view->GetBaseMipLevel(), 1, view->GetBaseArrayLayer(), 1);
- } break;
-
- case wgpu::StoreOp::Clear: {
- view->GetTexture()->SetIsSubresourceContentInitialized(
- false, view->GetBaseMipLevel(), 1, view->GetBaseArrayLayer(), 1);
- } break;
-
- default: { UNREACHABLE(); } break;
+ if (renderPassBuilder->GetRenderPassRenderTargetDescriptors()[i]
+ .BeginningAccess.Type == D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR) {
+ commandList->ClearRenderTargetView(
+ renderPassBuilder->GetRenderPassRenderTargetDescriptors()[i].cpuDescriptor,
+ renderPassBuilder->GetRenderPassRenderTargetDescriptors()[i]
+ .BeginningAccess.Clear.ClearValue.Color,
+ 0, nullptr);
}
}
- if (renderPass->attachmentState->HasDepthStencilAttachment()) {
- auto& attachmentInfo = renderPass->depthStencilAttachment;
- Texture* texture = ToBackend(renderPass->depthStencilAttachment.view->GetTexture());
- TextureView* view = ToBackend(attachmentInfo.view.Get());
- float clearDepth = attachmentInfo.clearDepth;
+ if (renderPassBuilder->HasDepth()) {
+ D3D12_CLEAR_FLAGS clearFlags = {};
+ float depthClear = 0.0f;
+ uint8_t stencilClear = 0u;
+
+ if (renderPassBuilder->GetRenderPassDepthStencilDescriptor()
+ ->DepthBeginningAccess.Type ==
+ D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR) {
+ clearFlags |= D3D12_CLEAR_FLAG_DEPTH;
+ depthClear = renderPassBuilder->GetRenderPassDepthStencilDescriptor()
+ ->DepthBeginningAccess.Clear.ClearValue.DepthStencil.Depth;
+ }
+ if (renderPassBuilder->GetRenderPassDepthStencilDescriptor()
+ ->StencilBeginningAccess.Type ==
+ D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR) {
+ clearFlags |= D3D12_CLEAR_FLAG_STENCIL;
+ stencilClear =
+ renderPassBuilder->GetRenderPassDepthStencilDescriptor()
+ ->StencilBeginningAccess.Clear.ClearValue.DepthStencil.Stencil;
+ }
+
// TODO(kainino@chromium.org): investigate: should the Dawn clear
// stencil type be uint8_t?
- uint8_t clearStencil = static_cast<uint8_t>(attachmentInfo.clearStencil);
-
- // Load op - depth/stencil
- bool doDepthClear = texture->GetFormat().HasDepth() &&
- (attachmentInfo.depthLoadOp == wgpu::LoadOp::Clear);
- bool doStencilClear = texture->GetFormat().HasStencil() &&
- (attachmentInfo.stencilLoadOp == wgpu::LoadOp::Clear);
-
- D3D12_CLEAR_FLAGS clearFlags = {};
- if (doDepthClear) {
- clearFlags |= D3D12_CLEAR_FLAG_DEPTH;
- }
- if (doStencilClear) {
- clearFlags |= D3D12_CLEAR_FLAG_STENCIL;
- }
- // If the depth stencil texture has not been initialized, we want to use loadop
- // clear to init the contents to 0's
- if (!texture->IsSubresourceContentInitialized(
- view->GetBaseMipLevel(), view->GetLevelCount(), view->GetBaseArrayLayer(),
- view->GetLayerCount())) {
- if (texture->GetFormat().HasDepth() &&
- attachmentInfo.depthLoadOp == wgpu::LoadOp::Load) {
- clearDepth = 0.0f;
- clearFlags |= D3D12_CLEAR_FLAG_DEPTH;
- }
- if (texture->GetFormat().HasStencil() &&
- attachmentInfo.stencilLoadOp == wgpu::LoadOp::Load) {
- clearStencil = 0u;
- clearFlags |= D3D12_CLEAR_FLAG_STENCIL;
- }
- }
-
if (clearFlags) {
- D3D12_CPU_DESCRIPTOR_HANDLE handle = args.dsv;
- commandList->ClearDepthStencilView(handle, clearFlags, clearDepth, clearStencil,
- 0, nullptr);
- }
-
- if (attachmentInfo.depthStoreOp == wgpu::StoreOp::Store &&
- attachmentInfo.stencilStoreOp == wgpu::StoreOp::Store) {
- texture->SetIsSubresourceContentInitialized(
- true, view->GetBaseMipLevel(), view->GetLevelCount(),
- view->GetBaseArrayLayer(), view->GetLayerCount());
- } else if (attachmentInfo.depthStoreOp == wgpu::StoreOp::Clear &&
- attachmentInfo.stencilStoreOp == wgpu::StoreOp::Clear) {
- texture->SetIsSubresourceContentInitialized(
- false, view->GetBaseMipLevel(), view->GetLevelCount(),
- view->GetBaseArrayLayer(), view->GetLayerCount());
+ commandList->ClearDepthStencilView(
+ renderPassBuilder->GetRenderPassDepthStencilDescriptor()->cpuDescriptor,
+ clearFlags, depthClear, stencilClear, 0, nullptr);
}
}
}
- // Set up render targets
- {
- if (args.dsv.ptr) {
- commandList->OMSetRenderTargets(args.numRTVs, args.RTVs.data(), FALSE, &args.dsv);
- } else {
- commandList->OMSetRenderTargets(args.numRTVs, args.RTVs.data(), FALSE, nullptr);
- }
+ commandList->OMSetRenderTargets(
+ renderPassBuilder->GetColorAttachmentCount(), renderPassBuilder->GetRenderTargetViews(),
+ FALSE,
+ renderPassBuilder->HasDepth()
+ ? &renderPassBuilder->GetRenderPassDepthStencilDescriptor()->cpuDescriptor
+ : nullptr);
+ }
+
+ void CommandBuffer::RecordRenderPass(
+ CommandRecordingContext* commandContext,
+ BindGroupStateTracker* bindingTracker,
+ RenderPassDescriptorHeapTracker* renderPassDescriptorHeapTracker,
+ BeginRenderPassCmd* renderPass,
+ const bool passHasUAV) {
+ OMSetRenderTargetArgs args =
+ renderPassDescriptorHeapTracker->GetSubpassOMSetRenderTargetArgs(renderPass);
+
+ const bool useRenderPass = GetDevice()->IsToggleEnabled(Toggle::UseD3D12RenderPass);
+
+ // renderPassBuilder must be scoped to RecordRenderPass because any underlying
+ // D3D12_RENDER_PASS_ENDING_ACCESS_RESOLVE_SUBRESOURCE_PARAMETERS structs must remain
+ // valid until after EndRenderPass() has been called.
+ RenderPassBuilder renderPassBuilder(args, passHasUAV);
+
+ SetupRenderPass(commandContext, renderPass, &renderPassBuilder);
+
+ // Use D3D12's native render pass API if it's available, otherwise emulate the
+ // beginning and ending access operations.
+ if (useRenderPass) {
+ commandContext->GetCommandList4()->BeginRenderPass(
+ renderPassBuilder.GetColorAttachmentCount(),
+ renderPassBuilder.GetRenderPassRenderTargetDescriptors(),
+ renderPassBuilder.HasDepth()
+ ? renderPassBuilder.GetRenderPassDepthStencilDescriptor()
+ : nullptr,
+ renderPassBuilder.GetRenderPassFlags());
+ } else {
+ EmulateBeginRenderPass(commandContext, &renderPassBuilder);
}
+ ID3D12GraphicsCommandList* commandList = commandContext->GetCommandList();
+
// Set up default dynamic state
{
uint32_t width = renderPass->width;
@@ -1189,10 +1262,9 @@
switch (type) {
case Command::EndRenderPass: {
mCommands.NextCommand<EndRenderPassCmd>();
-
- // TODO(brandon1.jones@intel.com): avoid calling this function and enable MSAA
- // resolve in D3D12 render pass on the platforms that support this feature.
- if (renderPass->attachmentState->GetSampleCount() > 1) {
+ if (useRenderPass) {
+ commandContext->GetCommandList4()->EndRenderPass();
+ } else if (renderPass->attachmentState->GetSampleCount() > 1) {
ResolveMultisampledRenderPass(commandContext, renderPass);
}
return;
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.h b/src/dawn_native/d3d12/CommandBufferD3D12.h
index 3e1a16f..52f95fd 100644
--- a/src/dawn_native/d3d12/CommandBufferD3D12.h
+++ b/src/dawn_native/d3d12/CommandBufferD3D12.h
@@ -31,10 +31,17 @@
namespace dawn_native { namespace d3d12 {
+ struct OMSetRenderTargetArgs {
+ unsigned int numRTVs = 0;
+ std::array<D3D12_CPU_DESCRIPTOR_HANDLE, kMaxColorAttachments> RTVs = {};
+ D3D12_CPU_DESCRIPTOR_HANDLE dsv = {};
+ };
+
class BindGroupStateTracker;
class CommandRecordingContext;
class Device;
class RenderPassDescriptorHeapTracker;
+ class RenderPassBuilder;
class RenderPipeline;
class CommandBuffer : public CommandBufferBase {
@@ -49,8 +56,14 @@
BindGroupStateTracker* bindingTracker);
void RecordRenderPass(CommandRecordingContext* commandContext,
BindGroupStateTracker* bindingTracker,
- RenderPassDescriptorHeapTracker* renderPassTracker,
- BeginRenderPassCmd* renderPass);
+ RenderPassDescriptorHeapTracker* renderPassDescriptorHeapTracker,
+ BeginRenderPassCmd* renderPass,
+ bool passHasUAV);
+ void SetupRenderPass(CommandRecordingContext* commandContext,
+ BeginRenderPassCmd* renderPass,
+ RenderPassBuilder* renderPassBuilder);
+ void EmulateBeginRenderPass(CommandRecordingContext* commandContext,
+ const RenderPassBuilder* renderPassBuilder) const;
CommandIterator mCommands;
};
diff --git a/src/dawn_native/d3d12/CommandRecordingContext.cpp b/src/dawn_native/d3d12/CommandRecordingContext.cpp
index 76ea3b0..209009c 100644
--- a/src/dawn_native/d3d12/CommandRecordingContext.cpp
+++ b/src/dawn_native/d3d12/CommandRecordingContext.cpp
@@ -41,6 +41,9 @@
nullptr, IID_PPV_ARGS(&d3d12GraphicsCommandList)),
"D3D12 creating direct command list"));
mD3d12CommandList = std::move(d3d12GraphicsCommandList);
+ // Store a cast to ID3D12GraphicsCommandList4. This is required to use the D3D12 render
+ // pass APIs introduced in Windows build 1809.
+ mD3d12CommandList.As(&mD3d12CommandList4);
}
mIsOpen = true;
@@ -80,8 +83,17 @@
return mD3d12CommandList.Get();
}
+ // This function will fail on Windows versions prior to 1809. Support must be queried through
+ // the device before calling.
+ ID3D12GraphicsCommandList4* CommandRecordingContext::GetCommandList4() const {
+ ASSERT(IsOpen());
+ ASSERT(mD3d12CommandList.Get() != nullptr);
+ return mD3d12CommandList4.Get();
+ }
+
void CommandRecordingContext::Release() {
mD3d12CommandList.Reset();
+ mD3d12CommandList4.Reset();
mIsOpen = false;
mSharedTextures.clear();
}
diff --git a/src/dawn_native/d3d12/CommandRecordingContext.h b/src/dawn_native/d3d12/CommandRecordingContext.h
index a0b0c83..d501d59 100644
--- a/src/dawn_native/d3d12/CommandRecordingContext.h
+++ b/src/dawn_native/d3d12/CommandRecordingContext.h
@@ -31,6 +31,7 @@
CommandAllocatorManager* commandAllocationManager);
ID3D12GraphicsCommandList* GetCommandList() const;
+ ID3D12GraphicsCommandList4* GetCommandList4() const;
void Release();
bool IsOpen() const;
@@ -38,6 +39,7 @@
private:
ComPtr<ID3D12GraphicsCommandList> mD3d12CommandList;
+ ComPtr<ID3D12GraphicsCommandList4> mD3d12CommandList4;
bool mIsOpen = false;
std::set<Texture*> mSharedTextures;
};
diff --git a/src/dawn_native/d3d12/D3D12Info.cpp b/src/dawn_native/d3d12/D3D12Info.cpp
index edfeb82..de9bd03 100644
--- a/src/dawn_native/d3d12/D3D12Info.cpp
+++ b/src/dawn_native/d3d12/D3D12Info.cpp
@@ -24,25 +24,33 @@
ResultOrError<D3D12DeviceInfo> GatherDeviceInfo(const Adapter& adapter) {
D3D12DeviceInfo info = {};
- // Gather info about device memory
- {
- // Newer builds replace D3D_FEATURE_DATA_ARCHITECTURE with
- // D3D_FEATURE_DATA_ARCHITECTURE1. However, D3D_FEATURE_DATA_ARCHITECTURE can be used
- // for backwards compat.
- // https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/ne-d3d12-d3d12_feature
- D3D12_FEATURE_DATA_ARCHITECTURE arch = {};
- DAWN_TRY(CheckHRESULT(adapter.GetDevice()->CheckFeatureSupport(
- D3D12_FEATURE_ARCHITECTURE, &arch, sizeof(arch)),
- "ID3D12Device::CheckFeatureSupport"));
+ // Newer builds replace D3D_FEATURE_DATA_ARCHITECTURE with
+ // D3D_FEATURE_DATA_ARCHITECTURE1. However, D3D_FEATURE_DATA_ARCHITECTURE can be used
+ // for backwards compat.
+ // https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/ne-d3d12-d3d12_feature
+ D3D12_FEATURE_DATA_ARCHITECTURE arch = {};
+ DAWN_TRY(CheckHRESULT(adapter.GetDevice()->CheckFeatureSupport(D3D12_FEATURE_ARCHITECTURE,
+ &arch, sizeof(arch)),
+ "ID3D12Device::CheckFeatureSupport"));
- info.isUMA = arch.UMA;
+ info.isUMA = arch.UMA;
- D3D12_FEATURE_DATA_D3D12_OPTIONS options = {};
- DAWN_TRY(CheckHRESULT(adapter.GetDevice()->CheckFeatureSupport(
- D3D12_FEATURE_D3D12_OPTIONS, &options, sizeof(options)),
- "ID3D12Device::CheckFeatureSupport"));
+ D3D12_FEATURE_DATA_D3D12_OPTIONS options = {};
+ DAWN_TRY(CheckHRESULT(adapter.GetDevice()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS,
+ &options, sizeof(options)),
+ "ID3D12Device::CheckFeatureSupport"));
- info.resourceHeapTier = options.ResourceHeapTier;
+ info.resourceHeapTier = options.ResourceHeapTier;
+
+ // Windows builds 1809 and above can use the D3D12 render pass API. If we query
+ // CheckFeatureSupport for D3D12_FEATURE_D3D12_OPTIONS5 successfully, then we can use
+ // the render pass API.
+ D3D12_FEATURE_DATA_D3D12_OPTIONS5 featureOptions5 = {};
+ if (SUCCEEDED(adapter.GetDevice()->CheckFeatureSupport(
+ D3D12_FEATURE_D3D12_OPTIONS5, &featureOptions5, sizeof(featureOptions5)))) {
+ info.supportsRenderPass = true;
+ } else {
+ info.supportsRenderPass = false;
}
return info;
diff --git a/src/dawn_native/d3d12/D3D12Info.h b/src/dawn_native/d3d12/D3D12Info.h
index 1471be0..78d3820 100644
--- a/src/dawn_native/d3d12/D3D12Info.h
+++ b/src/dawn_native/d3d12/D3D12Info.h
@@ -25,6 +25,7 @@
struct D3D12DeviceInfo {
bool isUMA;
uint32_t resourceHeapTier;
+ bool supportsRenderPass;
};
ResultOrError<D3D12DeviceInfo> GatherDeviceInfo(const Adapter& adapter);
diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp
index 4a384a0..7ce3a3e 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -414,6 +414,7 @@
void Device::InitTogglesFromDriver() {
const bool useResourceHeapTier2 = (GetDeviceInfo().resourceHeapTier >= 2);
SetToggle(Toggle::UseD3D12ResourceHeapTier2, useResourceHeapTier2);
+ SetToggle(Toggle::UseD3D12RenderPass, GetDeviceInfo().supportsRenderPass);
}
}} // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/RenderPassBuilderD3D12.cpp b/src/dawn_native/d3d12/RenderPassBuilderD3D12.cpp
new file mode 100644
index 0000000..e535a66
--- /dev/null
+++ b/src/dawn_native/d3d12/RenderPassBuilderD3D12.cpp
@@ -0,0 +1,229 @@
+// Copyright 2019 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 "dawn_native/d3d12/RenderPassBuilderD3D12.h"
+
+#include "dawn_native/Format.h"
+#include "dawn_native/d3d12/CommandBufferD3D12.h"
+#include "dawn_native/d3d12/Forward.h"
+#include "dawn_native/d3d12/TextureD3D12.h"
+
+#include "dawn_native/dawn_platform.h"
+
+namespace dawn_native { namespace d3d12 {
+
+ namespace {
+ D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE D3D12BeginningAccessType(wgpu::LoadOp loadOp) {
+ switch (loadOp) {
+ case wgpu::LoadOp::Clear:
+ return D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR;
+ case wgpu::LoadOp::Load:
+ return D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ D3D12_RENDER_PASS_ENDING_ACCESS_TYPE D3D12EndingAccessType(wgpu::StoreOp storeOp) {
+ switch (storeOp) {
+ case wgpu::StoreOp::Clear:
+ return D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD;
+ case wgpu::StoreOp::Store:
+ return D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ D3D12_RENDER_PASS_ENDING_ACCESS_RESOLVE_PARAMETERS D3D12EndingAccessResolveParameters(
+ wgpu::StoreOp storeOp,
+ TextureView* resolveSource,
+ TextureView* resolveDestination) {
+ D3D12_RENDER_PASS_ENDING_ACCESS_RESOLVE_PARAMETERS resolveParameters;
+
+ resolveParameters.Format = resolveDestination->GetD3D12Format();
+ resolveParameters.pSrcResource =
+ ToBackend(resolveSource->GetTexture())->GetD3D12Resource();
+ resolveParameters.pDstResource =
+ ToBackend(resolveDestination->GetTexture())->GetD3D12Resource();
+
+ // Clear or preserve the resolve source.
+ if (storeOp == wgpu::StoreOp::Clear) {
+ resolveParameters.PreserveResolveSource = false;
+ } else if (storeOp == wgpu::StoreOp::Store) {
+ resolveParameters.PreserveResolveSource = true;
+ }
+
+ // RESOLVE_MODE_AVERAGE is only valid for non-integer formats.
+ // TODO: Investigate and determine how integer format resolves should work in WebGPU.
+ switch (resolveDestination->GetFormat().type) {
+ case Format::Type::Sint:
+ case Format::Type::Uint:
+ resolveParameters.ResolveMode = D3D12_RESOLVE_MODE_MAX;
+ break;
+ default:
+ resolveParameters.ResolveMode = D3D12_RESOLVE_MODE_AVERAGE;
+ break;
+ }
+
+ resolveParameters.SubresourceCount = 1;
+
+ return resolveParameters;
+ }
+
+ D3D12_RENDER_PASS_ENDING_ACCESS_RESOLVE_SUBRESOURCE_PARAMETERS
+ D3D12EndingAccessResolveSubresourceParameters(TextureView* resolveDestination) {
+ D3D12_RENDER_PASS_ENDING_ACCESS_RESOLVE_SUBRESOURCE_PARAMETERS subresourceParameters;
+ Texture* resolveDestinationTexture = ToBackend(resolveDestination->GetTexture());
+
+ subresourceParameters.DstX = 0;
+ subresourceParameters.DstY = 0;
+ subresourceParameters.SrcSubresource = 0;
+ subresourceParameters.DstSubresource = resolveDestinationTexture->GetSubresourceIndex(
+ resolveDestination->GetBaseMipLevel(), resolveDestination->GetBaseArrayLayer());
+ subresourceParameters.SrcRect = {0, 0, resolveDestinationTexture->GetSize().width,
+ resolveDestinationTexture->GetSize().height};
+
+ return subresourceParameters;
+ }
+ } // anonymous namespace
+
+ RenderPassBuilder::RenderPassBuilder(const OMSetRenderTargetArgs& args, bool hasUAV)
+ : mColorAttachmentCount(args.numRTVs), mRenderTargetViews(args.RTVs.data()) {
+ for (uint32_t i = 0; i < mColorAttachmentCount; i++) {
+ mRenderPassRenderTargetDescriptors[i].cpuDescriptor = args.RTVs[i];
+ }
+
+ mRenderPassDepthStencilDesc.cpuDescriptor = args.dsv;
+
+ if (hasUAV) {
+ mRenderPassFlags = D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES;
+ }
+ }
+
+ uint32_t RenderPassBuilder::GetColorAttachmentCount() const {
+ return mColorAttachmentCount;
+ }
+
+ bool RenderPassBuilder::HasDepth() const {
+ return mHasDepth;
+ }
+
+ const D3D12_RENDER_PASS_RENDER_TARGET_DESC*
+ RenderPassBuilder::GetRenderPassRenderTargetDescriptors() const {
+ return mRenderPassRenderTargetDescriptors.data();
+ }
+
+ const D3D12_RENDER_PASS_DEPTH_STENCIL_DESC*
+ RenderPassBuilder::GetRenderPassDepthStencilDescriptor() const {
+ return &mRenderPassDepthStencilDesc;
+ }
+
+ D3D12_RENDER_PASS_FLAGS RenderPassBuilder::GetRenderPassFlags() const {
+ return mRenderPassFlags;
+ }
+
+ const D3D12_CPU_DESCRIPTOR_HANDLE* RenderPassBuilder::GetRenderTargetViews() const {
+ return mRenderTargetViews;
+ }
+
+ void RenderPassBuilder::SetRenderTargetBeginningAccess(uint32_t attachment,
+ wgpu::LoadOp loadOp,
+ dawn_native::Color clearColor,
+ DXGI_FORMAT format) {
+ mRenderPassRenderTargetDescriptors[attachment].BeginningAccess.Type =
+ D3D12BeginningAccessType(loadOp);
+ if (loadOp == wgpu::LoadOp::Clear) {
+ mRenderPassRenderTargetDescriptors[attachment]
+ .BeginningAccess.Clear.ClearValue.Color[0] = clearColor.r;
+ mRenderPassRenderTargetDescriptors[attachment]
+ .BeginningAccess.Clear.ClearValue.Color[1] = clearColor.g;
+ mRenderPassRenderTargetDescriptors[attachment]
+ .BeginningAccess.Clear.ClearValue.Color[2] = clearColor.b;
+ mRenderPassRenderTargetDescriptors[attachment]
+ .BeginningAccess.Clear.ClearValue.Color[3] = clearColor.a;
+ mRenderPassRenderTargetDescriptors[attachment].BeginningAccess.Clear.ClearValue.Format =
+ format;
+ }
+ }
+
+ void RenderPassBuilder::SetRenderTargetEndingAccess(uint32_t attachment,
+ wgpu::StoreOp storeOp) {
+ mRenderPassRenderTargetDescriptors[attachment].EndingAccess.Type =
+ D3D12EndingAccessType(storeOp);
+ }
+
+ void RenderPassBuilder::SetRenderTargetEndingAccessResolve(uint32_t attachment,
+ wgpu::StoreOp storeOp,
+ TextureView* resolveSource,
+ TextureView* resolveDestination) {
+ mRenderPassRenderTargetDescriptors[attachment].EndingAccess.Type =
+ D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_RESOLVE;
+ mRenderPassRenderTargetDescriptors[attachment].EndingAccess.Resolve =
+ D3D12EndingAccessResolveParameters(storeOp, resolveSource, resolveDestination);
+
+ mSubresourceParams[attachment] =
+ D3D12EndingAccessResolveSubresourceParameters(resolveDestination);
+
+ mRenderPassRenderTargetDescriptors[attachment].EndingAccess.Resolve.pSubresourceParameters =
+ &mSubresourceParams[attachment];
+ }
+
+ void RenderPassBuilder::SetDepthAccess(wgpu::LoadOp loadOp,
+ wgpu::StoreOp storeOp,
+ float clearDepth,
+ DXGI_FORMAT format) {
+ mHasDepth = true;
+ mRenderPassDepthStencilDesc.DepthBeginningAccess.Type = D3D12BeginningAccessType(loadOp);
+ if (loadOp == wgpu::LoadOp::Clear) {
+ mRenderPassDepthStencilDesc.DepthBeginningAccess.Clear.ClearValue.DepthStencil.Depth =
+ clearDepth;
+ mRenderPassDepthStencilDesc.DepthBeginningAccess.Clear.ClearValue.Format = format;
+ }
+ mRenderPassDepthStencilDesc.DepthEndingAccess.Type = D3D12EndingAccessType(storeOp);
+ }
+
+ void RenderPassBuilder::SetStencilAccess(wgpu::LoadOp loadOp,
+ wgpu::StoreOp storeOp,
+ uint8_t clearStencil,
+ DXGI_FORMAT format) {
+ mRenderPassDepthStencilDesc.StencilBeginningAccess.Type = D3D12BeginningAccessType(loadOp);
+ if (loadOp == wgpu::LoadOp::Clear) {
+ mRenderPassDepthStencilDesc.StencilBeginningAccess.Clear.ClearValue.DepthStencil
+ .Stencil = clearStencil;
+ mRenderPassDepthStencilDesc.StencilBeginningAccess.Clear.ClearValue.Format = format;
+ }
+ mRenderPassDepthStencilDesc.StencilEndingAccess.Type = D3D12EndingAccessType(storeOp);
+ }
+
+ void RenderPassBuilder::SetDepthNoAccess() {
+ mRenderPassDepthStencilDesc.DepthBeginningAccess.Type =
+ D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS;
+ mRenderPassDepthStencilDesc.DepthEndingAccess.Type =
+ D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS;
+ }
+
+ void RenderPassBuilder::SetDepthStencilNoAccess() {
+ SetDepthNoAccess();
+ SetStencilNoAccess();
+ }
+
+ void RenderPassBuilder::SetStencilNoAccess() {
+ mRenderPassDepthStencilDesc.StencilBeginningAccess.Type =
+ D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS;
+ mRenderPassDepthStencilDesc.StencilEndingAccess.Type =
+ D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS;
+ }
+
+}} // namespace dawn_native::d3d12
\ No newline at end of file
diff --git a/src/dawn_native/d3d12/RenderPassBuilderD3D12.h b/src/dawn_native/d3d12/RenderPassBuilderD3D12.h
new file mode 100644
index 0000000..1ecd87e
--- /dev/null
+++ b/src/dawn_native/d3d12/RenderPassBuilderD3D12.h
@@ -0,0 +1,89 @@
+// Copyright 2019 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 DAWNNATIVE_D3D12_RENDERPASSBUILDERD3D12_H_
+#define DAWNNATIVE_D3D12_RENDERPASSBUILDERD3D12_H_
+
+#include "common/Constants.h"
+#include "dawn_native/d3d12/d3d12_platform.h"
+#include "dawn_native/dawn_platform.h"
+
+#include <array>
+
+namespace dawn_native { namespace d3d12 {
+
+ class TextureView;
+
+ struct OMSetRenderTargetArgs;
+
+ // RenderPassBuilder stores parameters related to render pass load and store operations.
+ // When the D3D12 render pass API is available, the needed descriptors can be fetched
+ // directly from the RenderPassBuilder. When the D3D12 render pass API is not available, the
+ // descriptors are still fetched and any information necessary to emulate the load and store
+ // operations is extracted from the descriptors.
+ class RenderPassBuilder {
+ public:
+ RenderPassBuilder(const OMSetRenderTargetArgs& args, bool hasUAV);
+
+ uint32_t GetColorAttachmentCount() const;
+
+ // Returns descriptors that are fed directly to BeginRenderPass, or are used as parameter
+ // storage if D3D12 render pass API is unavailable.
+ const D3D12_RENDER_PASS_RENDER_TARGET_DESC* GetRenderPassRenderTargetDescriptors() const;
+ const D3D12_RENDER_PASS_DEPTH_STENCIL_DESC* GetRenderPassDepthStencilDescriptor() const;
+
+ D3D12_RENDER_PASS_FLAGS GetRenderPassFlags() const;
+
+ // Returns attachment RTVs to use with OMSetRenderTargets.
+ const D3D12_CPU_DESCRIPTOR_HANDLE* GetRenderTargetViews() const;
+
+ bool HasDepth() const;
+
+ // Functions that set the appropriate values in the render pass descriptors.
+ void SetDepthAccess(wgpu::LoadOp loadOp,
+ wgpu::StoreOp storeOp,
+ float clearDepth,
+ DXGI_FORMAT format);
+ void SetDepthNoAccess();
+ void SetDepthStencilNoAccess();
+ void SetRenderTargetBeginningAccess(uint32_t attachment,
+ wgpu::LoadOp loadOp,
+ dawn_native::Color clearColor,
+ DXGI_FORMAT format);
+ void SetRenderTargetEndingAccess(uint32_t attachment, wgpu::StoreOp storeOp);
+ void SetRenderTargetEndingAccessResolve(uint32_t attachment,
+ wgpu::StoreOp storeOp,
+ TextureView* resolveSource,
+ TextureView* resolveDestination);
+ void SetStencilAccess(wgpu::LoadOp loadOp,
+ wgpu::StoreOp storeOp,
+ uint8_t clearStencil,
+ DXGI_FORMAT format);
+ void SetStencilNoAccess();
+
+ private:
+ uint32_t mColorAttachmentCount = 0;
+ bool mHasDepth = false;
+ D3D12_RENDER_PASS_FLAGS mRenderPassFlags = D3D12_RENDER_PASS_FLAG_NONE;
+ D3D12_RENDER_PASS_DEPTH_STENCIL_DESC mRenderPassDepthStencilDesc;
+ std::array<D3D12_RENDER_PASS_RENDER_TARGET_DESC, kMaxColorAttachments>
+ mRenderPassRenderTargetDescriptors;
+ const D3D12_CPU_DESCRIPTOR_HANDLE* mRenderTargetViews;
+ std::array<D3D12_RENDER_PASS_ENDING_ACCESS_RESOLVE_SUBRESOURCE_PARAMETERS,
+ kMaxColorAttachments>
+ mSubresourceParams;
+ };
+}} // namespace dawn_native::d3d12
+
+#endif // DAWNNATIVE_D3D12_RENDERPASSBUILDERD3D12_H_
\ No newline at end of file
diff --git a/src/tests/end2end/MultisampledRenderingTests.cpp b/src/tests/end2end/MultisampledRenderingTests.cpp
index cd1b0ee..1097391 100644
--- a/src/tests/end2end/MultisampledRenderingTests.cpp
+++ b/src/tests/end2end/MultisampledRenderingTests.cpp
@@ -506,6 +506,7 @@
DAWN_INSTANTIATE_TEST(MultisampledRenderingTest,
D3D12Backend,
ForceWorkarounds(D3D12Backend, {}, {"use_d3d12_resource_heap_tier2"}),
+ ForceWorkarounds(D3D12Backend, {}, {"use_d3d12_render_pass"}),
MetalBackend,
OpenGLBackend,
VulkanBackend,
diff --git a/src/tests/end2end/RenderPassTests.cpp b/src/tests/end2end/RenderPassTests.cpp
index c17af18..6d7660f 100644
--- a/src/tests/end2end/RenderPassTests.cpp
+++ b/src/tests/end2end/RenderPassTests.cpp
@@ -171,4 +171,9 @@
EXPECT_PIXEL_RGBA8_EQ(kRed, renderTarget, kRTSize - 1, 1);
}
-DAWN_INSTANTIATE_TEST(RenderPassTest, D3D12Backend, MetalBackend, OpenGLBackend, VulkanBackend);
+DAWN_INSTANTIATE_TEST(RenderPassTest,
+ D3D12Backend,
+ ForceWorkarounds(D3D12Backend, {}, {"use_d3d12_render_pass"}),
+ MetalBackend,
+ OpenGLBackend,
+ VulkanBackend);
diff --git a/src/tests/end2end/TextureZeroInitTests.cpp b/src/tests/end2end/TextureZeroInitTests.cpp
index f19e316..2f68329 100644
--- a/src/tests/end2end/TextureZeroInitTests.cpp
+++ b/src/tests/end2end/TextureZeroInitTests.cpp
@@ -779,5 +779,8 @@
DAWN_INSTANTIATE_TEST(
TextureZeroInitTest,
ForceWorkarounds(D3D12Backend, {"nonzero_clear_resources_on_creation_for_testing"}),
+ ForceWorkarounds(D3D12Backend,
+ {"nonzero_clear_resources_on_creation_for_testing"},
+ {"use_d3d12_render_pass"}),
ForceWorkarounds(OpenGLBackend, {"nonzero_clear_resources_on_creation_for_testing"}),
ForceWorkarounds(VulkanBackend, {"nonzero_clear_resources_on_creation_for_testing"}));