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"}));