Implement Implicit State Decays For D3D12

When resources are accessed on a command list, the resource will
implicitly decay in some scenarios. This commit tracks when the decay
occurs to more frequently enable implicit promotion.

Bug: dawn:167
Change-Id: Ide4c06454efe136baee0d39a3437a407a613bcc7
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/8243
Commit-Queue: Brandon Jones <brandon1.jones@intel.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn_native/d3d12/BufferD3D12.cpp b/src/dawn_native/d3d12/BufferD3D12.cpp
index 45c423d..3317984 100644
--- a/src/dawn_native/d3d12/BufferD3D12.cpp
+++ b/src/dawn_native/d3d12/BufferD3D12.cpp
@@ -113,8 +113,20 @@
         DestroyInternal();
     }
 
-    bool Buffer::CreateD3D12ResourceBarrierIfNeeded(D3D12_RESOURCE_BARRIER* barrier,
-                                                    dawn::BufferUsageBit newUsage) const {
+    uint32_t Buffer::GetD3D12Size() const {
+        // TODO(enga@google.com): TODO investigate if this needs to be a constraint at the API level
+        return Align(GetSize(), 256);
+    }
+
+    ComPtr<ID3D12Resource> Buffer::GetD3D12Resource() {
+        return mResource;
+    }
+
+    // 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 Buffer::TransitionUsageAndGetResourceBarrier(D3D12_RESOURCE_BARRIER* barrier,
+                                                      dawn::BufferUsageBit newUsage) {
         // Resources in upload and readback heaps must be kept in the COPY_SOURCE/DEST state
         if (mFixedResourceState) {
             ASSERT(mLastUsage == newUsage);
@@ -129,28 +141,35 @@
 
         D3D12_RESOURCE_STATES lastState = D3D12BufferUsage(mLastUsage);
         D3D12_RESOURCE_STATES newState = D3D12BufferUsage(newUsage);
+        mLastUsage = newUsage;
 
         // The COMMON state represents a state where no write operations can be pending, which makes
-        // it possible to transition to some states without synchronizaton (i.e. without an explicit
-        // ResourceBarrier call). This can be to 1) a single write state, or 2) multiple read
-        // states.
-        //
-        // Destination states used in Dawn that qualify for implicit transition for a buffer:
-        // COPY_SOURCE, VERTEX_AND_CONSTANT_BUFFER, INDEX_BUFFER, COPY_DEST, UNORDERED_ACCESS
+        // it possible to transition to and from some states without synchronizaton (i.e. without an
+        // explicit ResourceBarrier call). A buffer can be implicitly promoted to 1) a single write
+        // state, or 2) multiple read states. A buffer that is accessed within a command list will
+        // always implicitly decay to the COMMON state after the call to ExecuteCommandLists
+        // completes - this is because all buffer writes are guaranteed to be completed before the
+        // next ExecuteCommandLists call executes.
         // https://docs.microsoft.com/en-us/windows/desktop/direct3d12/using-resource-barriers-to-synchronize-resource-states-in-direct3d-12#implicit-state-transitions
-        {
-            static constexpr D3D12_RESOURCE_STATES kD3D12BufferReadOnlyStates =
-                D3D12_RESOURCE_STATE_COPY_SOURCE | D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER |
-                D3D12_RESOURCE_STATE_INDEX_BUFFER;
 
-            if (lastState == D3D12_RESOURCE_STATE_COMMON) {
-                bool singleWriteState = ((newState == D3D12_RESOURCE_STATE_COPY_DEST) ||
-                                         (newState == D3D12_RESOURCE_STATE_UNORDERED_ACCESS));
-                bool readOnlyState = newState == (newState & kD3D12BufferReadOnlyStates);
-                if (singleWriteState ^ readOnlyState) {
-                    return false;
-                }
-            }
+        // To track implicit decays, we must record the pending serial on which a transition will
+        // occur. When that buffer is used again, the previously recorded serial must be compared to
+        // the last completed serial to determine if the buffer has implicity decayed to the common
+        // state.
+        const Serial pendingCommandSerial = ToBackend(GetDevice())->GetPendingCommandSerial();
+        if (pendingCommandSerial > mLastUsedSerial) {
+            lastState = D3D12_RESOURCE_STATE_COMMON;
+            mLastUsedSerial = pendingCommandSerial;
+        }
+
+        // All possible buffer states used by Dawn are eligible for implicit promotion from COMMON.
+        // These are: COPY_SOURCE, VERTEX_AND_COPY_BUFFER, INDEX_BUFFER, COPY_DEST,
+        // UNORDERED_ACCESS, and INDIRECT_ARGUMENT. Note that for implicit promotion, the
+        // destination state cannot be 1) more than one write state, or 2) both a read and write
+        // state. This goes unchecked here because it should not be allowed through render/compute
+        // pass validation.
+        if (lastState == D3D12_RESOURCE_STATE_COMMON) {
+            return false;
         }
 
         barrier->Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
@@ -163,28 +182,13 @@
         return true;
     }
 
-    uint32_t Buffer::GetD3D12Size() const {
-        // TODO(enga@google.com): TODO investigate if this needs to be a constraint at the API level
-        return Align(GetSize(), 256);
-    }
-
-    ComPtr<ID3D12Resource> Buffer::GetD3D12Resource() {
-        return mResource;
-    }
-
-    void Buffer::SetUsage(dawn::BufferUsageBit newUsage) {
-        mLastUsage = newUsage;
-    }
-
     void Buffer::TransitionUsageNow(ComPtr<ID3D12GraphicsCommandList> commandList,
                                     dawn::BufferUsageBit usage) {
         D3D12_RESOURCE_BARRIER barrier;
 
-        if (CreateD3D12ResourceBarrierIfNeeded(&barrier, usage)) {
+        if (TransitionUsageAndGetResourceBarrier(&barrier, usage)) {
             commandList->ResourceBarrier(1, &barrier);
         }
-
-        mLastUsage = usage;
     }
 
     D3D12_GPU_VIRTUAL_ADDRESS Buffer::GetVA() const {
diff --git a/src/dawn_native/d3d12/BufferD3D12.h b/src/dawn_native/d3d12/BufferD3D12.h
index 86954a7..c4b0fcf 100644
--- a/src/dawn_native/d3d12/BufferD3D12.h
+++ b/src/dawn_native/d3d12/BufferD3D12.h
@@ -29,13 +29,12 @@
         Buffer(Device* device, const BufferDescriptor* descriptor);
         ~Buffer();
 
-        bool CreateD3D12ResourceBarrierIfNeeded(D3D12_RESOURCE_BARRIER* barrier,
-                                                dawn::BufferUsageBit newUsage) const;
         uint32_t GetD3D12Size() const;
         ComPtr<ID3D12Resource> GetD3D12Resource();
         D3D12_GPU_VIRTUAL_ADDRESS GetVA() const;
         void OnMapCommandSerialFinished(uint32_t mapSerial, void* data, bool isWrite);
-        void SetUsage(dawn::BufferUsageBit newUsage);
+        bool TransitionUsageAndGetResourceBarrier(D3D12_RESOURCE_BARRIER* barrier,
+                                                  dawn::BufferUsageBit newUsage);
         void TransitionUsageNow(ComPtr<ID3D12GraphicsCommandList> commandList,
                                 dawn::BufferUsageBit usage);
 
@@ -52,6 +51,7 @@
         ComPtr<ID3D12Resource> mResource;
         bool mFixedResourceState = false;
         dawn::BufferUsageBit mLastUsage = dawn::BufferUsageBit::None;
+        Serial mLastUsedSerial = UINT64_MAX;
         D3D12_RANGE mWrittenMappedRange;
     };
 
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
index c2c3a71..035980d 100644
--- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
@@ -435,10 +435,9 @@
             for (size_t i = 0; i < usages.buffers.size(); ++i) {
                 D3D12_RESOURCE_BARRIER barrier;
                 if (ToBackend(usages.buffers[i])
-                        ->CreateD3D12ResourceBarrierIfNeeded(&barrier, usages.bufferUsages[i])) {
+                        ->TransitionUsageAndGetResourceBarrier(&barrier, usages.bufferUsages[i])) {
                     barriers.push_back(barrier);
                 }
-                ToBackend(usages.buffers[i])->SetUsage(usages.bufferUsages[i]);
             }
 
             for (size_t i = 0; i < usages.textures.size(); ++i) {
@@ -452,10 +451,9 @@
             for (size_t i = 0; i < usages.textures.size(); ++i) {
                 D3D12_RESOURCE_BARRIER barrier;
                 if (ToBackend(usages.textures[i])
-                        ->CreateD3D12ResourceBarrierIfNeeded(&barrier, usages.textureUsages[i])) {
+                        ->TransitionUsageAndGetResourceBarrier(&barrier, usages.textureUsages[i])) {
                     barriers.push_back(barrier);
                 }
-                ToBackend(usages.textures[i])->SetUsage(usages.textureUsages[i]);
             }
 
             if (barriers.size()) {
diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp
index 7a6b1da..a29983f 100644
--- a/src/dawn_native/d3d12/TextureD3D12.cpp
+++ b/src/dawn_native/d3d12/TextureD3D12.cpp
@@ -281,52 +281,6 @@
         DestroyInternal();
     }
 
-    bool Texture::CreateD3D12ResourceBarrierIfNeeded(D3D12_RESOURCE_BARRIER* barrier,
-                                                     dawn::TextureUsageBit newUsage) const {
-        return CreateD3D12ResourceBarrierIfNeeded(barrier,
-                                                  D3D12TextureUsage(newUsage, GetFormat()));
-    }
-
-    bool Texture::CreateD3D12ResourceBarrierIfNeeded(D3D12_RESOURCE_BARRIER* barrier,
-                                                     D3D12_RESOURCE_STATES newState) const {
-        // 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;
-        }
-
-        // 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 some states without
-        // synchronization (i.e. without an explicit ResourceBarrier call). This can be to 1) a
-        // single write state, or 2) multiple read states.
-        //
-        // Destination states that qualify for an implicit transition for a non-simulataneous-access
-        // texture: NON_PIXEL_SHADER_RESOURCE, PIXEL_SHADER_RESOURCE, COPY_SRC, COPY_DEST
-        // https://docs.microsoft.com/en-us/windows/desktop/direct3d12/using-resource-barriers-to-synchronize-resource-states-in-direct3d-12#implicit-state-transitions
-        {
-            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 (mLastState == D3D12_RESOURCE_STATE_COMMON) {
-                bool singleWriteState = (newState == D3D12_RESOURCE_STATE_COPY_DEST);
-                bool readOnlyState = newState == (newState & kD3D12TextureReadOnlyStates);
-                if (singleWriteState ^ readOnlyState) {
-                    return false;
-                }
-            }
-        }
-
-        barrier->Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
-        barrier->Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
-        barrier->Transition.pResource = mResource.Get();
-        barrier->Transition.StateBefore = mLastState;
-        barrier->Transition.StateAfter = newState;
-        barrier->Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
-
-        return true;
-    }
-
     void Texture::DestroyImpl() {
         // If we own the resource, release it.
         ToBackend(GetDevice())->GetResourceAllocator()->Release(mResource);
@@ -350,8 +304,83 @@
         }
     }
 
-    void Texture::SetUsage(dawn::TextureUsageBit newUsage) {
-        mLastState = D3D12TextureUsage(newUsage, GetFormat());
+    // 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(D3D12_RESOURCE_BARRIER* barrier,
+                                                       dawn::TextureUsageBit newUsage) {
+        return TransitionUsageAndGetResourceBarrier(barrier,
+                                                    D3D12TextureUsage(newUsage, GetFormat()));
+    }
+
+    // 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(D3D12_RESOURCE_BARRIER* barrier,
+                                                       D3D12_RESOURCE_STATES newState) {
+        // 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;
+        }
+
+        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;
+        }
+
+        // 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;
+                }
+            }
+        }
+
+        barrier->Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
+        barrier->Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
+        barrier->Transition.pResource = mResource.Get();
+        barrier->Transition.StateBefore = lastState;
+        barrier->Transition.StateAfter = newState;
+        barrier->Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
+
+        mValidToDecay = false;
+
+        return true;
     }
 
     void Texture::TransitionUsageNow(ComPtr<ID3D12GraphicsCommandList> commandList,
@@ -362,11 +391,10 @@
     void Texture::TransitionUsageNow(ComPtr<ID3D12GraphicsCommandList> commandList,
                                      D3D12_RESOURCE_STATES newState) {
         D3D12_RESOURCE_BARRIER barrier;
-        if (CreateD3D12ResourceBarrierIfNeeded(&barrier, newState)) {
+
+        if (TransitionUsageAndGetResourceBarrier(&barrier, newState)) {
             commandList->ResourceBarrier(1, &barrier);
         }
-
-        mLastState = newState;
     }
 
     D3D12_RENDER_TARGET_VIEW_DESC Texture::GetRTVDescriptor(uint32_t baseMipLevel,
diff --git a/src/dawn_native/d3d12/TextureD3D12.h b/src/dawn_native/d3d12/TextureD3D12.h
index c74a746..787c5b1 100644
--- a/src/dawn_native/d3d12/TextureD3D12.h
+++ b/src/dawn_native/d3d12/TextureD3D12.h
@@ -15,6 +15,7 @@
 #ifndef DAWNNATIVE_D3D12_TEXTURED3D12_H_
 #define DAWNNATIVE_D3D12_TEXTURED3D12_H_
 
+#include "common/Serial.h"
 #include "dawn_native/Texture.h"
 
 #include "dawn_native/d3d12/d3d12_platform.h"
@@ -31,13 +32,10 @@
         Texture(Device* device, const TextureDescriptor* descriptor, ID3D12Resource* nativeTexture);
         ~Texture();
 
-        bool CreateD3D12ResourceBarrierIfNeeded(D3D12_RESOURCE_BARRIER* barrier,
-                                                dawn::TextureUsageBit newUsage) const;
-        bool CreateD3D12ResourceBarrierIfNeeded(D3D12_RESOURCE_BARRIER* barrier,
-                                                D3D12_RESOURCE_STATES newState) const;
         DXGI_FORMAT GetD3D12Format() const;
         ID3D12Resource* GetD3D12Resource() const;
-        void SetUsage(dawn::TextureUsageBit newUsage);
+        bool TransitionUsageAndGetResourceBarrier(D3D12_RESOURCE_BARRIER* barrier,
+                                                  dawn::TextureUsageBit newUsage);
         void TransitionUsageNow(ComPtr<ID3D12GraphicsCommandList> commandList,
                                 dawn::TextureUsageBit usage);
         void TransitionUsageNow(ComPtr<ID3D12GraphicsCommandList> commandList,
@@ -64,8 +62,14 @@
 
         UINT16 GetDepthOrArraySize();
 
+        bool TransitionUsageAndGetResourceBarrier(D3D12_RESOURCE_BARRIER* barrier,
+                                                  D3D12_RESOURCE_STATES newState);
+
         ComPtr<ID3D12Resource> mResource;
         D3D12_RESOURCE_STATES mLastState = D3D12_RESOURCE_STATES::D3D12_RESOURCE_STATE_COMMON;
+
+        Serial mLastUsedSerial = UINT64_MAX;
+        bool mValidToDecay = false;
     };
 
     class TextureView : public TextureViewBase {