Implement ErrorScopes for synchronous errors

This patch implements Push/PopErrorScope except for asynchronous
or GPU commands. These commands, such as Queue::Submit will need
to hold onto the ErrorScope until GPU execution is complete.

Bug: dawn:153
Change-Id: I2d340b8b391d117a59497f35690993a9cd7503e6
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/10700
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
This commit is contained in:
Austin Eng 2019-09-10 23:19:11 +00:00 committed by Commit Bot service account
parent 619935f7f2
commit f35dcfe60a
8 changed files with 356 additions and 29 deletions

View File

@ -166,6 +166,8 @@ source_set("libdawn_native_sources") {
"src/dawn_native/Error.h",
"src/dawn_native/ErrorData.cpp",
"src/dawn_native/ErrorData.h",
"src/dawn_native/ErrorScope.cpp",
"src/dawn_native/ErrorScope.h",
"src/dawn_native/Extensions.cpp",
"src/dawn_native/Extensions.h",
"src/dawn_native/Fence.cpp",
@ -767,6 +769,7 @@ test("dawn_unittests") {
"src/tests/unittests/validation/DebugMarkerValidationTests.cpp",
"src/tests/unittests/validation/DrawIndirectValidationTests.cpp",
"src/tests/unittests/validation/DynamicStateCommandValidationTests.cpp",
"src/tests/unittests/validation/ErrorScopeValidationTests.cpp",
"src/tests/unittests/validation/FenceValidationTests.cpp",
"src/tests/unittests/validation/QueueSubmitValidationTests.cpp",
"src/tests/unittests/validation/RenderBundleValidationTests.cpp",

View File

@ -24,6 +24,7 @@
#include "dawn_native/ComputePipeline.h"
#include "dawn_native/DynamicUploader.h"
#include "dawn_native/ErrorData.h"
#include "dawn_native/ErrorScope.h"
#include "dawn_native/Fence.h"
#include "dawn_native/FenceSignalTracker.h"
#include "dawn_native/Instance.h"
@ -35,6 +36,7 @@
#include "dawn_native/ShaderModule.h"
#include "dawn_native/SwapChain.h"
#include "dawn_native/Texture.h"
#include "dawn_native/ValidationUtils_autogen.h"
#include <unordered_set>
@ -61,7 +63,9 @@ namespace dawn_native {
// DeviceBase
DeviceBase::DeviceBase(AdapterBase* adapter, const DeviceDescriptor* descriptor)
: mAdapter(adapter) {
: mAdapter(adapter),
mRootErrorScope(AcquireRef(new ErrorScope())),
mCurrentErrorScope(mRootErrorScope.Get()) {
mCaches = std::make_unique<DeviceBase::Caches>();
mFenceSignalTracker = std::make_unique<FenceSignalTracker>(this);
mDynamicUploader = std::make_unique<DynamicUploader>(this);
@ -89,25 +93,32 @@ namespace dawn_native {
}
void DeviceBase::HandleError(dawn::ErrorType type, const char* message) {
if (mErrorCallback) {
mErrorCallback(static_cast<DawnErrorType>(type), message, mErrorUserdata);
}
mCurrentErrorScope->HandleError(type, message);
}
void DeviceBase::HandleError(ErrorData* data) {
mCurrentErrorScope->HandleError(data);
}
void DeviceBase::SetUncapturedErrorCallback(dawn::ErrorCallback callback, void* userdata) {
mErrorCallback = callback;
mErrorUserdata = userdata;
mRootErrorScope->SetCallback(callback, userdata);
}
void DeviceBase::PushErrorScope(dawn::ErrorFilter filter) {
// TODO(crbug.com/dawn/153): Implement error scopes.
HandleError(dawn::ErrorType::Validation, "Error scopes not implemented");
if (ConsumedError(ValidateErrorFilter(filter))) {
return;
}
mCurrentErrorScope = AcquireRef(new ErrorScope(filter, mCurrentErrorScope.Get()));
}
bool DeviceBase::PopErrorScope(dawn::ErrorCallback callback, void* userdata) {
// TODO(crbug.com/dawn/153): Implement error scopes.
HandleError(dawn::ErrorType::Validation, "Error scopes not implemented");
return false;
if (DAWN_UNLIKELY(mCurrentErrorScope.Get() == mRootErrorScope.Get())) {
return false;
}
mCurrentErrorScope->SetCallback(callback, userdata);
mCurrentErrorScope = Ref<ErrorScope>(mCurrentErrorScope->GetParent());
return true;
}
MaybeError DeviceBase::ValidateObject(const ObjectBase* object) const {
@ -286,8 +297,7 @@ namespace dawn_native {
return static_cast<AttachmentState*>(*iter);
}
Ref<AttachmentState> attachmentState = new AttachmentState(this, *blueprint);
attachmentState->Release();
Ref<AttachmentState> attachmentState = AcquireRef(new AttachmentState(this, *blueprint));
mCaches->attachmentStates.insert(attachmentState.Get());
return attachmentState;
}
@ -680,12 +690,6 @@ namespace dawn_native {
// Other implementation details
void DeviceBase::ConsumeError(ErrorData* error) {
ASSERT(error != nullptr);
HandleError(error->GetType(), error->GetMessage().c_str());
delete error;
}
ResultOrError<DynamicUploader*> DeviceBase::GetDynamicUploader() const {
if (mDynamicUploader->IsEmpty()) {
DAWN_TRY(mDynamicUploader->CreateAndAppendBuffer());

View File

@ -35,6 +35,7 @@ namespace dawn_native {
class AdapterBase;
class AttachmentState;
class AttachmentStateBlueprint;
class ErrorScope;
class FenceSignalTracker;
class DynamicUploader;
class StagingBufferBase;
@ -45,10 +46,11 @@ namespace dawn_native {
virtual ~DeviceBase();
void HandleError(dawn::ErrorType type, const char* message);
void HandleError(ErrorData* error);
bool ConsumedError(MaybeError maybeError) {
if (DAWN_UNLIKELY(maybeError.IsError())) {
ConsumeError(maybeError.AcquireError());
HandleError(maybeError.AcquireError());
return true;
}
return false;
@ -230,11 +232,13 @@ namespace dawn_native {
void ApplyExtensions(const DeviceDescriptor* deviceDescriptor);
void ConsumeError(ErrorData* error);
void SetDefaultToggles();
AdapterBase* mAdapter = nullptr;
Ref<ErrorScope> mRootErrorScope;
Ref<ErrorScope> mCurrentErrorScope;
// The object caches aren't exposed in the header as they would require a lot of
// additional includes.
struct Caches;
@ -250,8 +254,6 @@ namespace dawn_native {
std::unique_ptr<FenceSignalTracker> mFenceSignalTracker;
std::vector<DeferredCreateBufferMappedAsync> mDeferredCreateBufferMappedAsyncResults;
dawn::ErrorCallback mErrorCallback = nullptr;
void* mErrorUserdata = 0;
uint32_t mRefCount = 1;
FormatTable mFormatTable;

View File

@ -0,0 +1,114 @@
// 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/ErrorScope.h"
#include "common/Assert.h"
#include "dawn_native/ErrorData.h"
namespace dawn_native {
ErrorScope::ErrorScope() = default;
ErrorScope::ErrorScope(dawn::ErrorFilter errorFilter, ErrorScope* parent)
: RefCounted(), mErrorFilter(errorFilter), mParent(parent) {
ASSERT(mParent.Get() != nullptr);
}
ErrorScope::~ErrorScope() {
if (mCallback == nullptr || IsRoot()) {
return;
}
mCallback(static_cast<DawnErrorType>(mErrorType), mErrorMessage.c_str(), mUserdata);
}
void ErrorScope::SetCallback(dawn::ErrorCallback callback, void* userdata) {
mCallback = callback;
mUserdata = userdata;
}
ErrorScope* ErrorScope::GetParent() {
return mParent.Get();
}
bool ErrorScope::IsRoot() const {
return mParent.Get() == nullptr;
}
void ErrorScope::HandleError(dawn::ErrorType type, const char* message) {
HandleErrorImpl(this, type, message);
}
void ErrorScope::HandleError(ErrorData* error) {
ASSERT(error != nullptr);
HandleErrorImpl(this, error->GetType(), error->GetMessage().c_str());
}
// static
void ErrorScope::HandleErrorImpl(ErrorScope* scope, dawn::ErrorType type, const char* message) {
ErrorScope* currentScope = scope;
for (; !currentScope->IsRoot(); currentScope = currentScope->GetParent()) {
ASSERT(currentScope != nullptr);
bool consumed = false;
switch (type) {
case dawn::ErrorType::Validation:
if (currentScope->mErrorFilter != dawn::ErrorFilter::Validation) {
// Error filter does not match. Move on to the next scope.
continue;
}
consumed = true;
break;
case dawn::ErrorType::OutOfMemory:
if (currentScope->mErrorFilter != dawn::ErrorFilter::OutOfMemory) {
// Error filter does not match. Move on to the next scope.
continue;
}
consumed = true;
break;
// Unknown and DeviceLost are fatal. All error scopes capture them.
// |consumed| is false because these should bubble to all scopes.
case dawn::ErrorType::Unknown:
case dawn::ErrorType::DeviceLost:
consumed = false;
break;
case dawn::ErrorType::NoError:
default:
UNREACHABLE();
return;
}
// Record the error if the scope doesn't have one yet.
if (currentScope->mErrorType == dawn::ErrorType::NoError) {
currentScope->mErrorType = type;
currentScope->mErrorMessage = message;
}
if (consumed) {
return;
}
}
// The root error scope captures all uncaptured errors.
ASSERT(currentScope->IsRoot());
if (currentScope->mCallback) {
currentScope->mCallback(static_cast<DawnErrorType>(type), message,
currentScope->mUserdata);
}
}
} // namespace dawn_native

View File

@ -0,0 +1,68 @@
// 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_ERRORSCOPE_H_
#define DAWNNATIVE_ERRORSCOPE_H_
#include "dawn_native/dawn_platform.h"
#include "dawn_native/RefCounted.h"
#include <string>
namespace dawn_native {
class ErrorData;
// 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 : public RefCounted {
public:
ErrorScope(); // Constructor for the root error scope.
ErrorScope(dawn::ErrorFilter errorFilter, ErrorScope* parent);
~ErrorScope();
void SetCallback(dawn::ErrorCallback callback, void* userdata);
ErrorScope* GetParent();
void HandleError(dawn::ErrorType type, const char* message);
void HandleError(ErrorData* error);
private:
bool IsRoot() const;
static void HandleErrorImpl(ErrorScope* scope, dawn::ErrorType type, const char* message);
dawn::ErrorFilter mErrorFilter = dawn::ErrorFilter::None;
Ref<ErrorScope> mParent = nullptr;
dawn::ErrorCallback mCallback = nullptr;
void* mUserdata = nullptr;
dawn::ErrorType mErrorType = dawn::ErrorType::NoError;
std::string mErrorMessage = "";
};
} // namespace dawn_native
#endif // DAWNNATIVE_ERRORSCOPE_H_

View File

@ -120,6 +120,13 @@ namespace dawn_native {
T* mPointee = nullptr;
};
template <typename T>
Ref<T> AcquireRef(T* pointee) {
Ref<T> ref(pointee);
ref->Release();
return ref;
}
} // namespace dawn_native
#endif // DAWNNATIVE_REFCOUNTED_H_

View File

@ -330,12 +330,7 @@ namespace dawn_native { namespace vulkan {
tempBufferDescriptor.usage = dawn::BufferUsage::CopySrc | dawn::BufferUsage::CopyDst;
Device* device = ToBackend(GetDevice());
Ref<Buffer> tempBuffer = ToBackend(device->CreateBuffer(&tempBufferDescriptor));
// After device->CreateBuffer(&tempBufferDescriptor) is called, the ref count of the buffer
// object is 1, and after assigning it to a Ref<Buffer>, the ref count of it will be 2. To
// prevent memory leak, we must reduce the ref count here to ensure the ref count of this
// object to be 0 after all the Ref<> objects that contain the buffer object are released.
tempBuffer->Release();
Ref<Buffer> tempBuffer = AcquireRef(ToBackend(device->CreateBuffer(&tempBufferDescriptor)));
BufferCopy tempBufferCopy;
tempBufferCopy.buffer = tempBuffer.Get();

View File

@ -0,0 +1,134 @@
// 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 "tests/unittests/validation/ValidationTest.h"
#include <gmock/gmock.h>
using namespace testing;
class MockDevicePopErrorScopeCallback {
public:
MOCK_METHOD3(Call, void(DawnErrorType type, const char* message, void* userdata));
};
static std::unique_ptr<MockDevicePopErrorScopeCallback> mockDevicePopErrorScopeCallback;
static void ToMockDevicePopErrorScopeCallback(DawnErrorType type,
const char* message,
void* userdata) {
mockDevicePopErrorScopeCallback->Call(type, message, userdata);
}
class ErrorScopeValidationTest : public ValidationTest {
private:
void SetUp() override {
ValidationTest::SetUp();
mockDevicePopErrorScopeCallback = std::make_unique<MockDevicePopErrorScopeCallback>();
}
void TearDown() override {
// Delete mocks so that expectations are checked
mockDevicePopErrorScopeCallback = nullptr;
ValidationTest::TearDown();
}
};
// Test the simple success case.
TEST_F(ErrorScopeValidationTest, Success) {
device.PushErrorScope(dawn::ErrorFilter::Validation);
EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_NO_ERROR, _, this)).Times(1);
device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this);
}
// Test the simple case where the error scope catches an error.
TEST_F(ErrorScopeValidationTest, CatchesError) {
device.PushErrorScope(dawn::ErrorFilter::Validation);
dawn::BufferDescriptor desc = {};
desc.usage = static_cast<dawn::BufferUsage>(DAWN_BUFFER_USAGE_FORCE32);
device.CreateBuffer(&desc);
EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_VALIDATION, _, this))
.Times(1);
device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this);
}
// Test that errors bubble to the parent scope if not handled by the current scope.
TEST_F(ErrorScopeValidationTest, ErrorBubbles) {
device.PushErrorScope(dawn::ErrorFilter::Validation);
device.PushErrorScope(dawn::ErrorFilter::OutOfMemory);
dawn::BufferDescriptor desc = {};
desc.usage = static_cast<dawn::BufferUsage>(DAWN_BUFFER_USAGE_FORCE32);
device.CreateBuffer(&desc);
// OutOfMemory does not match Validation error.
EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_NO_ERROR, _, this)).Times(1);
device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this);
// Parent validation error scope captures the error.
EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_VALIDATION, _, this + 1))
.Times(1);
device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this + 1);
}
// Test that if an error scope matches an error, it does not bubble to the parent scope.
TEST_F(ErrorScopeValidationTest, HandledErrorsStopBubbling) {
device.PushErrorScope(dawn::ErrorFilter::OutOfMemory);
device.PushErrorScope(dawn::ErrorFilter::Validation);
dawn::BufferDescriptor desc = {};
desc.usage = static_cast<dawn::BufferUsage>(DAWN_BUFFER_USAGE_FORCE32);
device.CreateBuffer(&desc);
// Inner scope catches the error.
EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_VALIDATION, _, this))
.Times(1);
device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this);
// Parent scope does not see the error.
EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_NO_ERROR, _, this + 1))
.Times(1);
device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this + 1);
}
// Test that if no error scope handles an error, it goes to the device UncapturedError callback
TEST_F(ErrorScopeValidationTest, UnhandledErrorsMatchUncapturedErrorCallback) {
device.PushErrorScope(dawn::ErrorFilter::OutOfMemory);
dawn::BufferDescriptor desc = {};
desc.usage = static_cast<dawn::BufferUsage>(DAWN_BUFFER_USAGE_FORCE32);
ASSERT_DEVICE_ERROR(device.CreateBuffer(&desc));
EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_NO_ERROR, _, this)).Times(1);
device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this);
}
// Check that push/popping error scopes must be balanced.
TEST_F(ErrorScopeValidationTest, PushPopBalanced) {
// No error scopes to pop.
{ EXPECT_FALSE(device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this)); }
// Too many pops
{
device.PushErrorScope(dawn::ErrorFilter::Validation);
EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_NO_ERROR, _, this + 1))
.Times(1);
device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this + 1);
EXPECT_FALSE(device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this + 2));
}
}