diff --git a/src/dawn_native/BUILD.gn b/src/dawn_native/BUILD.gn index 3f3cb4fce1..77afa36061 100644 --- a/src/dawn_native/BUILD.gn +++ b/src/dawn_native/BUILD.gn @@ -217,8 +217,6 @@ source_set("dawn_native_sources") { "ErrorInjector.h", "ErrorScope.cpp", "ErrorScope.h", - "ErrorScopeTracker.cpp", - "ErrorScopeTracker.h", "Extensions.cpp", "Extensions.h", "Fence.cpp", diff --git a/src/dawn_native/CMakeLists.txt b/src/dawn_native/CMakeLists.txt index 6263a9dcb9..91c474cbd9 100644 --- a/src/dawn_native/CMakeLists.txt +++ b/src/dawn_native/CMakeLists.txt @@ -90,8 +90,6 @@ target_sources(dawn_native PRIVATE "ErrorInjector.h" "ErrorScope.cpp" "ErrorScope.h" - "ErrorScopeTracker.cpp" - "ErrorScopeTracker.h" "Extensions.cpp" "Extensions.h" "ObjectContentHasher.cpp" diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp index 29c9a9522a..d2fdd97e63 100644 --- a/src/dawn_native/Device.cpp +++ b/src/dawn_native/Device.cpp @@ -27,7 +27,6 @@ #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/Instance.h" #include "dawn_native/InternalPipelineStore.h" @@ -98,22 +97,17 @@ namespace dawn_native { MaybeError DeviceBase::Initialize(QueueBase* defaultQueue) { mQueue = AcquireRef(defaultQueue); - mRootErrorScope = AcquireRef(new ErrorScope()); - mCurrentErrorScope = mRootErrorScope.Get(); #if defined(DAWN_ENABLE_ASSERTS) - mRootErrorScope->SetCallback( - [](WGPUErrorType, char const*, void*) { - static bool calledOnce = false; - if (!calledOnce) { - calledOnce = true; - dawn::WarningLog() - << "No Dawn device uncaptured error callback was set. This is " - "probably not intended. If you really want to ignore errors " - "and suppress this message, set the callback to null."; - } - }, - nullptr); + mUncapturedErrorCallback = [](WGPUErrorType, char const*, void*) { + static bool calledOnce = false; + if (!calledOnce) { + calledOnce = true; + dawn::WarningLog() << "No Dawn device uncaptured error callback was set. This is " + "probably not intended. If you really want to ignore errors " + "and suppress this message, set the callback to null."; + } + }; mDeviceLostCallback = [](char const*, void*) { static bool calledOnce = false; @@ -127,7 +121,7 @@ namespace dawn_native { #endif // DAWN_ENABLE_ASSERTS mCaches = std::make_unique(); - mErrorScopeTracker = std::make_unique(this); + mErrorScopeStack = std::make_unique(); mDynamicUploader = std::make_unique(this); mCreateReadyPipelineTracker = std::make_unique(this); mDeprecationWarnings = std::make_unique(); @@ -146,9 +140,6 @@ namespace dawn_native { void DeviceBase::ShutDownBase() { // Skip handling device facilities if they haven't even been created (or failed doing so) if (mState != State::BeingCreated) { - // Reject all error scope callbacks. - mErrorScopeTracker->ClearForShutDown(); - // Reject all async pipeline creations. mCreateReadyPipelineTracker->ClearForShutDown(); } @@ -194,11 +185,6 @@ namespace dawn_native { // At this point GPU operations are always finished, so we are in the disconnected state. mState = State::Disconnected; - // mCurrentErrorScope can be null if we failed device initialization. - if (mCurrentErrorScope != nullptr) { - mCurrentErrorScope->UnlinkForShutdown(); - } - mErrorScopeTracker = nullptr; mDynamicUploader = nullptr; mCreateReadyPipelineTracker = nullptr; mPersistentCache = nullptr; @@ -242,16 +228,27 @@ namespace dawn_native { type = InternalErrorType::DeviceLost; } - // The device was lost, call the application callback. - if (type == InternalErrorType::DeviceLost && mDeviceLostCallback != nullptr) { + if (type == InternalErrorType::DeviceLost) { + // The device was lost, call the application callback. + if (mDeviceLostCallback != nullptr) { + mDeviceLostCallback(message, mDeviceLostUserdata); + mDeviceLostCallback = nullptr; + } + mQueue->HandleDeviceLoss(); - mDeviceLostCallback(message, mDeviceLostUserdata); - mDeviceLostCallback = nullptr; + // Still forward device loss errors to the error scopes so they all reject. + mErrorScopeStack->HandleError(ToWGPUErrorType(type), message); + } else { + // Pass the error to the error scope stack and call the uncaptured error callback + // if it isn't handled. DeviceLost is not handled here because it should be + // handled by the lost callback. + bool captured = mErrorScopeStack->HandleError(ToWGPUErrorType(type), message); + if (!captured && mUncapturedErrorCallback != nullptr) { + mUncapturedErrorCallback(static_cast(ToWGPUErrorType(type)), message, + mUncapturedErrorUserdata); + } } - - // Still forward device loss errors to the error scopes so they all reject. - mCurrentErrorScope->HandleError(ToWGPUErrorType(type), message); } void DeviceBase::InjectError(wgpu::ErrorType type, const char* message) { @@ -282,7 +279,8 @@ namespace dawn_native { } void DeviceBase::SetUncapturedErrorCallback(wgpu::ErrorCallback callback, void* userdata) { - mRootErrorScope->SetCallback(callback, userdata); + mUncapturedErrorCallback = callback; + mUncapturedErrorUserdata = userdata; } void DeviceBase::SetDeviceLostCallback(wgpu::DeviceLostCallback callback, void* userdata) { @@ -294,24 +292,22 @@ namespace dawn_native { if (ConsumedError(ValidateErrorFilter(filter))) { return; } - mCurrentErrorScope = AcquireRef(new ErrorScope(filter, mCurrentErrorScope.Get())); + mErrorScopeStack->Push(filter); } bool DeviceBase::PopErrorScope(wgpu::ErrorCallback callback, void* userdata) { - if (DAWN_UNLIKELY(mCurrentErrorScope.Get() == mRootErrorScope.Get())) { + if (mErrorScopeStack->Empty()) { return false; } - mCurrentErrorScope->SetCallback(callback, userdata); - mCurrentErrorScope = Ref(mCurrentErrorScope->GetParent()); + ErrorScope scope = mErrorScopeStack->Pop(); + if (callback != nullptr) { + callback(static_cast(scope.GetErrorType()), scope.GetErrorMessage(), + userdata); + } return true; } - ErrorScope* DeviceBase::GetCurrentErrorScope() { - ASSERT(mCurrentErrorScope != nullptr); - return mCurrentErrorScope.Get(); - } - PersistentCache* DeviceBase::GetPersistentCache() { ASSERT(mPersistentCache.get() != nullptr); return mPersistentCache.get(); @@ -360,10 +356,6 @@ namespace dawn_native { return GetAdapter()->GetInstance()->GetPlatform(); } - ErrorScopeTracker* DeviceBase::GetErrorScopeTracker() const { - return mErrorScopeTracker.get(); - } - ExecutionSerial DeviceBase::GetCompletedCommandSerial() const { return mCompletedSerial; } @@ -868,7 +860,6 @@ namespace dawn_native { // tick the dynamic uploader before the backend resource allocators. This would allow // reclaiming resources one tick earlier. mDynamicUploader->Deallocate(mCompletedSerial); - mErrorScopeTracker->Tick(mCompletedSerial); GetQueue()->Tick(mCompletedSerial); mCreateReadyPipelineTracker->Tick(mCompletedSerial); diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h index 413a9d899a..3a830d926e 100644 --- a/src/dawn_native/Device.h +++ b/src/dawn_native/Device.h @@ -35,8 +35,7 @@ namespace dawn_native { class BindGroupLayoutBase; class CreateReadyPipelineTracker; class DynamicUploader; - class ErrorScope; - class ErrorScopeTracker; + class ErrorScopeStack; class PersistentCache; class StagingBufferBase; struct InternalPipelineStore; @@ -72,8 +71,6 @@ namespace dawn_native { AdapterBase* GetAdapter() const; dawn_platform::Platform* GetPlatform() const; - ErrorScopeTracker* GetErrorScopeTracker() const; - // Returns the Format corresponding to the wgpu::TextureFormat or an error if the format // isn't a valid wgpu::TextureFormat or isn't supported by this device. // The pointer returned has the same lifetime as the device. @@ -183,8 +180,6 @@ namespace dawn_native { MaybeError ValidateIsAlive() const; - ErrorScope* GetCurrentErrorScope(); - PersistentCache* GetPersistentCache(); void Reference(); @@ -363,9 +358,14 @@ namespace dawn_native { // resources. virtual MaybeError WaitForIdleForDestruction() = 0; + wgpu::ErrorCallback mUncapturedErrorCallback = nullptr; + void* mUncapturedErrorUserdata = nullptr; + wgpu::DeviceLostCallback mDeviceLostCallback = nullptr; void* mDeviceLostUserdata = nullptr; + std::unique_ptr mErrorScopeStack; + // The Device keeps a ref to the Instance so that any live Device keeps the Instance alive. // The Instance shouldn't need to ref child objects so this shouldn't introduce ref cycles. // The Device keeps a simple pointer to the Adapter because the Adapter is owned by the @@ -373,9 +373,6 @@ namespace dawn_native { Ref mInstance; AdapterBase* mAdapter = nullptr; - Ref mRootErrorScope; - Ref mCurrentErrorScope; - // The object caches aren't exposed in the header as they would require a lot of // additional includes. struct Caches; @@ -384,7 +381,6 @@ namespace dawn_native { Ref mEmptyBindGroupLayout; std::unique_ptr mDynamicUploader; - std::unique_ptr mErrorScopeTracker; std::unique_ptr mCreateReadyPipelineTracker; Ref mQueue; diff --git a/src/dawn_native/ErrorScope.cpp b/src/dawn_native/ErrorScope.cpp index 314b422350..01c88ed573 100644 --- a/src/dawn_native/ErrorScope.cpp +++ b/src/dawn_native/ErrorScope.cpp @@ -18,136 +18,77 @@ namespace dawn_native { - ErrorScope::ErrorScope() : mIsRoot(true) { - } + namespace { - ErrorScope::ErrorScope(wgpu::ErrorFilter errorFilter, ErrorScope* parent) - : RefCounted(), mErrorFilter(errorFilter), mParent(parent), mIsRoot(false) { - ASSERT(mParent != nullptr); - } - - ErrorScope::~ErrorScope() { - if (!IsRoot()) { - RunNonRootCallback(); + wgpu::ErrorType ErrorFilterToErrorType(wgpu::ErrorFilter filter) { + switch (filter) { + case wgpu::ErrorFilter::None: + return wgpu::ErrorType::NoError; + case wgpu::ErrorFilter::Validation: + return wgpu::ErrorType::Validation; + case wgpu::ErrorFilter::OutOfMemory: + return wgpu::ErrorType::OutOfMemory; + } } + + } // namespace + + ErrorScope::ErrorScope(wgpu::ErrorFilter errorFilter) + : mMatchedErrorType(ErrorFilterToErrorType(errorFilter)) { } - void ErrorScope::SetCallback(wgpu::ErrorCallback callback, void* userdata) { - mCallback = callback; - mUserdata = userdata; + wgpu::ErrorType ErrorScope::GetErrorType() const { + return mCapturedError; } - ErrorScope* ErrorScope::GetParent() { - return mParent.Get(); + const char* ErrorScope::GetErrorMessage() const { + return mErrorMessage.c_str(); } - bool ErrorScope::IsRoot() const { - return mIsRoot; + void ErrorScopeStack::Push(wgpu::ErrorFilter filter) { + mScopes.push_back(ErrorScope(filter)); } - void ErrorScope::RunNonRootCallback() { - ASSERT(!IsRoot()); - - if (mCallback != nullptr) { - // For non-root error scopes, the callback can run at most once. - mCallback(static_cast(mErrorType), mErrorMessage.c_str(), mUserdata); - mCallback = nullptr; - } + ErrorScope ErrorScopeStack::Pop() { + ASSERT(!mScopes.empty()); + ErrorScope scope = std::move(mScopes.back()); + mScopes.pop_back(); + return scope; } - void ErrorScope::HandleError(wgpu::ErrorType type, const char* message) { - HandleErrorImpl(this, type, message); + bool ErrorScopeStack::Empty() const { + return mScopes.empty(); } - void ErrorScope::UnlinkForShutdown() { - UnlinkForShutdownImpl(this); - } - - // static - void ErrorScope::HandleErrorImpl(ErrorScope* scope, wgpu::ErrorType type, const char* message) { - ErrorScope* currentScope = scope; - for (; !currentScope->IsRoot(); currentScope = currentScope->GetParent()) { - ASSERT(currentScope != nullptr); - - bool consumed = false; - switch (type) { - case wgpu::ErrorType::Validation: - if (currentScope->mErrorFilter != wgpu::ErrorFilter::Validation) { - // Error filter does not match. Move on to the next scope. - continue; - } - consumed = true; - break; - - case wgpu::ErrorType::OutOfMemory: - if (currentScope->mErrorFilter != wgpu::ErrorFilter::OutOfMemory) { - // Error filter does not match. Move on to the next scope. - continue; - } - consumed = true; - break; - - // DeviceLost is fatal. All error scopes capture them. - // |consumed| is false because these should bubble to all scopes. - case wgpu::ErrorType::DeviceLost: - consumed = false; - if (currentScope->mErrorType != wgpu::ErrorType::DeviceLost) { - // DeviceLost overrides any other error that is not a DeviceLost. - currentScope->mErrorType = type; - currentScope->mErrorMessage = message; - } - break; - - case wgpu::ErrorType::Unknown: - // Means the scope was destroyed before contained work finished. - // This happens when you destroy the device while there's pending work. - // That's handled in ErrorScope::UnlinkForShutdownImpl, not here. - case wgpu::ErrorType::NoError: - // Not considered an error, and should never be passed to HandleError. - UNREACHABLE(); - return; + bool ErrorScopeStack::HandleError(wgpu::ErrorType type, const char* message) { + ASSERT(type != wgpu::ErrorType::NoError); + for (auto it = mScopes.rbegin(); it != mScopes.rend(); ++it) { + if (it->mMatchedErrorType != type) { + // Error filter does not match. Move on to the next scope. + continue; } + // Filter matches. // Record the error if the scope doesn't have one yet. - if (currentScope->mErrorType == wgpu::ErrorType::NoError) { - currentScope->mErrorType = type; - currentScope->mErrorMessage = message; + if (it->mCapturedError == wgpu::ErrorType::NoError) { + it->mCapturedError = type; + it->mErrorMessage = message; } - if (consumed) { - return; + if (type == wgpu::ErrorType::DeviceLost) { + if (it->mCapturedError != wgpu::ErrorType::DeviceLost) { + // DeviceLost overrides any other error that is not a DeviceLost. + it->mCapturedError = type; + it->mErrorMessage = message; + } + } else { + // Errors that are not device lost are captured and stop propogating. + return true; } } - // The root error scope captures all uncaptured errors. - // Except, it should not capture device lost errors since those go to - // the device lost callback. - ASSERT(currentScope->IsRoot()); - if (currentScope->mCallback && type != wgpu::ErrorType::DeviceLost) { - currentScope->mCallback(static_cast(type), message, - currentScope->mUserdata); - } - } - - // static - void ErrorScope::UnlinkForShutdownImpl(ErrorScope* scope) { - Ref currentScope = scope; - Ref parentScope = nullptr; - for (; !currentScope->IsRoot(); currentScope = parentScope.Get()) { - ASSERT(!currentScope->IsRoot()); - ASSERT(currentScope != nullptr); - parentScope = std::move(currentScope->mParent); - ASSERT(parentScope != nullptr); - - // On shutdown, error scopes that have yet to have a status get Unknown. - if (currentScope->mErrorType == wgpu::ErrorType::NoError) { - currentScope->mErrorType = wgpu::ErrorType::Unknown; - currentScope->mErrorMessage = "Error scope destroyed"; - } - - // Run the callback if it hasn't run already. - currentScope->RunNonRootCallback(); - } + // The error was not captured. + return false; } } // namespace dawn_native diff --git a/src/dawn_native/ErrorScope.h b/src/dawn_native/ErrorScope.h index 75327d5569..2d74456f71 100644 --- a/src/dawn_native/ErrorScope.h +++ b/src/dawn_native/ErrorScope.h @@ -17,54 +17,41 @@ #include "dawn_native/dawn_platform.h" -#include "common/RefCounted.h" - #include +#include namespace dawn_native { - // Errors can be recorded into an ErrorScope by calling |HandleError|. - // Because an error scope should not resolve until contained - // commands are complete, calling the callback is deferred until it is destructed. - // In-flight commands or asynchronous events should hold a reference to the - // ErrorScope for their duration. - // - // Because parent ErrorScopes should not resolve before child ErrorScopes, - // ErrorScopes hold a reference to their parent. - // - // To simplify ErrorHandling, there is a sentinel root error scope which has - // no parent. All uncaptured errors are handled by the root error scope. Its - // callback is called immediately once it encounters an error. - class ErrorScope final : public RefCounted { + class ErrorScope { public: - ErrorScope(); // Constructor for the root error scope. - ErrorScope(wgpu::ErrorFilter errorFilter, ErrorScope* parent); - - void SetCallback(wgpu::ErrorCallback callback, void* userdata); - ErrorScope* GetParent(); - - void HandleError(wgpu::ErrorType type, const char* message); - void UnlinkForShutdown(); + wgpu::ErrorType GetErrorType() const; + const char* GetErrorMessage() const; private: - ~ErrorScope() override; - bool IsRoot() const; - void RunNonRootCallback(); + friend class ErrorScopeStack; + explicit ErrorScope(wgpu::ErrorFilter errorFilter); - static void HandleErrorImpl(ErrorScope* scope, wgpu::ErrorType type, const char* message); - static void UnlinkForShutdownImpl(ErrorScope* scope); - - wgpu::ErrorFilter mErrorFilter = wgpu::ErrorFilter::None; - Ref mParent = nullptr; - bool mIsRoot; - - wgpu::ErrorCallback mCallback = nullptr; - void* mUserdata = nullptr; - - wgpu::ErrorType mErrorType = wgpu::ErrorType::NoError; + wgpu::ErrorType mMatchedErrorType; + wgpu::ErrorType mCapturedError = wgpu::ErrorType::NoError; std::string mErrorMessage = ""; }; + class ErrorScopeStack { + public: + void Push(wgpu::ErrorFilter errorFilter); + ErrorScope Pop(); + + bool Empty() const; + + // Pass an error to the scopes in the stack. Returns true if one of the scopes + // captured the error. Returns false if the error should be forwarded to the + // uncaptured error callback. + bool HandleError(wgpu::ErrorType type, const char* message); + + private: + std::vector mScopes; + }; + } // namespace dawn_native #endif // DAWNNATIVE_ERRORSCOPE_H_ diff --git a/src/dawn_native/ErrorScopeTracker.cpp b/src/dawn_native/ErrorScopeTracker.cpp deleted file mode 100644 index b466422b0d..0000000000 --- a/src/dawn_native/ErrorScopeTracker.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// 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" - -#include - -namespace dawn_native { - - ErrorScopeTracker::ErrorScopeTracker(DeviceBase* device) : mDevice(device) { - } - - ErrorScopeTracker::~ErrorScopeTracker() { - ASSERT(mScopesInFlight.Empty()); - } - - void ErrorScopeTracker::TrackUntilLastSubmitComplete(ErrorScope* scope) { - mScopesInFlight.Enqueue(scope, mDevice->GetLastSubmittedCommandSerial()); - mDevice->AddFutureSerial(mDevice->GetPendingCommandSerial()); - } - - void ErrorScopeTracker::Tick(ExecutionSerial completedSerial) { - mScopesInFlight.ClearUpTo(completedSerial); - } - - void ErrorScopeTracker::ClearForShutDown() { - for (Ref& scope : mScopesInFlight.IterateAll()) { - scope->UnlinkForShutdown(); - } - mScopesInFlight.Clear(); - } - -} // namespace dawn_native diff --git a/src/dawn_native/ErrorScopeTracker.h b/src/dawn_native/ErrorScopeTracker.h deleted file mode 100644 index d05daa1860..0000000000 --- a/src/dawn_native/ErrorScopeTracker.h +++ /dev/null @@ -1,44 +0,0 @@ -// 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/RefCounted.h" -#include "common/SerialQueue.h" -#include "dawn_native/IntegerTypes.h" - -namespace dawn_native { - - class DeviceBase; - class ErrorScope; - - class ErrorScopeTracker { - public: - ErrorScopeTracker(DeviceBase* device); - ~ErrorScopeTracker(); - - void TrackUntilLastSubmitComplete(ErrorScope* scope); - - void Tick(ExecutionSerial completedSerial); - void ClearForShutDown(); - - protected: - DeviceBase* mDevice; - SerialQueue> mScopesInFlight; - }; - -} // namespace dawn_native - -#endif // DAWNNATIVE_ERRORSCOPETRACKER_H_ diff --git a/src/dawn_native/Queue.cpp b/src/dawn_native/Queue.cpp index 1bb56defc4..fd65fbd65a 100644 --- a/src/dawn_native/Queue.cpp +++ b/src/dawn_native/Queue.cpp @@ -23,8 +23,6 @@ #include "dawn_native/CopyTextureForBrowserHelper.h" #include "dawn_native/Device.h" #include "dawn_native/DynamicUploader.h" -#include "dawn_native/ErrorScope.h" -#include "dawn_native/ErrorScopeTracker.h" #include "dawn_native/Fence.h" #include "dawn_native/QuerySet.h" #include "dawn_native/RenderPassEncoder.h" @@ -176,8 +174,6 @@ namespace dawn_native { fence->SetSignaledValue(signalValue); fence->UpdateFenceOnComplete(fence, signalValue); - device->GetErrorScopeTracker()->TrackUntilLastSubmitComplete( - device->GetCurrentErrorScope()); } void QueueBase::TrackTask(std::unique_ptr task, ExecutionSerial serial) { @@ -487,9 +483,6 @@ namespace dawn_native { if (device->ConsumedError(SubmitImpl(commandCount, commands))) { return; } - - device->GetErrorScopeTracker()->TrackUntilLastSubmitComplete( - device->GetCurrentErrorScope()); } } // namespace dawn_native diff --git a/src/tests/unittests/validation/ErrorScopeValidationTests.cpp b/src/tests/unittests/validation/ErrorScopeValidationTests.cpp index e59a82c4ad..e25d373bab 100644 --- a/src/tests/unittests/validation/ErrorScopeValidationTests.cpp +++ b/src/tests/unittests/validation/ErrorScopeValidationTests.cpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "tests/MockCallback.h" #include "tests/unittests/validation/ValidationTest.h" #include @@ -140,64 +141,64 @@ TEST_F(ErrorScopeValidationTest, PushPopBalanced) { } } -// Test that error scopes do not call their callbacks until after an enclosed Queue::Submit +// Test that error scopes call their callbacks before an enclosed Queue::Submit // completes -TEST_F(ErrorScopeValidationTest, CallbackAfterQueueSubmit) { +TEST_F(ErrorScopeValidationTest, EnclosedQueueSubmit) { wgpu::Queue queue = device.GetQueue(); device.PushErrorScope(wgpu::ErrorFilter::OutOfMemory); + queue.Submit(0, nullptr); - device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this); + wgpu::Fence fence = queue.CreateFence(); + queue.Signal(fence, 1); - EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_NoError, _, this)).Times(1); + testing::Sequence seq; - // Side effects of Queue::Submit only are seen after Tick() - device.Tick(); - FlushWire(); + MockCallback fenceCallback; + fence.OnCompletion(1, fenceCallback.Callback(), fenceCallback.MakeUserdata(this)); + + MockCallback errorScopeCallback; + EXPECT_CALL(errorScopeCallback, Call(WGPUErrorType_NoError, _, this + 1)).InSequence(seq); + device.PopErrorScope(errorScopeCallback.Callback(), errorScopeCallback.MakeUserdata(this + 1)); + + EXPECT_CALL(fenceCallback, Call(WGPUFenceCompletionStatus_Success, this)).InSequence(seq); + WaitForAllOperations(device); } -// Test that parent error scopes do not call their callbacks until after an enclosed Queue::Submit +// Test that parent error scopes also call their callbacks before an enclosed Queue::Submit // completes -TEST_F(ErrorScopeValidationTest, CallbackAfterQueueSubmitNested) { +TEST_F(ErrorScopeValidationTest, EnclosedQueueSubmitNested) { wgpu::Queue queue = device.GetQueue(); device.PushErrorScope(wgpu::ErrorFilter::OutOfMemory); device.PushErrorScope(wgpu::ErrorFilter::OutOfMemory); + queue.Submit(0, nullptr); - device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this); - device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this + 1); + wgpu::Fence fence = queue.CreateFence(); + queue.Signal(fence, 1); - EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_NoError, _, this)).Times(1); - EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_NoError, _, this + 1)) - .Times(1); + testing::Sequence seq; - // Side effects of Queue::Submit only are seen after Tick() - device.Tick(); - FlushWire(); -} + MockCallback fenceCallback; + fence.OnCompletion(1, fenceCallback.Callback(), fenceCallback.MakeUserdata(this)); -// Test a callback that returns asynchronously followed by a synchronous one -TEST_F(ErrorScopeValidationTest, AsynchronousThenSynchronous) { - wgpu::Queue queue = device.GetQueue(); + MockCallback errorScopeCallback2; + EXPECT_CALL(errorScopeCallback2, Call(WGPUErrorType_NoError, _, this + 1)).InSequence(seq); + device.PopErrorScope(errorScopeCallback2.Callback(), + errorScopeCallback2.MakeUserdata(this + 1)); - device.PushErrorScope(wgpu::ErrorFilter::OutOfMemory); - queue.Submit(0, nullptr); - device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this); + MockCallback errorScopeCallback1; + EXPECT_CALL(errorScopeCallback1, Call(WGPUErrorType_NoError, _, this + 2)).InSequence(seq); + device.PopErrorScope(errorScopeCallback1.Callback(), + errorScopeCallback1.MakeUserdata(this + 2)); - EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_NoError, _, this + 1)) - .Times(1); - device.PushErrorScope(wgpu::ErrorFilter::OutOfMemory); - device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this + 1); - - EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_NoError, _, this)).Times(1); - - // Side effects of Queue::Submit only are seen after Tick() - device.Tick(); - FlushWire(); + EXPECT_CALL(fenceCallback, Call(WGPUFenceCompletionStatus_Success, this)).InSequence(seq); + WaitForAllOperations(device); } // Test that if the device is destroyed before the callback occurs, it is called with NoError -// because all previous operations are waited upon before the destruction returns. +// in dawn_native, but Unknown in dawn_wire because the device is destroyed before the callback +// message happens. TEST_F(ErrorScopeValidationTest, DeviceDestroyedBeforeCallback) { device.PushErrorScope(wgpu::ErrorFilter::OutOfMemory); { @@ -205,10 +206,20 @@ TEST_F(ErrorScopeValidationTest, DeviceDestroyedBeforeCallback) { wgpu::Queue queue = device.GetQueue(); queue.Submit(0, nullptr); } - device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this); - EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_Unknown, _, this)).Times(1); - device = nullptr; + if (UsesWire()) { + device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this); + + EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_Unknown, _, this)) + .Times(1); + device = nullptr; + } else { + EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_NoError, _, this)) + .Times(1); + device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this); + + device = nullptr; + } } // Regression test that on device shutdown, we don't get a recursion in O(pushed error scope) that