Support ErrorScopes for asynchronous GPU execution
This changes updates ErrorScopes so that scopes enclosing a
Queue::Submit or Queue::Signal resolve their callbacks asynchronously
after GPU execution is complete.
Bug: dawn:153
Change-Id: I0e0b8a9f19f3f29d1b6a3683938154b87f190a07
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/10701
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index e096cd3..d5364a1 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -168,6 +168,8 @@
"src/dawn_native/ErrorData.h",
"src/dawn_native/ErrorScope.cpp",
"src/dawn_native/ErrorScope.h",
+ "src/dawn_native/ErrorScopeTracker.cpp",
+ "src/dawn_native/ErrorScopeTracker.h",
"src/dawn_native/Extensions.cpp",
"src/dawn_native/Extensions.h",
"src/dawn_native/Fence.cpp",
@@ -595,7 +597,6 @@
# Allow inclusion of <GLFW/glfw3.h>
include_dirs = [ "${dawn_glfw_dir}/include" ]
-
# The GLFW/glfw3.h header includes <GL/gl.h> by default, but the latter
# does not exist on Fuchsia. Defining GLFW_INCLUDE_NONE helps work around
# the issue, but it needs to be defined for any file that includes the
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index 4343911..fa45063 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -25,6 +25,7 @@
#include "dawn_native/DynamicUploader.h"
#include "dawn_native/ErrorData.h"
#include "dawn_native/ErrorScope.h"
+#include "dawn_native/ErrorScopeTracker.h"
#include "dawn_native/Fence.h"
#include "dawn_native/FenceSignalTracker.h"
#include "dawn_native/Instance.h"
@@ -67,6 +68,7 @@
mRootErrorScope(AcquireRef(new ErrorScope())),
mCurrentErrorScope(mRootErrorScope.Get()) {
mCaches = std::make_unique<DeviceBase::Caches>();
+ mErrorScopeTracker = std::make_unique<ErrorScopeTracker>(this);
mFenceSignalTracker = std::make_unique<FenceSignalTracker>(this);
mDynamicUploader = std::make_unique<DynamicUploader>(this);
SetDefaultToggles();
@@ -121,6 +123,11 @@
return true;
}
+ ErrorScope* DeviceBase::GetCurrentErrorScope() {
+ ASSERT(mCurrentErrorScope.Get() != nullptr);
+ return mCurrentErrorScope.Get();
+ }
+
MaybeError DeviceBase::ValidateObject(const ObjectBase* object) const {
if (DAWN_UNLIKELY(object->GetDevice() != this)) {
return DAWN_VALIDATION_ERROR("Object from a different device.");
@@ -139,6 +146,10 @@
return GetAdapter()->GetInstance()->GetPlatform();
}
+ ErrorScopeTracker* DeviceBase::GetErrorScopeTracker() const {
+ return mErrorScopeTracker.get();
+ }
+
FenceSignalTracker* DeviceBase::GetFenceSignalTracker() const {
return mFenceSignalTracker.get();
}
@@ -519,6 +530,7 @@
deferred.callback(deferred.status, deferred.result, deferred.userdata);
}
}
+ mErrorScopeTracker->Tick(GetCompletedCommandSerial());
mFenceSignalTracker->Tick(GetCompletedCommandSerial());
}
diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h
index fe52fd2..1c3354f 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -36,6 +36,7 @@
class AttachmentState;
class AttachmentStateBlueprint;
class ErrorScope;
+ class ErrorScopeTracker;
class FenceSignalTracker;
class DynamicUploader;
class StagingBufferBase;
@@ -61,6 +62,7 @@
AdapterBase* GetAdapter() const;
dawn_platform::Platform* GetPlatform() const;
+ ErrorScopeTracker* GetErrorScopeTracker() const;
FenceSignalTracker* GetFenceSignalTracker() const;
// Returns the Format corresponding to the dawn::TextureFormat or an error if the format
@@ -153,6 +155,7 @@
void SetUncapturedErrorCallback(dawn::ErrorCallback callback, void* userdata);
void PushErrorScope(dawn::ErrorFilter filter);
bool PopErrorScope(dawn::ErrorCallback callback, void* userdata);
+ ErrorScope* GetCurrentErrorScope();
void Reference();
void Release();
@@ -251,6 +254,7 @@
void* userdata;
};
+ std::unique_ptr<ErrorScopeTracker> mErrorScopeTracker;
std::unique_ptr<FenceSignalTracker> mFenceSignalTracker;
std::vector<DeferredCreateBufferMappedAsync> mDeferredCreateBufferMappedAsyncResults;
diff --git a/src/dawn_native/ErrorScope.cpp b/src/dawn_native/ErrorScope.cpp
index 0b53216..812ab69 100644
--- a/src/dawn_native/ErrorScope.cpp
+++ b/src/dawn_native/ErrorScope.cpp
@@ -111,4 +111,11 @@
}
}
+ void ErrorScope::Destroy() {
+ if (!IsRoot()) {
+ mErrorType = dawn::ErrorType::Unknown;
+ mErrorMessage = "Error scope destroyed";
+ }
+ }
+
} // namespace dawn_native
diff --git a/src/dawn_native/ErrorScope.h b/src/dawn_native/ErrorScope.h
index 4fd57c9..9bafcf9 100644
--- a/src/dawn_native/ErrorScope.h
+++ b/src/dawn_native/ErrorScope.h
@@ -49,6 +49,8 @@
void HandleError(dawn::ErrorType type, const char* message);
void HandleError(ErrorData* error);
+ void Destroy();
+
private:
bool IsRoot() const;
static void HandleErrorImpl(ErrorScope* scope, dawn::ErrorType type, const char* message);
diff --git a/src/dawn_native/ErrorScopeTracker.cpp b/src/dawn_native/ErrorScopeTracker.cpp
new file mode 100644
index 0000000..e0a21aa
--- /dev/null
+++ b/src/dawn_native/ErrorScopeTracker.cpp
@@ -0,0 +1,44 @@
+// Copyright 2019 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dawn_native/ErrorScopeTracker.h"
+
+#include "dawn_native/Device.h"
+#include "dawn_native/ErrorScope.h"
+
+namespace dawn_native {
+
+ ErrorScopeTracker::ErrorScopeTracker(DeviceBase* device) : mDevice(device) {
+ }
+
+ ErrorScopeTracker::~ErrorScopeTracker() {
+ // The tracker is destroyed when the Device is destroyed. We need to
+ // call Destroy on all in-flight error scopes so they resolve their callbacks
+ // with UNKNOWN.
+ constexpr Serial maxSerial = std::numeric_limits<Serial>::max();
+ for (Ref<ErrorScope>& scope : mScopesInFlight.IterateUpTo(maxSerial)) {
+ scope->Destroy();
+ }
+ Tick(maxSerial);
+ }
+
+ void ErrorScopeTracker::TrackUntilLastSubmitComplete(ErrorScope* scope) {
+ mScopesInFlight.Enqueue(scope, mDevice->GetLastSubmittedCommandSerial());
+ }
+
+ void ErrorScopeTracker::Tick(Serial completedSerial) {
+ mScopesInFlight.ClearUpTo(completedSerial);
+ }
+
+} // namespace dawn_native
diff --git a/src/dawn_native/ErrorScopeTracker.h b/src/dawn_native/ErrorScopeTracker.h
new file mode 100644
index 0000000..7337eb6
--- /dev/null
+++ b/src/dawn_native/ErrorScopeTracker.h
@@ -0,0 +1,42 @@
+// Copyright 2019 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef DAWNNATIVE_ERRORSCOPETRACKER_H_
+#define DAWNNATIVE_ERRORSCOPETRACKER_H_
+
+#include "common/SerialQueue.h"
+#include "dawn_native/RefCounted.h"
+
+namespace dawn_native {
+
+ class DeviceBase;
+ class ErrorScope;
+
+ class ErrorScopeTracker {
+ public:
+ ErrorScopeTracker(DeviceBase* device);
+ ~ErrorScopeTracker();
+
+ void TrackUntilLastSubmitComplete(ErrorScope* scope);
+
+ void Tick(Serial completedSerial);
+
+ protected:
+ DeviceBase* mDevice;
+ SerialQueue<Ref<ErrorScope>> mScopesInFlight;
+ };
+
+} // namespace dawn_native
+
+#endif // DAWNNATIVE_ERRORSCOPETRACKER_H_
diff --git a/src/dawn_native/Queue.cpp b/src/dawn_native/Queue.cpp
index 46ead11..d3b2455 100644
--- a/src/dawn_native/Queue.cpp
+++ b/src/dawn_native/Queue.cpp
@@ -17,6 +17,8 @@
#include "dawn_native/Buffer.h"
#include "dawn_native/CommandBuffer.h"
#include "dawn_native/Device.h"
+#include "dawn_native/ErrorScope.h"
+#include "dawn_native/ErrorScopeTracker.h"
#include "dawn_native/Fence.h"
#include "dawn_native/FenceSignalTracker.h"
#include "dawn_native/Texture.h"
@@ -30,24 +32,29 @@
}
void QueueBase::Submit(uint32_t commandCount, CommandBufferBase* const* commands) {
- TRACE_EVENT0(GetDevice()->GetPlatform(), TRACE_DISABLED_BY_DEFAULT("gpu.dawn"),
- "Queue::Submit");
- if (GetDevice()->ConsumedError(ValidateSubmit(commandCount, commands))) {
+ DeviceBase* device = GetDevice();
+ TRACE_EVENT0(device->GetPlatform(), TRACE_DISABLED_BY_DEFAULT("gpu.dawn"), "Queue::Submit");
+ if (device->ConsumedError(ValidateSubmit(commandCount, commands))) {
return;
}
ASSERT(!IsError());
SubmitImpl(commandCount, commands);
+ device->GetErrorScopeTracker()->TrackUntilLastSubmitComplete(
+ device->GetCurrentErrorScope());
}
void QueueBase::Signal(FenceBase* fence, uint64_t signalValue) {
- if (GetDevice()->ConsumedError(ValidateSignal(fence, signalValue))) {
+ DeviceBase* device = GetDevice();
+ if (device->ConsumedError(ValidateSignal(fence, signalValue))) {
return;
}
ASSERT(!IsError());
fence->SetSignaledValue(signalValue);
- GetDevice()->GetFenceSignalTracker()->UpdateFenceOnComplete(fence, signalValue);
+ device->GetFenceSignalTracker()->UpdateFenceOnComplete(fence, signalValue);
+ device->GetErrorScopeTracker()->TrackUntilLastSubmitComplete(
+ device->GetCurrentErrorScope());
}
FenceBase* QueueBase::CreateFence(const FenceDescriptor* descriptor) {
diff --git a/src/tests/unittests/validation/ErrorScopeValidationTests.cpp b/src/tests/unittests/validation/ErrorScopeValidationTests.cpp
index 4d9629e..8d2c749 100644
--- a/src/tests/unittests/validation/ErrorScopeValidationTests.cpp
+++ b/src/tests/unittests/validation/ErrorScopeValidationTests.cpp
@@ -132,3 +132,68 @@
EXPECT_FALSE(device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this + 2));
}
}
+
+// Test that error scopes do not call their callbacks until after an enclosed Queue::Submit
+// completes
+TEST_F(ErrorScopeValidationTest, CallbackAfterQueueSubmit) {
+ dawn::Queue queue = device.CreateQueue();
+
+ device.PushErrorScope(dawn::ErrorFilter::OutOfMemory);
+ queue.Submit(0, nullptr);
+ device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this);
+
+ EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_NO_ERROR, _, this)).Times(1);
+
+ // Side effects of Queue::Submit only are seen after Tick()
+ device.Tick();
+}
+
+// Test that parent error scopes do not call their callbacks until after an enclosed Queue::Submit
+// completes
+TEST_F(ErrorScopeValidationTest, CallbackAfterQueueSubmitNested) {
+ dawn::Queue queue = device.CreateQueue();
+
+ device.PushErrorScope(dawn::ErrorFilter::OutOfMemory);
+ device.PushErrorScope(dawn::ErrorFilter::OutOfMemory);
+ queue.Submit(0, nullptr);
+ device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this);
+ device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this + 1);
+
+ EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_NO_ERROR, _, this)).Times(1);
+ EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_NO_ERROR, _, this + 1))
+ .Times(1);
+
+ // Side effects of Queue::Submit only are seen after Tick()
+ device.Tick();
+}
+
+// Test a callback that returns asynchronously followed by a synchronous one
+TEST_F(ErrorScopeValidationTest, AsynchronousThenSynchronous) {
+ dawn::Queue queue = device.CreateQueue();
+
+ device.PushErrorScope(dawn::ErrorFilter::OutOfMemory);
+ queue.Submit(0, nullptr);
+ device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this);
+
+ EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_NO_ERROR, _, this + 1))
+ .Times(1);
+ device.PushErrorScope(dawn::ErrorFilter::OutOfMemory);
+ device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this + 1);
+
+ EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_NO_ERROR, _, this)).Times(1);
+
+ // Side effects of Queue::Submit only are seen after Tick()
+ device.Tick();
+}
+
+// Test that if the device is destroyed before the callback occurs, it is called with UNKNOWN.
+TEST_F(ErrorScopeValidationTest, DeviceDestroyedBeforeCallback) {
+ dawn::Queue queue = device.CreateQueue();
+
+ device.PushErrorScope(dawn::ErrorFilter::OutOfMemory);
+ queue.Submit(0, nullptr);
+ device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this);
+
+ EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_UNKNOWN, _, this)).Times(1);
+ device = nullptr;
+}