diff --git a/src/dawn_native/d3d12/BufferD3D12.cpp b/src/dawn_native/d3d12/BufferD3D12.cpp index 45c423d133..331798450d 100644 --- a/src/dawn_native/d3d12/BufferD3D12.cpp +++ b/src/dawn_native/d3d12/BufferD3D12.cpp @@ -113,8 +113,20 @@ namespace dawn_native { namespace d3d12 { 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 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 @@ namespace dawn_native { namespace d3d12 { 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 @@ namespace dawn_native { namespace d3d12 { 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 Buffer::GetD3D12Resource() { - return mResource; - } - - void Buffer::SetUsage(dawn::BufferUsageBit newUsage) { - mLastUsage = newUsage; - } - void Buffer::TransitionUsageNow(ComPtr 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 86954a748a..c4b0fcf261 100644 --- a/src/dawn_native/d3d12/BufferD3D12.h +++ b/src/dawn_native/d3d12/BufferD3D12.h @@ -29,13 +29,12 @@ namespace dawn_native { namespace d3d12 { Buffer(Device* device, const BufferDescriptor* descriptor); ~Buffer(); - bool CreateD3D12ResourceBarrierIfNeeded(D3D12_RESOURCE_BARRIER* barrier, - dawn::BufferUsageBit newUsage) const; uint32_t GetD3D12Size() const; ComPtr 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 commandList, dawn::BufferUsageBit usage); @@ -52,6 +51,7 @@ namespace dawn_native { namespace d3d12 { ComPtr 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 c2c3a71af6..035980d16b 100644 --- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp +++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp @@ -435,10 +435,9 @@ namespace dawn_native { namespace d3d12 { 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 @@ namespace dawn_native { namespace d3d12 { 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 7a6b1da7bd..a29983f0f6 100644 --- a/src/dawn_native/d3d12/TextureD3D12.cpp +++ b/src/dawn_native/d3d12/TextureD3D12.cpp @@ -281,52 +281,6 @@ namespace dawn_native { namespace d3d12 { 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 @@ namespace dawn_native { namespace d3d12 { } } - 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 commandList, @@ -362,11 +391,10 @@ namespace dawn_native { namespace d3d12 { void Texture::TransitionUsageNow(ComPtr 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 c74a746933..787c5b19c6 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 @@ namespace dawn_native { namespace d3d12 { 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 commandList, dawn::TextureUsageBit usage); void TransitionUsageNow(ComPtr commandList, @@ -64,8 +62,14 @@ namespace dawn_native { namespace d3d12 { UINT16 GetDepthOrArraySize(); + bool TransitionUsageAndGetResourceBarrier(D3D12_RESOURCE_BARRIER* barrier, + D3D12_RESOURCE_STATES newState); + ComPtr mResource; D3D12_RESOURCE_STATES mLastState = D3D12_RESOURCE_STATES::D3D12_RESOURCE_STATE_COMMON; + + Serial mLastUsedSerial = UINT64_MAX; + bool mValidToDecay = false; }; class TextureView : public TextureViewBase {