Implement texture subresource on D3D12

When we use a texture for different purpose, we need to add proper
barrier(s) in order to make it ready. Previously, the barrier is
done per entire texture. So it is invalid to sample/read/copy from
one subresource (say a mip/array slice) and render/write/copy to
another subresource of the same texture at the same time.

With this patch, barrier is set per each texture subresource. So it is
valid to use a subresource as source and use another subresource of the
same texture as destination at the same time.

However, planar slices like depth/stencil planes are not handled
gracefully. This is a TODO task. Another task is to combine barriers
into one if they can be combined. I will do this optimization in
another patch in near future.

Bug: dawn:157

Change-Id: I783a76cb88fcdffb60c307ddfb89d50f1583201a
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/22101
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Yunchao He <yunchao.he@intel.com>
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
index 2321a4c..86254d7 100644
--- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
@@ -169,20 +169,26 @@
                                                                  wgpu::BufferUsage::Storage);
                                 break;
 
-                            case wgpu::BindingType::ReadonlyStorageTexture:
-                                ToBackend(static_cast<TextureView*>(mBindings[index][binding])
-                                              ->GetTexture())
-                                    ->TrackUsageAndTransitionNow(commandContext,
-                                                                 kReadonlyStorageTexture);
+                            case wgpu::BindingType::ReadonlyStorageTexture: {
+                                TextureViewBase* view =
+                                    static_cast<TextureViewBase*>(mBindings[index][binding]);
+                                ToBackend(view->GetTexture())
+                                    ->TrackUsageAndTransitionNow(
+                                        commandContext, kReadonlyStorageTexture,
+                                        view->GetBaseMipLevel(), view->GetLevelCount(),
+                                        view->GetBaseArrayLayer(), view->GetLayerCount());
                                 break;
-
-                            case wgpu::BindingType::WriteonlyStorageTexture:
-                                ToBackend(static_cast<TextureView*>(mBindings[index][binding])
-                                              ->GetTexture())
-                                    ->TrackUsageAndTransitionNow(commandContext,
-                                                                 wgpu::TextureUsage::Storage);
+                            }
+                            case wgpu::BindingType::WriteonlyStorageTexture: {
+                                TextureViewBase* view =
+                                    static_cast<TextureViewBase*>(mBindings[index][binding]);
+                                ToBackend(view->GetTexture())
+                                    ->TrackUsageAndTransitionNow(
+                                        commandContext, wgpu::TextureUsage::Storage,
+                                        view->GetBaseMipLevel(), view->GetLevelCount(),
+                                        view->GetBaseArrayLayer(), view->GetLayerCount());
                                 break;
-
+                            }
                             case wgpu::BindingType::StorageTexture:
                                 // Not implemented.
 
@@ -434,15 +440,19 @@
                     continue;
                 }
 
-                Texture* colorTexture =
-                    ToBackend(renderPass->colorAttachments[i].view->GetTexture());
+                TextureViewBase* colorView = renderPass->colorAttachments[i].view.Get();
+                Texture* colorTexture = ToBackend(colorView->GetTexture());
                 Texture* resolveTexture = ToBackend(resolveTarget->GetTexture());
 
                 // Transition the usages of the color attachment and resolve target.
-                colorTexture->TrackUsageAndTransitionNow(commandContext,
-                                                         D3D12_RESOURCE_STATE_RESOLVE_SOURCE);
-                resolveTexture->TrackUsageAndTransitionNow(commandContext,
-                                                           D3D12_RESOURCE_STATE_RESOLVE_DEST);
+                colorTexture->TrackUsageAndTransitionNow(
+                    commandContext, D3D12_RESOURCE_STATE_RESOLVE_SOURCE,
+                    colorView->GetBaseMipLevel(), colorView->GetLevelCount(),
+                    colorView->GetBaseArrayLayer(), colorView->GetLayerCount());
+                resolveTexture->TrackUsageAndTransitionNow(
+                    commandContext, D3D12_RESOURCE_STATE_RESOLVE_DEST,
+                    resolveTarget->GetBaseMipLevel(), resolveTarget->GetLevelCount(),
+                    resolveTarget->GetBaseArrayLayer(), resolveTarget->GetLayerCount());
 
                 // Do MSAA resolve with ResolveSubResource().
                 ID3D12Resource* colorTextureHandle = colorTexture->GetD3D12Resource();
@@ -510,12 +520,9 @@
             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])
-                        ->TrackUsageAndGetResourceBarrier(commandContext, &barrier,
-                                                          usages.textureUsages[i].usage)) {
-                    barriers.push_back(barrier);
-                }
+                ToBackend(usages.textures[i])
+                    ->TrackUsageAndGetResourceBarrierForPass(
+                        commandContext, &barriers, usages.textureUsages[i].subresourceUsages);
                 textureUsages |= usages.textureUsages[i].usage;
             }
 
@@ -593,8 +600,9 @@
                     }
 
                     buffer->TrackUsageAndTransitionNow(commandContext, wgpu::BufferUsage::CopySrc);
-                    texture->TrackUsageAndTransitionNow(commandContext,
-                                                        wgpu::TextureUsage::CopyDst);
+                    texture->TrackUsageAndTransitionNow(commandContext, wgpu::TextureUsage::CopyDst,
+                                                        copy->destination.mipLevel, 1,
+                                                        copy->destination.arrayLayer, 1);
 
                     auto copySplit = ComputeTextureCopySplit(
                         copy->destination.origin, copy->copySize, texture->GetFormat(),
@@ -629,8 +637,9 @@
                     texture->EnsureSubresourceContentInitialized(
                         commandContext, copy->source.mipLevel, 1, copy->source.arrayLayer, 1);
 
-                    texture->TrackUsageAndTransitionNow(commandContext,
-                                                        wgpu::TextureUsage::CopySrc);
+                    texture->TrackUsageAndTransitionNow(commandContext, wgpu::TextureUsage::CopySrc,
+                                                        copy->source.mipLevel, 1,
+                                                        copy->source.arrayLayer, 1);
                     buffer->TrackUsageAndTransitionNow(commandContext, wgpu::BufferUsage::CopyDst);
 
                     TextureCopySplit copySplit = ComputeTextureCopySplit(
@@ -680,9 +689,12 @@
                             commandContext, copy->destination.mipLevel, 1,
                             copy->destination.arrayLayer, copy->copySize.depth);
                     }
-                    source->TrackUsageAndTransitionNow(commandContext, wgpu::TextureUsage::CopySrc);
-                    destination->TrackUsageAndTransitionNow(commandContext,
-                                                            wgpu::TextureUsage::CopyDst);
+                    source->TrackUsageAndTransitionNow(
+                        commandContext, wgpu::TextureUsage::CopySrc, copy->source.mipLevel, 1,
+                        copy->source.arrayLayer, copy->copySize.depth);
+                    destination->TrackUsageAndTransitionNow(
+                        commandContext, wgpu::TextureUsage::CopyDst, copy->destination.mipLevel, 1,
+                        copy->destination.arrayLayer, copy->copySize.depth);
 
                     if (CanUseCopyResource(source, destination, copy->copySize)) {
                         commandList->CopyResource(destination->GetD3D12Resource(),
@@ -870,7 +882,11 @@
                     ToBackend(resolveDestinationView->GetTexture());
 
                 resolveDestinationTexture->TrackUsageAndTransitionNow(
-                    commandContext, D3D12_RESOURCE_STATE_RESOLVE_DEST);
+                    commandContext, D3D12_RESOURCE_STATE_RESOLVE_DEST,
+                    resolveDestinationView->GetBaseMipLevel(),
+                    resolveDestinationView->GetLevelCount(),
+                    resolveDestinationView->GetBaseArrayLayer(),
+                    resolveDestinationView->GetLayerCount());
 
                 renderPassBuilder->SetRenderTargetEndingAccessResolve(i, attachmentInfo.storeOp,
                                                                       view, resolveDestinationView);
diff --git a/src/dawn_native/d3d12/CommandRecordingContext.cpp b/src/dawn_native/d3d12/CommandRecordingContext.cpp
index 81dad3c..d652d87 100644
--- a/src/dawn_native/d3d12/CommandRecordingContext.cpp
+++ b/src/dawn_native/d3d12/CommandRecordingContext.cpp
@@ -61,7 +61,7 @@
             // common state right before command list submission. TransitionUsageNow itself ensures
             // no unnecessary transitions happen if the resources is already in the common state.
             for (Texture* texture : mSharedTextures) {
-                texture->TrackUsageAndTransitionNow(this, D3D12_RESOURCE_STATE_COMMON);
+                texture->TrackAllUsageAndTransitionNow(this, D3D12_RESOURCE_STATE_COMMON);
             }
 
             MaybeError error =
diff --git a/src/dawn_native/d3d12/SwapChainD3D12.cpp b/src/dawn_native/d3d12/SwapChainD3D12.cpp
index 45ec25b..ee5e9cc 100644
--- a/src/dawn_native/d3d12/SwapChainD3D12.cpp
+++ b/src/dawn_native/d3d12/SwapChainD3D12.cpp
@@ -55,7 +55,7 @@
         DAWN_TRY_ASSIGN(commandContext, device->GetPendingCommandContext());
 
         // Perform the necessary transition for the texture to be presented.
-        ToBackend(texture)->TrackUsageAndTransitionNow(commandContext, mTextureUsage);
+        ToBackend(texture)->TrackAllUsageAndTransitionNow(commandContext, mTextureUsage);
 
         DAWN_TRY(device->ExecutePendingCommandContext());
 
diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp
index 77966b4..78bf027 100644
--- a/src/dawn_native/d3d12/TextureD3D12.cpp
+++ b/src/dawn_native/d3d12/TextureD3D12.cpp
@@ -486,10 +486,17 @@
         return {};
     }
 
+    Texture::Texture(Device* device, const TextureDescriptor* descriptor, TextureState state)
+        : TextureBase(device, descriptor, state),
+          mSubresourceStateAndDecay(
+              GetSubresourceCount(),
+              {D3D12_RESOURCE_STATES::D3D12_RESOURCE_STATE_COMMON, UINT64_MAX, false}) {
+    }
+
     Texture::Texture(Device* device,
                      const TextureDescriptor* descriptor,
                      ComPtr<ID3D12Resource> nativeTexture)
-        : TextureBase(device, descriptor, TextureState::OwnedExternal) {
+        : Texture(device, descriptor, TextureState::OwnedExternal) {
         AllocationInfo info;
         info.mMethod = AllocationMethod::kExternal;
         // When creating the ResourceHeapAllocation, the resource heap is set to nullptr because the
@@ -546,126 +553,185 @@
         }
     }
 
-    // When true is returned, a D3D12_RESOURCE_BARRIER has been created and must be used in a
-    // ResourceBarrier call. Failing to do so will cause the tracked state to become invalid and can
-    // cause subsequent errors.
-    bool Texture::TrackUsageAndGetResourceBarrier(CommandRecordingContext* commandContext,
-                                                  D3D12_RESOURCE_BARRIER* barrier,
-                                                  wgpu::TextureUsage newUsage) {
-        return TrackUsageAndGetResourceBarrier(commandContext, barrier,
-                                               D3D12TextureUsage(newUsage, GetFormat()));
+    void Texture::TrackUsageAndTransitionNow(CommandRecordingContext* commandContext,
+                                             wgpu::TextureUsage usage,
+                                             uint32_t mipLevel,
+                                             uint32_t levelCount,
+                                             uint32_t arrayLayer,
+                                             uint32_t layerCount) {
+        TrackUsageAndTransitionNow(commandContext, D3D12TextureUsage(usage, GetFormat()), mipLevel,
+                                   levelCount, arrayLayer, layerCount);
     }
 
-    // When true is returned, a D3D12_RESOURCE_BARRIER has been created and must be used in a
-    // ResourceBarrier call. Failing to do so will cause the tracked state to become invalid and can
-    // cause subsequent errors.
-    bool Texture::TrackUsageAndGetResourceBarrier(CommandRecordingContext* commandContext,
-                                                  D3D12_RESOURCE_BARRIER* barrier,
-                                                  D3D12_RESOURCE_STATES newState) {
+    void Texture::TrackAllUsageAndTransitionNow(CommandRecordingContext* commandContext,
+                                                wgpu::TextureUsage usage) {
+        TrackUsageAndTransitionNow(commandContext, D3D12TextureUsage(usage, GetFormat()), 0,
+                                   GetNumMipLevels(), 0, GetArrayLayers());
+    }
+
+    void Texture::TrackAllUsageAndTransitionNow(CommandRecordingContext* commandContext,
+                                                D3D12_RESOURCE_STATES newState) {
+        TrackUsageAndTransitionNow(commandContext, newState, 0, GetNumMipLevels(), 0,
+                                   GetArrayLayers());
+    }
+
+    void Texture::TrackUsageAndTransitionNow(CommandRecordingContext* commandContext,
+                                             D3D12_RESOURCE_STATES newState,
+                                             uint32_t baseMipLevel,
+                                             uint32_t levelCount,
+                                             uint32_t baseArrayLayer,
+                                             uint32_t layerCount) {
         if (mResourceAllocation.GetInfo().mMethod != AllocationMethod::kExternal) {
             // Track the underlying heap to ensure residency.
             Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap());
             commandContext->TrackHeapUsage(heap, GetDevice()->GetPendingCommandSerial());
         }
 
-        // Return the resource barrier.
-        return TransitionUsageAndGetResourceBarrier(commandContext, barrier, newState);
-    }
+        std::vector<D3D12_RESOURCE_BARRIER> barriers;
+        barriers.reserve(levelCount * layerCount);
 
-    void Texture::TrackUsageAndTransitionNow(CommandRecordingContext* commandContext,
-                                             wgpu::TextureUsage usage) {
-        D3D12_RESOURCE_BARRIER barrier;
-
-        if (TrackUsageAndGetResourceBarrier(commandContext, &barrier, usage)) {
-            commandContext->GetCommandList()->ResourceBarrier(1, &barrier);
+        TransitionUsageAndGetResourceBarrier(commandContext, &barriers, newState, baseMipLevel,
+                                             levelCount, baseArrayLayer, layerCount);
+        if (barriers.size()) {
+            commandContext->GetCommandList()->ResourceBarrier(barriers.size(), barriers.data());
         }
     }
 
-    void Texture::TrackUsageAndTransitionNow(CommandRecordingContext* commandContext,
-                                             D3D12_RESOURCE_STATES newState) {
-        D3D12_RESOURCE_BARRIER barrier;
-
-        if (TrackUsageAndGetResourceBarrier(commandContext, &barrier, newState)) {
-            commandContext->GetCommandList()->ResourceBarrier(1, &barrier);
+    void Texture::TransitionSingleSubresource(std::vector<D3D12_RESOURCE_BARRIER>* barriers,
+                                              D3D12_RESOURCE_STATES newState,
+                                              uint32_t index,
+                                              const Serial pendingCommandSerial) {
+        StateAndDecay* state = &mSubresourceStateAndDecay[index];
+        // Avoid transitioning the texture when it isn't needed.
+        // TODO(cwallez@chromium.org): Need some form of UAV barriers at some point.
+        if (state->lastState == newState) {
+            return;
         }
+
+        D3D12_RESOURCE_STATES lastState = state->lastState;
+
+        // The COMMON state represents a state where no write operations can be pending, and
+        // where all pixels are uncompressed. This makes it possible to transition to and
+        // from some states without synchronization (i.e. without an explicit
+        // ResourceBarrier call). Textures can be implicitly promoted to 1) a single write
+        // state, or 2) multiple read states. Textures will implicitly decay to the COMMON
+        // state when all of the following are true: 1) the texture is accessed on a command
+        // list, 2) the ExecuteCommandLists call that uses that command list has ended, and
+        // 3) the texture was promoted implicitly to a read-only state and is still in that
+        // state.
+        // https://docs.microsoft.com/en-us/windows/desktop/direct3d12/using-resource-barriers-to-synchronize-resource-states-in-direct3d-12#implicit-state-transitions
+
+        // To track implicit decays, we must record the pending serial on which that
+        // transition will occur. When that texture is used again, the previously recorded
+        // serial must be compared to the last completed serial to determine if the texture
+        // has implicity decayed to the common state.
+        if (state->isValidToDecay && pendingCommandSerial > state->lastDecaySerial) {
+            lastState = D3D12_RESOURCE_STATE_COMMON;
+        }
+
+        // Update the tracked state.
+        state->lastState = newState;
+
+        // Destination states that qualify for an implicit promotion for a
+        // non-simultaneous-access texture: NON_PIXEL_SHADER_RESOURCE,
+        // PIXEL_SHADER_RESOURCE, COPY_SRC, COPY_DEST.
+        {
+            static constexpr D3D12_RESOURCE_STATES kD3D12PromotableReadOnlyStates =
+                D3D12_RESOURCE_STATE_COPY_SOURCE | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE |
+                D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
+
+            if (lastState == D3D12_RESOURCE_STATE_COMMON) {
+                if (newState == (newState & kD3D12PromotableReadOnlyStates)) {
+                    // Implicit texture state decays can only occur when the texture was implicitly
+                    // transitioned to a read-only state. isValidToDecay is needed to differentiate
+                    // between resources that were implictly or explicitly transitioned to a
+                    // read-only state.
+                    state->isValidToDecay = true;
+                    state->lastDecaySerial = pendingCommandSerial;
+                    return;
+                } else if (newState == D3D12_RESOURCE_STATE_COPY_DEST) {
+                    state->isValidToDecay = false;
+                    return;
+                }
+            }
+        }
+
+        D3D12_RESOURCE_BARRIER barrier;
+        barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
+        barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
+        barrier.Transition.pResource = GetD3D12Resource();
+        barrier.Transition.StateBefore = lastState;
+        barrier.Transition.StateAfter = newState;
+        barrier.Transition.Subresource = index;
+        barriers->push_back(barrier);
+        // TODO(yunchao.he@intel.com): support subresource for depth/stencil. Depth stencil
+        // texture has different plane slices. While the current implementation only has differernt
+        // mip slices and array slices for subresources.
+        // This is a hack because Dawn doesn't handle subresource of multiplanar resources
+        // correctly. We force the transition to be the same for all planes to match what the
+        // frontend validation checks for. This hack might be incorrect for stencil-only texture
+        // because we always set transition barrier for depth plane.
+        if (newState == D3D12_RESOURCE_STATE_DEPTH_WRITE && GetFormat().HasStencil()) {
+            D3D12_RESOURCE_BARRIER barrierStencil = barrier;
+            barrierStencil.Transition.Subresource += GetArrayLayers() * GetNumMipLevels();
+            barriers->push_back(barrierStencil);
+        }
+
+        state->isValidToDecay = false;
     }
 
-    // When true is returned, a D3D12_RESOURCE_BARRIER has been created and must be used in a
-    // ResourceBarrier call. Failing to do so will cause the tracked state to become invalid and can
-    // cause subsequent errors.
-    bool Texture::TransitionUsageAndGetResourceBarrier(CommandRecordingContext* commandContext,
-                                                       D3D12_RESOURCE_BARRIER* barrier,
-                                                       D3D12_RESOURCE_STATES newState) {
+    void Texture::HandleTransitionSpecialCases(CommandRecordingContext* commandContext) {
         // Textures with keyed mutexes can be written from other graphics queues. Hence, they
         // must be acquired before command list submission to ensure work from the other queues
         // has finished. See Device::ExecuteCommandContext.
         if (mDxgiKeyedMutex != nullptr) {
             commandContext->AddToSharedTextureList(this);
         }
+    }
 
-        // Avoid transitioning the texture when it isn't needed.
-        // TODO(cwallez@chromium.org): Need some form of UAV barriers at some point.
-        if (mLastState == newState) {
-            return false;
-        }
+    void Texture::TransitionUsageAndGetResourceBarrier(
+        CommandRecordingContext* commandContext,
+        std::vector<D3D12_RESOURCE_BARRIER>* barriers,
+        D3D12_RESOURCE_STATES newState,
+        uint32_t baseMipLevel,
+        uint32_t levelCount,
+        uint32_t baseArrayLayer,
+        uint32_t layerCount) {
+        HandleTransitionSpecialCases(commandContext);
 
-        D3D12_RESOURCE_STATES lastState = mLastState;
-
-        // The COMMON state represents a state where no write operations can be pending, and where
-        // all pixels are uncompressed. This makes it possible to transition to and from some states
-        // without synchronization (i.e. without an explicit ResourceBarrier call). Textures can be
-        // implicitly promoted to 1) a single write state, or 2) multiple read states. Textures will
-        // implicitly decay to the COMMON state when all of the following are true: 1) the texture
-        // is accessed on a command list, 2) the ExecuteCommandLists call that uses that command
-        // list has ended, and 3) the texture was promoted implicitly to a read-only state and is
-        // still in that state.
-        // https://docs.microsoft.com/en-us/windows/desktop/direct3d12/using-resource-barriers-to-synchronize-resource-states-in-direct3d-12#implicit-state-transitions
-
-        // To track implicit decays, we must record the pending serial on which that transition will
-        // occur. When that texture is used again, the previously recorded serial must be compared
-        // to the last completed serial to determine if the texture has implicity decayed to the
-        // common state.
         const Serial pendingCommandSerial = ToBackend(GetDevice())->GetPendingCommandSerial();
-        if (mValidToDecay && pendingCommandSerial > mLastUsedSerial) {
-            lastState = D3D12_RESOURCE_STATE_COMMON;
-        }
+        for (uint32_t arrayLayer = 0; arrayLayer < layerCount; ++arrayLayer) {
+            for (uint32_t mipLevel = 0; mipLevel < levelCount; ++mipLevel) {
+                uint32_t index =
+                    GetSubresourceIndex(baseMipLevel + mipLevel, baseArrayLayer + arrayLayer);
 
-        // Update the tracked state.
-        mLastState = newState;
-
-        // Destination states that qualify for an implicit promotion for a non-simultaneous-access
-        // texture: NON_PIXEL_SHADER_RESOURCE, PIXEL_SHADER_RESOURCE, COPY_SRC, COPY_DEST.
-        {
-            static constexpr D3D12_RESOURCE_STATES kD3D12TextureReadOnlyStates =
-                D3D12_RESOURCE_STATE_COPY_SOURCE | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE |
-                D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
-
-            if (lastState == D3D12_RESOURCE_STATE_COMMON) {
-                if (newState == (newState & kD3D12TextureReadOnlyStates)) {
-                    // Implicit texture state decays can only occur when the texture was implicitly
-                    // transitioned to a read-only state. mValidToDecay is needed to differentiate
-                    // between resources that were implictly or explicitly transitioned to a
-                    // read-only state.
-                    mValidToDecay = true;
-                    mLastUsedSerial = pendingCommandSerial;
-                    return false;
-                } else if (newState == D3D12_RESOURCE_STATE_COPY_DEST) {
-                    mValidToDecay = false;
-                    return false;
-                }
+                TransitionSingleSubresource(barriers, newState, index, pendingCommandSerial);
             }
         }
+    }
 
-        barrier->Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
-        barrier->Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
-        barrier->Transition.pResource = GetD3D12Resource();
-        barrier->Transition.StateBefore = lastState;
-        barrier->Transition.StateAfter = newState;
-        barrier->Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
+    void Texture::TrackUsageAndGetResourceBarrierForPass(
+        CommandRecordingContext* commandContext,
+        std::vector<D3D12_RESOURCE_BARRIER>* barriers,
+        const std::vector<wgpu::TextureUsage>& subresourceUsages) {
+        HandleTransitionSpecialCases(commandContext);
 
-        mValidToDecay = false;
+        const Serial pendingCommandSerial = ToBackend(GetDevice())->GetPendingCommandSerial();
+        for (uint32_t arrayLayer = 0; arrayLayer < GetArrayLayers(); ++arrayLayer) {
+            for (uint32_t mipLevel = 0; mipLevel < GetNumMipLevels(); ++mipLevel) {
+                uint32_t index = GetSubresourceIndex(mipLevel, arrayLayer);
 
-        return true;
+                // Skip if this subresource is not used during the current pass
+                if (subresourceUsages[index] == wgpu::TextureUsage::None) {
+                    continue;
+                }
+
+                D3D12_RESOURCE_STATES newState =
+                    D3D12TextureUsage(subresourceUsages[index], GetFormat());
+
+                TransitionSingleSubresource(barriers, newState, index, pendingCommandSerial);
+            }
+        }
     }
 
     D3D12_RENDER_TARGET_VIEW_DESC Texture::GetRTVDescriptor(uint32_t mipLevel,
@@ -741,7 +807,8 @@
 
         if (GetFormat().isRenderable) {
             if (GetFormat().HasDepthOrStencil()) {
-                TrackUsageAndTransitionNow(commandContext, D3D12_RESOURCE_STATE_DEPTH_WRITE);
+                TrackUsageAndTransitionNow(commandContext, D3D12_RESOURCE_STATE_DEPTH_WRITE,
+                                           baseMipLevel, levelCount, baseArrayLayer, layerCount);
 
                 D3D12_CLEAR_FLAGS clearFlags = {};
 
@@ -775,7 +842,8 @@
                     }
                 }
             } else {
-                TrackUsageAndTransitionNow(commandContext, D3D12_RESOURCE_STATE_RENDER_TARGET);
+                TrackUsageAndTransitionNow(commandContext, D3D12_RESOURCE_STATE_RENDER_TARGET,
+                                           baseMipLevel, levelCount, baseArrayLayer, layerCount);
 
                 const float clearColorRGBA[4] = {fClearColor, fClearColor, fClearColor,
                                                  fClearColor};
@@ -818,7 +886,8 @@
                             uploader->Allocate(bufferSize, device->GetPendingCommandSerial()));
             memset(uploadHandle.mappedBuffer, clearColor, bufferSize);
 
-            TrackUsageAndTransitionNow(commandContext, D3D12_RESOURCE_STATE_COPY_DEST);
+            TrackUsageAndTransitionNow(commandContext, D3D12_RESOURCE_STATE_COPY_DEST, baseMipLevel,
+                                       levelCount, baseArrayLayer, layerCount);
 
             for (uint32_t level = baseMipLevel; level < baseMipLevel + levelCount; ++level) {
                 // compute d3d12 texture copy locations for texture and buffer
diff --git a/src/dawn_native/d3d12/TextureD3D12.h b/src/dawn_native/d3d12/TextureD3D12.h
index 45443c1..c8c7f5a 100644
--- a/src/dawn_native/d3d12/TextureD3D12.h
+++ b/src/dawn_native/d3d12/TextureD3D12.h
@@ -60,15 +60,29 @@
                                                  uint32_t baseArrayLayer,
                                                  uint32_t layerCount);
 
-        bool TrackUsageAndGetResourceBarrier(CommandRecordingContext* commandContext,
-                                             D3D12_RESOURCE_BARRIER* barrier,
-                                             wgpu::TextureUsage newUsage);
+        void TrackUsageAndGetResourceBarrierForPass(
+            CommandRecordingContext* commandContext,
+            std::vector<D3D12_RESOURCE_BARRIER>* barrier,
+            const std::vector<wgpu::TextureUsage>& subresourceUsages);
         void TrackUsageAndTransitionNow(CommandRecordingContext* commandContext,
-                                        wgpu::TextureUsage usage);
+                                        wgpu::TextureUsage usage,
+                                        uint32_t baseMipLevel,
+                                        uint32_t levelCount,
+                                        uint32_t baseArrayLayer,
+                                        uint32_t layerCount);
         void TrackUsageAndTransitionNow(CommandRecordingContext* commandContext,
-                                        D3D12_RESOURCE_STATES newState);
+                                        D3D12_RESOURCE_STATES newState,
+                                        uint32_t baseMipLevel,
+                                        uint32_t levelCount,
+                                        uint32_t baseArrayLayer,
+                                        uint32_t layerCount);
+        void TrackAllUsageAndTransitionNow(CommandRecordingContext* commandContext,
+                                           wgpu::TextureUsage usage);
+        void TrackAllUsageAndTransitionNow(CommandRecordingContext* commandContext,
+                                           D3D12_RESOURCE_STATES newState);
 
       private:
+        Texture(Device* device, const TextureDescriptor* descriptor, TextureState state);
         ~Texture() override;
         using TextureBase::TextureBase;
 
@@ -89,18 +103,28 @@
 
         UINT16 GetDepthOrArraySize();
 
-        bool TrackUsageAndGetResourceBarrier(CommandRecordingContext* commandContext,
-                                             D3D12_RESOURCE_BARRIER* barrier,
-                                             D3D12_RESOURCE_STATES newState);
-        bool TransitionUsageAndGetResourceBarrier(CommandRecordingContext* commandContext,
-                                                  D3D12_RESOURCE_BARRIER* barrier,
-                                                  D3D12_RESOURCE_STATES newState);
+        void TransitionUsageAndGetResourceBarrier(CommandRecordingContext* commandContext,
+                                                  std::vector<D3D12_RESOURCE_BARRIER>* barrier,
+                                                  D3D12_RESOURCE_STATES newState,
+                                                  uint32_t baseMipLevel,
+                                                  uint32_t levelCount,
+                                                  uint32_t baseArrayLayer,
+                                                  uint32_t layerCount);
+
+        void TransitionSingleSubresource(std::vector<D3D12_RESOURCE_BARRIER>* barriers,
+                                         D3D12_RESOURCE_STATES subresourceNewState,
+                                         uint32_t index,
+                                         const Serial pendingCommandSerial);
+        void HandleTransitionSpecialCases(CommandRecordingContext* commandContext);
+
+        struct StateAndDecay {
+            D3D12_RESOURCE_STATES lastState;
+            Serial lastDecaySerial;
+            bool isValidToDecay;
+        };
+        std::vector<StateAndDecay> mSubresourceStateAndDecay;
 
         ResourceHeapAllocation mResourceAllocation;
-        D3D12_RESOURCE_STATES mLastState = D3D12_RESOURCE_STATES::D3D12_RESOURCE_STATE_COMMON;
-
-        Serial mLastUsedSerial = UINT64_MAX;
-        bool mValidToDecay = false;
         bool mSwapChainTexture = false;
 
         Serial mAcquireMutexKey = 0;
diff --git a/src/tests/end2end/TextureSubresourceTests.cpp b/src/tests/end2end/TextureSubresourceTests.cpp
index 03dd486..29411de 100644
--- a/src/tests/end2end/TextureSubresourceTests.cpp
+++ b/src/tests/end2end/TextureSubresourceTests.cpp
@@ -196,4 +196,8 @@
 //
 // * add tests for clear operation upon texture subresource if needed
 
-DAWN_INSTANTIATE_TEST(TextureSubresourceTest, MetalBackend(), OpenGLBackend(), VulkanBackend());
+DAWN_INSTANTIATE_TEST(TextureSubresourceTest,
+                      D3D12Backend(),
+                      MetalBackend(),
+                      OpenGLBackend(),
+                      VulkanBackend());