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 {