Avoid processing already processed tick

To avoid overly ticking, we only want to tick when:
1. the last submitted serial has moved beyond the completed serial
2. or the completed serial has not reached the future command serial added
by the trackers (MapRequestTracker, FenceSignalTracker, ErrorScopeTracker).

Bug: dawn:400
Change-Id: Ie7c65acc332846ac1a27f9a18f230149d96d2189
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/19062
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Natasha Lee <natlee@microsoft.com>
This commit is contained in:
Natasha Lee 2020-06-04 02:26:46 +00:00 committed by Commit Bot service account
parent 62b08f845f
commit 783cd5a79c
11 changed files with 97 additions and 79 deletions

View File

@ -126,7 +126,7 @@ namespace dawn_native {
// complete before proceeding with destruction. // complete before proceeding with destruction.
// Assert that errors are device loss so that we can continue with destruction // Assert that errors are device loss so that we can continue with destruction
AssertAndIgnoreDeviceLossError(WaitForIdleForDestruction()); AssertAndIgnoreDeviceLossError(WaitForIdleForDestruction());
ASSERT(mCompletedSerial == mLastSubmittedSerial); AssumeCommandsComplete();
break; break;
case State::BeingDisconnected: case State::BeingDisconnected:
@ -139,6 +139,8 @@ namespace dawn_native {
case State::Disconnected: case State::Disconnected:
break; break;
} }
ASSERT(mCompletedSerial == mLastSubmittedSerial);
ASSERT(mFutureCallbackSerial <= mCompletedSerial);
// Skip handling device facilities if they haven't even been created (or failed doing so) // Skip handling device facilities if they haven't even been created (or failed doing so)
if (mState != State::BeingCreated) { if (mState != State::BeingCreated) {
@ -163,6 +165,7 @@ namespace dawn_native {
mDynamicUploader = nullptr; mDynamicUploader = nullptr;
mMapRequestTracker = nullptr; mMapRequestTracker = nullptr;
AssumeCommandsComplete();
// Tell the backend that it can free all the objects now that the GPU timeline is empty. // Tell the backend that it can free all the objects now that the GPU timeline is empty.
ShutDownImpl(); ShutDownImpl();
@ -181,8 +184,10 @@ namespace dawn_native {
mState = State::BeingDisconnected; mState = State::BeingDisconnected;
// Assert that errors are device losses so that we can continue with destruction. // Assert that errors are device losses so that we can continue with destruction.
// Assume all commands are complete after WaitForIdleForDestruction (because they were)
AssertAndIgnoreDeviceLossError(WaitForIdleForDestruction()); AssertAndIgnoreDeviceLossError(WaitForIdleForDestruction());
ASSERT(mCompletedSerial == mLastSubmittedSerial); AssumeCommandsComplete();
ASSERT(mFutureCallbackSerial <= mCompletedSerial);
mState = State::Disconnected; mState = State::Disconnected;
// Now everything is as if the device was lost. // Now everything is as if the device was lost.
@ -320,24 +325,30 @@ namespace dawn_native {
return mLastSubmittedSerial; return mLastSubmittedSerial;
} }
Serial DeviceBase::GetFutureCallbackSerial() const {
return mFutureCallbackSerial;
}
void DeviceBase::IncrementLastSubmittedCommandSerial() { void DeviceBase::IncrementLastSubmittedCommandSerial() {
mLastSubmittedSerial++; mLastSubmittedSerial++;
} }
void DeviceBase::ArtificiallyIncrementSerials() {
mCompletedSerial++;
mLastSubmittedSerial++;
}
void DeviceBase::AssumeCommandsComplete() { void DeviceBase::AssumeCommandsComplete() {
mLastSubmittedSerial++; Serial maxSerial = std::max(mLastSubmittedSerial + 1, mFutureCallbackSerial);
mCompletedSerial = mLastSubmittedSerial; mLastSubmittedSerial = maxSerial;
mCompletedSerial = maxSerial;
} }
Serial DeviceBase::GetPendingCommandSerial() const { Serial DeviceBase::GetPendingCommandSerial() const {
return mLastSubmittedSerial + 1; return mLastSubmittedSerial + 1;
} }
void DeviceBase::AddFutureCallbackSerial(Serial serial) {
if (serial > mFutureCallbackSerial) {
mFutureCallbackSerial = serial;
}
}
void DeviceBase::CheckPassedSerials() { void DeviceBase::CheckPassedSerials() {
Serial completedSerial = CheckAndUpdateCompletedSerials(); Serial completedSerial = CheckAndUpdateCompletedSerials();
@ -712,17 +723,32 @@ namespace dawn_native {
if (ConsumedError(ValidateIsAlive())) { if (ConsumedError(ValidateIsAlive())) {
return; return;
} }
if (ConsumedError(TickImpl())) { // to avoid overly ticking, we only want to tick when:
return; // 1. the last submitted serial has moved beyond the completed serial
} // 2. or the completed serial has not reached the future serial set by the trackers
if (mLastSubmittedSerial > mCompletedSerial || mCompletedSerial < mFutureCallbackSerial) {
CheckPassedSerials();
// TODO(cwallez@chromium.org): decouple TickImpl from updating the serial so that we can if (ConsumedError(TickImpl())) {
// tick the dynamic uploader before the backend resource allocators. This would allow return;
// reclaiming resources one tick earlier. }
mDynamicUploader->Deallocate(GetCompletedCommandSerial());
mErrorScopeTracker->Tick(GetCompletedCommandSerial()); // There is no GPU work in flight, we need to move the serials forward so that
mFenceSignalTracker->Tick(GetCompletedCommandSerial()); // so that CPU operations waiting on GPU completion can know they don't have to wait.
mMapRequestTracker->Tick(GetCompletedCommandSerial()); // AssumeCommandsComplete will assign the max serial we must tick to in order to
// fire the awaiting callbacks.
if (mCompletedSerial == mLastSubmittedSerial) {
AssumeCommandsComplete();
}
// TODO(cwallez@chromium.org): decouple TickImpl from updating the serial so that we can
// tick the dynamic uploader before the backend resource allocators. This would allow
// reclaiming resources one tick earlier.
mDynamicUploader->Deallocate(mCompletedSerial);
mErrorScopeTracker->Tick(mCompletedSerial);
mFenceSignalTracker->Tick(mCompletedSerial);
mMapRequestTracker->Tick(mCompletedSerial);
}
} }
void DeviceBase::Reference() { void DeviceBase::Reference() {

View File

@ -90,6 +90,7 @@ namespace dawn_native {
Serial GetCompletedCommandSerial() const; Serial GetCompletedCommandSerial() const;
Serial GetLastSubmittedCommandSerial() const; Serial GetLastSubmittedCommandSerial() const;
Serial GetFutureCallbackSerial() const;
Serial GetPendingCommandSerial() const; Serial GetPendingCommandSerial() const;
virtual MaybeError TickImpl() = 0; virtual MaybeError TickImpl() = 0;
@ -214,6 +215,7 @@ namespace dawn_native {
size_t GetDeprecationWarningCountForTesting(); size_t GetDeprecationWarningCountForTesting();
void EmitDeprecationWarning(const char* warning); void EmitDeprecationWarning(const char* warning);
void LoseForTesting(); void LoseForTesting();
void AddFutureCallbackSerial(Serial serial);
protected: protected:
void SetToggle(Toggle toggle, bool isEnabled); void SetToggle(Toggle toggle, bool isEnabled);
@ -224,13 +226,6 @@ namespace dawn_native {
// Incrememt mLastSubmittedSerial when we submit the next serial // Incrememt mLastSubmittedSerial when we submit the next serial
void IncrementLastSubmittedCommandSerial(); void IncrementLastSubmittedCommandSerial();
// If there's no GPU work in flight we still need to artificially increment the serial
// so that CPU operations waiting on GPU completion can know they don't have to wait.
void ArtificiallyIncrementSerials();
// During shut down of device, some operations might have been started since the last submit
// and waiting on a serial that doesn't have a corresponding fence enqueued. Fake serials to
// make all commands look completed.
void AssumeCommandsComplete();
// Check for passed fences and set the new completed serial // Check for passed fences and set the new completed serial
void CheckPassedSerials(); void CheckPassedSerials();
@ -298,14 +293,21 @@ namespace dawn_native {
// Each backend should implement to check their passed fences if there are any and return a // Each backend should implement to check their passed fences if there are any and return a
// completed serial. Return 0 should indicate no fences to check. // completed serial. Return 0 should indicate no fences to check.
virtual Serial CheckAndUpdateCompletedSerials() = 0; virtual Serial CheckAndUpdateCompletedSerials() = 0;
// During shut down of device, some operations might have been started since the last submit
// and waiting on a serial that doesn't have a corresponding fence enqueued. Fake serials to
// make all commands look completed.
void AssumeCommandsComplete();
// mCompletedSerial tracks the last completed command serial that the fence has returned. // mCompletedSerial tracks the last completed command serial that the fence has returned.
// mLastSubmittedSerial tracks the last submitted command serial. // mLastSubmittedSerial tracks the last submitted command serial.
// During device removal, the serials could be artificially incremented // During device removal, the serials could be artificially incremented
// to make it appear as if commands have been compeleted. They can also be artificially // to make it appear as if commands have been compeleted. They can also be artificially
// incremented when no work is being done in the GPU so CPU operations don't have to wait on // incremented when no work is being done in the GPU so CPU operations don't have to wait on
// stale serials. // stale serials.
// mFutureCallbackSerial tracks the largest serial we need to tick to for the callbacks to
// fire
Serial mCompletedSerial = 0; Serial mCompletedSerial = 0;
Serial mLastSubmittedSerial = 0; Serial mLastSubmittedSerial = 0;
Serial mFutureCallbackSerial = 0;
// ShutDownImpl is used to clean up and release resources used by device, does not wait for // ShutDownImpl is used to clean up and release resources used by device, does not wait for
// GPU or check errors. // GPU or check errors.

View File

@ -37,6 +37,7 @@ namespace dawn_native {
void ErrorScopeTracker::TrackUntilLastSubmitComplete(ErrorScope* scope) { void ErrorScopeTracker::TrackUntilLastSubmitComplete(ErrorScope* scope) {
mScopesInFlight.Enqueue(scope, mDevice->GetLastSubmittedCommandSerial()); mScopesInFlight.Enqueue(scope, mDevice->GetLastSubmittedCommandSerial());
mDevice->AddFutureCallbackSerial(mDevice->GetPendingCommandSerial());
} }
void ErrorScopeTracker::Tick(Serial completedSerial) { void ErrorScopeTracker::Tick(Serial completedSerial) {

View File

@ -31,6 +31,7 @@ namespace dawn_native {
// the fence completed value once the last submitted serial has passed. // the fence completed value once the last submitted serial has passed.
mFencesInFlight.Enqueue(FenceInFlight{fence, value}, mFencesInFlight.Enqueue(FenceInFlight{fence, value},
mDevice->GetLastSubmittedCommandSerial()); mDevice->GetLastSubmittedCommandSerial());
mDevice->AddFutureCallbackSerial(mDevice->GetPendingCommandSerial());
} }
void FenceSignalTracker::Tick(Serial finishedSerial) { void FenceSignalTracker::Tick(Serial finishedSerial) {

View File

@ -34,6 +34,7 @@ namespace dawn_native {
request.isWrite = isWrite; request.isWrite = isWrite;
mInflightRequests.Enqueue(std::move(request), mDevice->GetPendingCommandSerial()); mInflightRequests.Enqueue(std::move(request), mDevice->GetPendingCommandSerial());
mDevice->AddFutureCallbackSerial(mDevice->GetPendingCommandSerial());
} }
void MapRequestTracker::Tick(Serial finishedSerial) { void MapRequestTracker::Tick(Serial finishedSerial) {

View File

@ -212,8 +212,6 @@ namespace dawn_native { namespace d3d12 {
} }
MaybeError Device::TickImpl() { MaybeError Device::TickImpl() {
CheckPassedSerials();
// Perform cleanup operations to free unused objects // Perform cleanup operations to free unused objects
Serial completedSerial = GetCompletedCommandSerial(); Serial completedSerial = GetCompletedCommandSerial();
@ -245,6 +243,7 @@ namespace dawn_native { namespace d3d12 {
DAWN_TRY(CheckHRESULT(mFence->SetEventOnCompletion(serial, mFenceEvent), DAWN_TRY(CheckHRESULT(mFence->SetEventOnCompletion(serial, mFenceEvent),
"D3D12 set event on completion")); "D3D12 set event on completion"));
WaitForSingleObject(mFenceEvent, INFINITE); WaitForSingleObject(mFenceEvent, INFINITE);
CheckPassedSerials();
} }
return {}; return {};
} }
@ -462,9 +461,6 @@ namespace dawn_native { namespace d3d12 {
// Call tick one last time so resources are cleaned up. // Call tick one last time so resources are cleaned up.
DAWN_TRY(TickImpl()); DAWN_TRY(TickImpl());
// Force all operations to look as if they were completed
AssumeCommandsComplete();
return {}; return {};
} }
@ -519,11 +515,6 @@ namespace dawn_native { namespace d3d12 {
// Immediately forget about all pending commands // Immediately forget about all pending commands
mPendingCommands.Release(); mPendingCommands.Release();
// Some operations might have been started since the last submit and waiting
// on a serial that doesn't have a corresponding fence enqueued. Force all
// operations to look as if they were completed (because they were).
AssumeCommandsComplete();
if (mFenceEvent != nullptr) { if (mFenceEvent != nullptr) {
::CloseHandle(mFenceEvent); ::CloseHandle(mFenceEvent);
} }

View File

@ -155,9 +155,9 @@ namespace dawn_native { namespace metal {
Serial Device::CheckAndUpdateCompletedSerials() { Serial Device::CheckAndUpdateCompletedSerials() {
if (GetCompletedCommandSerial() > mCompletedSerial) { if (GetCompletedCommandSerial() > mCompletedSerial) {
// sometimes we artificially increase the serials, in which case the completed serial in // sometimes we increase the serials, in which case the completed serial in
// the device base will surpass the completed serial we have in the metal backend, so we // the device base will surpass the completed serial we have in the metal backend, so we
// must update ours when we see that the completed serial from the frontend has // must update ours when we see that the completed serial from device base has
// increased. // increased.
mCompletedSerial = GetCompletedCommandSerial(); mCompletedSerial = GetCompletedCommandSerial();
} }
@ -166,15 +166,8 @@ namespace dawn_native { namespace metal {
} }
MaybeError Device::TickImpl() { MaybeError Device::TickImpl() {
CheckPassedSerials();
Serial completedSerial = GetCompletedCommandSerial();
if (mCommandContext.GetCommands() != nil) { if (mCommandContext.GetCommands() != nil) {
SubmitPendingCommandBuffer(); SubmitPendingCommandBuffer();
} else if (completedSerial == GetLastSubmittedCommandSerial()) {
// If there's no GPU work in flight we still need to artificially increment the serial
// so that CPU operations waiting on GPU completion can know they don't have to wait.
ArtificiallyIncrementSerials();
} }
return {}; return {};
@ -300,14 +293,8 @@ namespace dawn_native { namespace metal {
CheckPassedSerials(); CheckPassedSerials();
} }
// Artificially increase the serials so work that was pending knows it can complete.
ArtificiallyIncrementSerials();
DAWN_TRY(TickImpl()); DAWN_TRY(TickImpl());
// Force all operations to look as if they were completed
AssumeCommandsComplete();
return {}; return {};
} }

View File

@ -185,8 +185,6 @@ namespace dawn_native { namespace null {
} }
MaybeError Device::WaitForIdleForDestruction() { MaybeError Device::WaitForIdleForDestruction() {
// Fake all commands being completed
AssumeCommandsComplete();
return {}; return {};
} }

View File

@ -153,12 +153,6 @@ namespace dawn_native { namespace opengl {
} }
MaybeError Device::TickImpl() { MaybeError Device::TickImpl() {
CheckPassedSerials();
if (GetCompletedCommandSerial() == GetLastSubmittedCommandSerial()) {
// If there's no GPU work in flight we still need to artificially increment the serial
// so that CPU operations waiting on GPU completion can know they don't have to wait.
ArtificiallyIncrementSerials();
}
return {}; return {};
} }
@ -200,11 +194,6 @@ namespace dawn_native { namespace opengl {
void Device::ShutDownImpl() { void Device::ShutDownImpl() {
ASSERT(GetState() == State::Disconnected); ASSERT(GetState() == State::Disconnected);
// Some operations might have been started since the last submit and waiting
// on a serial that doesn't have a corresponding fence enqueued. Force all
// operations to look as if they were completed (because they were).
AssumeCommandsComplete();
} }
MaybeError Device::WaitForIdleForDestruction() { MaybeError Device::WaitForIdleForDestruction() {
@ -213,8 +202,6 @@ namespace dawn_native { namespace opengl {
ASSERT(mFencesInFlight.empty()); ASSERT(mFencesInFlight.empty());
Tick(); Tick();
// Force all operations to look as if they were completed
AssumeCommandsComplete();
return {}; return {};
} }

View File

@ -155,7 +155,6 @@ namespace dawn_native { namespace vulkan {
} }
MaybeError Device::TickImpl() { MaybeError Device::TickImpl() {
CheckPassedSerials();
RecycleCompletedCommands(); RecycleCompletedCommands();
Serial completedSerial = GetCompletedCommandSerial(); Serial completedSerial = GetCompletedCommandSerial();
@ -171,10 +170,6 @@ namespace dawn_native { namespace vulkan {
if (mRecordingContext.used) { if (mRecordingContext.used) {
DAWN_TRY(SubmitPendingCommands()); DAWN_TRY(SubmitPendingCommands());
} else if (completedSerial == GetLastSubmittedCommandSerial()) {
// If there's no GPU work in flight we still need to artificially increment the serial
// so that CPU operations waiting on GPU completion can know they don't have to wait.
ArtificiallyIncrementSerials();
} }
return {}; return {};
@ -731,9 +726,6 @@ namespace dawn_native { namespace vulkan {
mFencesInFlight.pop(); mFencesInFlight.pop();
} }
// Force all operations to look as if they were completed
AssumeCommandsComplete();
return {}; return {};
} }
@ -773,11 +765,6 @@ namespace dawn_native { namespace vulkan {
} }
mRecordingContext.signalSemaphores.clear(); mRecordingContext.signalSemaphores.clear();
// Some operations might have been started since the last submit and waiting
// on a serial that doesn't have a corresponding fence enqueued. Force all
// operations to look as if they were completed (because they were).
AssumeCommandsComplete();
// Assert that errors are device loss so that we can continue with destruction // Assert that errors are device loss so that we can continue with destruction
AssertAndIgnoreDeviceLossError(TickImpl()); AssertAndIgnoreDeviceLossError(TickImpl());

View File

@ -143,6 +143,43 @@ TEST_P(FenceTests, MultipleSignalOnCompletion) {
WaitForCompletedValue(fence, 4); WaitForCompletedValue(fence, 4);
} }
// Test callbacks still occur if Queue::Signal and fence::OnCompletion happens multiple times
TEST_P(FenceTests, SignalOnCompletionWait) {
wgpu::Fence fence = queue.CreateFence();
queue.Signal(fence, 2);
queue.Signal(fence, 6);
EXPECT_CALL(*mockFenceOnCompletionCallback, Call(WGPUFenceCompletionStatus_Success, this + 2))
.Times(1);
EXPECT_CALL(*mockFenceOnCompletionCallback, Call(WGPUFenceCompletionStatus_Success, this + 6))
.Times(1);
fence.OnCompletion(1u, ToMockFenceOnCompletionCallback, this + 2);
fence.OnCompletion(5u, ToMockFenceOnCompletionCallback, this + 6);
WaitForCompletedValue(fence, 6);
}
// Test callbacks still occur if Queue::Signal and fence::OnCompletion happens multiple times
TEST_P(FenceTests, SignalOnCompletionWaitStaggered) {
wgpu::Fence fence = queue.CreateFence();
queue.Signal(fence, 2);
EXPECT_CALL(*mockFenceOnCompletionCallback, Call(WGPUFenceCompletionStatus_Success, this + 2))
.Times(1);
fence.OnCompletion(1u, ToMockFenceOnCompletionCallback, this + 2);
queue.Signal(fence, 4);
EXPECT_CALL(*mockFenceOnCompletionCallback, Call(WGPUFenceCompletionStatus_Success, this + 4))
.Times(1);
fence.OnCompletion(3u, ToMockFenceOnCompletionCallback, this + 4);
WaitForCompletedValue(fence, 4);
}
// Test all callbacks are called if they are added for the same fence value // Test all callbacks are called if they are added for the same fence value
TEST_P(FenceTests, OnCompletionMultipleCallbacks) { TEST_P(FenceTests, OnCompletionMultipleCallbacks) {
wgpu::Fence fence = queue.CreateFence(); wgpu::Fence fence = queue.CreateFence();