d3d12: Signal fence after PIX Present

Calling ID3D12SharingContract::Present issues GPU work on the command
queue which needs to be synchronized with resource deallocation. This
seems to work now perhaps due to the keyed mutex semantics keeping the
D3D11 texture alive for longer than necessary. With fences, this missing
synchronization causes the validation layers to complain about early
deallocation of the ID3D12Resource.

Moving the Present to SynchronizeImportTextureBeforeUse ensures that it
happens before NextSerial and hence the signal fence that's recorded
will include any GPU work issued by Present. Also, resource deallocation
will happen after this work. However, this has the side-effect of PIX
seeing more frames, once per ExecuteCommandLists, but it could be argued
that's more accurate and useful.

Bug: dawn:1544
Change-Id: I1b417049045a812837f67072d7f09ac47bc18125
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/103841
Reviewed-by: Austin Eng <enga@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Sunny Sachanandani <sunnyps@chromium.org>
This commit is contained in:
Sunny Sachanandani 2022-09-28 00:58:46 +00:00 committed by Dawn LUCI CQ
parent a33bc2c6f2
commit 3194b4a31f
1 changed files with 22 additions and 25 deletions

View File

@ -656,53 +656,37 @@ Texture::~Texture() {}
void Texture::DestroyImpl() { void Texture::DestroyImpl() {
TextureBase::DestroyImpl(); TextureBase::DestroyImpl();
Device* device = ToBackend(GetDevice()); ToBackend(GetDevice())->DeallocateMemory(mResourceAllocation);
// In PIX's D3D12-only mode, there is no way to determine frame boundaries // Set mSwapChainTexture to false to prevent ever calling ID3D12SharingContract::Present again.
// for WebGPU since Dawn does not manage DXGI swap chains. Without assistance,
// PIX will wait forever for a present that never happens.
// If we know we're dealing with a swapbuffer texture, inform PIX we've
// "presented" the texture so it can determine frame boundaries and use its
// contents for the UI.
if (mSwapChainTexture) {
ID3D12SharingContract* d3dSharingContract = device->GetSharingContract();
if (d3dSharingContract != nullptr) {
d3dSharingContract->Present(mResourceAllocation.GetD3D12Resource(), 0, 0);
}
}
device->DeallocateMemory(mResourceAllocation);
// Now that we've deallocated the memory, the texture is no longer a swap chain texture.
// We can set mSwapChainTexture to false to avoid passing a nullptr to
// ID3D12SharingContract::Present.
mSwapChainTexture = false; mSwapChainTexture = false;
// Now that the texture has been destroyed. It should release the refptr of the d3d11on12 // Now that the texture has been destroyed, it should release the d3d11on12 resource refptr.
// resource and the fence.
mD3D11on12Resource = nullptr; mD3D11on12Resource = nullptr;
} }
ResultOrError<ExecutionSerial> Texture::EndAccess() { ResultOrError<ExecutionSerial> Texture::EndAccess() {
ASSERT(mD3D11on12Resource == nullptr); ASSERT(mD3D11on12Resource == nullptr);
Device* device = ToBackend(GetDevice());
// Synchronize if texture access wasn't synchronized already due to ExecuteCommandLists. // Synchronize if texture access wasn't synchronized already due to ExecuteCommandLists.
if (!mSignalFenceValue.has_value()) { if (!mSignalFenceValue.has_value()) {
// Needed to ensure that command allocator doesn't get destroyed before pending commands // Needed to ensure that command allocator doesn't get destroyed before pending commands
// are submitted due to calling NextSerial(). No-op if there are no pending commands. // are submitted due to calling NextSerial(). No-op if there are no pending commands.
DAWN_TRY(ToBackend(GetDevice())->ExecutePendingCommandContext()); DAWN_TRY(device->ExecutePendingCommandContext());
// If there were pending commands that used this texture mSignalFenceValue will be set, // If there were pending commands that used this texture mSignalFenceValue will be set,
// but if it's still not set, generate a signal fence after waiting on wait fences. // but if it's still not set, generate a signal fence after waiting on wait fences.
if (!mSignalFenceValue.has_value()) { if (!mSignalFenceValue.has_value()) {
DAWN_TRY(SynchronizeImportedTextureBeforeUse()); DAWN_TRY(SynchronizeImportedTextureBeforeUse());
DAWN_TRY(SynchronizeImportedTextureAfterUse()); DAWN_TRY(SynchronizeImportedTextureAfterUse());
} }
DAWN_TRY(ToBackend(GetDevice())->NextSerial()); DAWN_TRY(device->NextSerial());
ASSERT(mSignalFenceValue.has_value()); ASSERT(mSignalFenceValue.has_value());
} }
ExecutionSerial ret = mSignalFenceValue.value(); ExecutionSerial ret = mSignalFenceValue.value();
ASSERT(ret <= GetDevice()->GetLastSubmittedCommandSerial()); ASSERT(ret <= device->GetLastSubmittedCommandSerial());
// Explicitly call reset() since std::move() on optional doesn't make it std::nullopt. // Explicitly call reset() since std::move() on optional doesn't make it std::nullopt.
mSignalFenceValue.reset(); mSignalFenceValue.reset();
return ret; return ret;
@ -757,11 +741,24 @@ MaybeError Texture::SynchronizeImportedTextureBeforeUse() {
} }
MaybeError Texture::SynchronizeImportedTextureAfterUse() { MaybeError Texture::SynchronizeImportedTextureAfterUse() {
// In PIX's D3D12-only mode, there is no way to determine frame boundaries
// for WebGPU since Dawn does not manage DXGI swap chains. Without assistance,
// PIX will wait forever for a present that never happens.
// If we know we're dealing with a swapbuffer texture, inform PIX we've
// "presented" the texture so it can determine frame boundaries and use its
// contents for the UI.
Device* device = ToBackend(GetDevice());
if (mSwapChainTexture) {
ID3D12SharingContract* d3dSharingContract = device->GetSharingContract();
if (d3dSharingContract != nullptr) {
d3dSharingContract->Present(mResourceAllocation.GetD3D12Resource(), 0, 0);
}
}
if (mD3D11on12Resource != nullptr) { if (mD3D11on12Resource != nullptr) {
DAWN_TRY(mD3D11on12Resource->ReleaseKeyedMutex()); DAWN_TRY(mD3D11on12Resource->ReleaseKeyedMutex());
} else { } else {
// NextSerial() will be called after this - this is also checked in EndAccess(). // NextSerial() will be called after this - this is also checked in EndAccess().
mSignalFenceValue = ToBackend(GetDevice())->GetPendingCommandSerial(); mSignalFenceValue = device->GetPendingCommandSerial();
} }
return {}; return {};
} }