Adds error promoting to device loss when disallowed error occurs in a scope.

- Defaults consume error calls to only allow validation and device loss
  errors.
- Allows OOM errors on Buffers, QuerySets, and Textures only.
- Adds initial suite of unit tests (and any necessary updates to mock
  framework).

Bug: dawn:1336
Change-Id: I82112ea6c147e894280e605bf8ae0ce00488c9f3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/119800
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Loko Kung <lokokung@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
Loko Kung 2023-02-28 04:34:32 +00:00 committed by Dawn LUCI CQ
parent 8812918fa4
commit 53893a3d77
17 changed files with 574 additions and 64 deletions

View File

@ -209,6 +209,7 @@ DeviceBase::DeviceBase(AdapterBase* adapter,
} }
DeviceBase::DeviceBase() : mState(State::Alive), mToggles(ToggleStage::Device) { DeviceBase::DeviceBase() : mState(State::Alive), mToggles(ToggleStage::Device) {
GetDefaultLimits(&mLimits.v1);
mFormatTable = BuildFormatTable(this); mFormatTable = BuildFormatTable(this);
} }
@ -465,9 +466,12 @@ void DeviceBase::APIDestroy() {
Destroy(); Destroy();
} }
void DeviceBase::HandleError(InternalErrorType type, void DeviceBase::HandleError(std::unique_ptr<ErrorData> error,
const char* message, InternalErrorType additionalAllowedErrors,
WGPUDeviceLostReason lost_reason) { WGPUDeviceLostReason lost_reason) {
InternalErrorType allowedErrors =
InternalErrorType::Validation | InternalErrorType::DeviceLost | additionalAllowedErrors;
InternalErrorType type = error->GetType();
if (type == InternalErrorType::DeviceLost) { if (type == InternalErrorType::DeviceLost) {
mState = State::Disconnected; mState = State::Disconnected;
@ -481,10 +485,12 @@ void DeviceBase::HandleError(InternalErrorType type,
// A real device lost happened. Set the state to disconnected as the device cannot be // A real device lost happened. Set the state to disconnected as the device cannot be
// used. Also tags all commands as completed since the device stopped running. // used. Also tags all commands as completed since the device stopped running.
AssumeCommandsComplete(); AssumeCommandsComplete();
} else if (type == InternalErrorType::Internal) { } else if (!(allowedErrors & type)) {
// If we receive an internal error, assume the backend can't recover and proceed with // If we receive an error which we did not explicitly allow, assume the backend can't
// device destruction. We first wait for all previous commands to be completed so that // recover and proceed with device destruction. We first wait for all previous commands to
// backend objects can be freed immediately, before handling the loss. // be completed so that backend objects can be freed immediately, before handling the loss.
error->AppendContext("handling unexpected error type %s when allowed errors are %s.", type,
allowedErrors);
// Move away from the Alive state so that the application cannot use this device // Move away from the Alive state so that the application cannot use this device
// anymore. // anymore.
@ -503,6 +509,9 @@ void DeviceBase::HandleError(InternalErrorType type,
type = InternalErrorType::DeviceLost; type = InternalErrorType::DeviceLost;
} }
// TODO(lokokung) Update call sites that take the c-string to take string_view.
const std::string messageStr = error->GetFormattedMessage();
const char* message = messageStr.c_str();
if (type == InternalErrorType::DeviceLost) { if (type == InternalErrorType::DeviceLost) {
// The device was lost, call the application callback. // The device was lost, call the application callback.
if (mDeviceLostCallback != nullptr) { if (mDeviceLostCallback != nullptr) {
@ -533,10 +542,11 @@ void DeviceBase::HandleError(InternalErrorType type,
} }
} }
void DeviceBase::ConsumeError(std::unique_ptr<ErrorData> error) { void DeviceBase::ConsumeError(std::unique_ptr<ErrorData> error,
InternalErrorType additionalAllowedErrors) {
ASSERT(error != nullptr); ASSERT(error != nullptr);
AppendDebugLayerMessages(error.get()); AppendDebugLayerMessages(error.get());
HandleError(error->GetType(), error->GetFormattedMessage().c_str()); HandleError(std::move(error), additionalAllowedErrors);
} }
void DeviceBase::APISetLoggingCallback(wgpu::LoggingCallback callback, void* userdata) { void DeviceBase::APISetLoggingCallback(wgpu::LoggingCallback callback, void* userdata) {
@ -651,7 +661,10 @@ void DeviceBase::APIForceLoss(wgpu::DeviceLostReason reason, const char* message
if (mState != State::Alive) { if (mState != State::Alive) {
return; return;
} }
HandleError(InternalErrorType::Internal, message, ToAPI(reason)); // Note that since we are passing None as the allowedErrors, an additional message will be
// appended noting that the error was unexpected. Since this call is for testing only it is not
// too important, but useful for users to understand where the extra message is coming from.
HandleError(DAWN_INTERNAL_ERROR(message), InternalErrorType::None, ToAPI(reason));
} }
DeviceBase::State DeviceBase::GetState() const { DeviceBase::State DeviceBase::GetState() const {
@ -1033,8 +1046,8 @@ BindGroupLayoutBase* DeviceBase::APICreateBindGroupLayout(
} }
BufferBase* DeviceBase::APICreateBuffer(const BufferDescriptor* descriptor) { BufferBase* DeviceBase::APICreateBuffer(const BufferDescriptor* descriptor) {
Ref<BufferBase> result = nullptr; Ref<BufferBase> result = nullptr;
if (ConsumedError(CreateBuffer(descriptor), &result, "calling %s.CreateBuffer(%s).", this, if (ConsumedError(CreateBuffer(descriptor), &result, InternalErrorType::OutOfMemory,
descriptor)) { "calling %s.CreateBuffer(%s).", this, descriptor)) {
ASSERT(result == nullptr); ASSERT(result == nullptr);
return BufferBase::MakeError(this, descriptor); return BufferBase::MakeError(this, descriptor);
} }
@ -1090,8 +1103,8 @@ PipelineLayoutBase* DeviceBase::APICreatePipelineLayout(
} }
QuerySetBase* DeviceBase::APICreateQuerySet(const QuerySetDescriptor* descriptor) { QuerySetBase* DeviceBase::APICreateQuerySet(const QuerySetDescriptor* descriptor) {
Ref<QuerySetBase> result; Ref<QuerySetBase> result;
if (ConsumedError(CreateQuerySet(descriptor), &result, "calling %s.CreateQuerySet(%s).", this, if (ConsumedError(CreateQuerySet(descriptor), &result, InternalErrorType::OutOfMemory,
descriptor)) { "calling %s.CreateQuerySet(%s).", this, descriptor)) {
return QuerySetBase::MakeError(this, descriptor); return QuerySetBase::MakeError(this, descriptor);
} }
return result.Detach(); return result.Detach();
@ -1174,8 +1187,8 @@ SwapChainBase* DeviceBase::APICreateSwapChain(Surface* surface,
} }
TextureBase* DeviceBase::APICreateTexture(const TextureDescriptor* descriptor) { TextureBase* DeviceBase::APICreateTexture(const TextureDescriptor* descriptor) {
Ref<TextureBase> result; Ref<TextureBase> result;
if (ConsumedError(CreateTexture(descriptor), &result, "calling %s.CreateTexture(%s).", this, if (ConsumedError(CreateTexture(descriptor), &result, InternalErrorType::OutOfMemory,
descriptor)) { "calling %s.CreateTexture(%s).", this, descriptor)) {
return TextureBase::MakeError(this, descriptor); return TextureBase::MakeError(this, descriptor);
} }
return result.Detach(); return result.Detach();
@ -1191,12 +1204,14 @@ BufferBase* DeviceBase::APICreateErrorBuffer(const BufferDescriptor* desc) {
// MapppedAtCreation == false. // MapppedAtCreation == false.
MaybeError maybeError = ValidateBufferDescriptor(this, &fakeDescriptor); MaybeError maybeError = ValidateBufferDescriptor(this, &fakeDescriptor);
if (maybeError.IsError()) { if (maybeError.IsError()) {
ConsumedError(maybeError.AcquireError(), "calling %s.CreateBuffer(%s).", this, desc); ConsumedError(maybeError.AcquireError(), InternalErrorType::OutOfMemory,
"calling %s.CreateBuffer(%s).", this, desc);
} else { } else {
const DawnBufferDescriptorErrorInfoFromWireClient* clientErrorInfo = nullptr; const DawnBufferDescriptorErrorInfoFromWireClient* clientErrorInfo = nullptr;
FindInChain(desc->nextInChain, &clientErrorInfo); FindInChain(desc->nextInChain, &clientErrorInfo);
if (clientErrorInfo != nullptr && clientErrorInfo->outOfMemory) { if (clientErrorInfo != nullptr && clientErrorInfo->outOfMemory) {
ConsumedError(DAWN_OUT_OF_MEMORY_ERROR("Failed to allocate memory for buffer mapping")); ConsumedError(DAWN_OUT_OF_MEMORY_ERROR("Failed to allocate memory for buffer mapping"),
InternalErrorType::OutOfMemory);
} }
} }
@ -1377,12 +1392,12 @@ void DeviceBase::APIInjectError(wgpu::ErrorType type, const char* message) {
// This method should only be used to make error scope reject. For DeviceLost there is the // This method should only be used to make error scope reject. For DeviceLost there is the
// LoseForTesting function that can be used instead. // LoseForTesting function that can be used instead.
if (type != wgpu::ErrorType::Validation && type != wgpu::ErrorType::OutOfMemory) { if (type != wgpu::ErrorType::Validation && type != wgpu::ErrorType::OutOfMemory) {
HandleError(InternalErrorType::Validation, HandleError(
"Invalid injected error, must be Validation or OutOfMemory"); DAWN_VALIDATION_ERROR("Invalid injected error, must be Validation or OutOfMemory"));
return; return;
} }
HandleError(FromWGPUErrorType(type), message); HandleError(DAWN_MAKE_ERROR(FromWGPUErrorType(type), message), InternalErrorType::OutOfMemory);
} }
void DeviceBase::APIValidateTextureDescriptor(const TextureDescriptor* desc) { void DeviceBase::APIValidateTextureDescriptor(const TextureDescriptor* desc) {

View File

@ -69,23 +69,28 @@ class DeviceBase : public RefCountedWithExternalCount {
// Handles the error, causing a device loss if applicable. Almost always when a device loss // Handles the error, causing a device loss if applicable. Almost always when a device loss
// occurs because of an error, we want to call the device loss callback with an undefined // occurs because of an error, we want to call the device loss callback with an undefined
// reason, but the ForceLoss API allows for an injection of the reason, hence the default // reason, but the ForceLoss API allows for an injection of the reason, hence the default
// argument. // argument. The `additionalAllowedErrors` mask allows specifying additional errors are allowed
void HandleError(InternalErrorType type, // (on top of validation and device loss errors). Note that "allowed" is defined as surfacing to
const char* message, // users as the respective error rather than causing a device loss instead.
void HandleError(std::unique_ptr<ErrorData> error,
InternalErrorType additionalAllowedErrors = InternalErrorType::None,
WGPUDeviceLostReason lost_reason = WGPUDeviceLostReason_Undefined); WGPUDeviceLostReason lost_reason = WGPUDeviceLostReason_Undefined);
bool ConsumedError(MaybeError maybeError) { bool ConsumedError(MaybeError maybeError,
InternalErrorType additionalAllowedErrors = InternalErrorType::None) {
if (DAWN_UNLIKELY(maybeError.IsError())) { if (DAWN_UNLIKELY(maybeError.IsError())) {
ConsumeError(maybeError.AcquireError()); ConsumeError(maybeError.AcquireError(), additionalAllowedErrors);
return true; return true;
} }
return false; return false;
} }
template <typename T> template <typename T>
bool ConsumedError(ResultOrError<T> resultOrError, T* result) { bool ConsumedError(ResultOrError<T> resultOrError,
T* result,
InternalErrorType additionalAllowedErrors = InternalErrorType::None) {
if (DAWN_UNLIKELY(resultOrError.IsError())) { if (DAWN_UNLIKELY(resultOrError.IsError())) {
ConsumeError(resultOrError.AcquireError()); ConsumeError(resultOrError.AcquireError(), additionalAllowedErrors);
return true; return true;
} }
*result = resultOrError.AcquireSuccess(); *result = resultOrError.AcquireSuccess();
@ -93,47 +98,52 @@ class DeviceBase : public RefCountedWithExternalCount {
} }
template <typename... Args> template <typename... Args>
bool ConsumedError(MaybeError maybeError, const char* formatStr, const Args&... args) { bool ConsumedError(MaybeError maybeError,
InternalErrorType additionalAllowedErrors,
const char* formatStr,
const Args&... args) {
if (DAWN_UNLIKELY(maybeError.IsError())) { if (DAWN_UNLIKELY(maybeError.IsError())) {
std::unique_ptr<ErrorData> error = maybeError.AcquireError(); std::unique_ptr<ErrorData> error = maybeError.AcquireError();
if (error->GetType() == InternalErrorType::Validation) { if (error->GetType() == InternalErrorType::Validation) {
std::string out; error->AppendContext(formatStr, args...);
absl::UntypedFormatSpec format(formatStr);
if (absl::FormatUntyped(&out, format, {absl::FormatArg(args)...})) {
error->AppendContext(std::move(out));
} else {
error->AppendContext(
absl::StrFormat("[Failed to format error: \"%s\"]", formatStr));
} }
} ConsumeError(std::move(error), additionalAllowedErrors);
ConsumeError(std::move(error));
return true; return true;
} }
return false; return false;
} }
template <typename... Args>
bool ConsumedError(MaybeError maybeError, const char* formatStr, Args&&... args) {
return ConsumedError(std::move(maybeError), InternalErrorType::None, formatStr,
std::forward<Args>(args)...);
}
template <typename T, typename... Args>
bool ConsumedError(ResultOrError<T> resultOrError,
T* result,
InternalErrorType additionalAllowedErrors,
const char* formatStr,
const Args&... args) {
if (DAWN_UNLIKELY(resultOrError.IsError())) {
std::unique_ptr<ErrorData> error = resultOrError.AcquireError();
if (error->GetType() == InternalErrorType::Validation) {
error->AppendContext(formatStr, args...);
}
ConsumeError(std::move(error), additionalAllowedErrors);
return true;
}
*result = resultOrError.AcquireSuccess();
return false;
}
template <typename T, typename... Args> template <typename T, typename... Args>
bool ConsumedError(ResultOrError<T> resultOrError, bool ConsumedError(ResultOrError<T> resultOrError,
T* result, T* result,
const char* formatStr, const char* formatStr,
const Args&... args) { Args&&... args) {
if (DAWN_UNLIKELY(resultOrError.IsError())) { return ConsumedError(std::move(resultOrError), result, InternalErrorType::None, formatStr,
std::unique_ptr<ErrorData> error = resultOrError.AcquireError(); std::forward<Args>(args)...);
if (error->GetType() == InternalErrorType::Validation) {
std::string out;
absl::UntypedFormatSpec format(formatStr);
if (absl::FormatUntyped(&out, format, {absl::FormatArg(args)...})) {
error->AppendContext(std::move(out));
} else {
error->AppendContext(
absl::StrFormat("[Failed to format error: \"%s\"]", formatStr));
}
}
ConsumeError(std::move(error));
return true;
}
*result = resultOrError.AcquireSuccess();
return false;
} }
MaybeError ValidateObject(const ApiObjectBase* object) const; MaybeError ValidateObject(const ApiObjectBase* object) const;
@ -489,7 +499,8 @@ class DeviceBase : public RefCountedWithExternalCount {
void SetWGSLExtensionAllowList(); void SetWGSLExtensionAllowList();
void ConsumeError(std::unique_ptr<ErrorData> error); void ConsumeError(std::unique_ptr<ErrorData> error,
InternalErrorType additionalAllowedErrors = InternalErrorType::None);
// 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.

View File

@ -87,7 +87,7 @@ void EncodingContext::HandleError(std::unique_ptr<ErrorData> error) {
mError = std::move(error); mError = std::move(error);
} }
} else { } else {
mDevice->HandleError(error->GetType(), error->GetFormattedMessage().c_str()); mDevice->HandleError(std::move(error));
} }
} }

View File

@ -61,4 +61,64 @@ InternalErrorType FromWGPUErrorType(wgpu::ErrorType type) {
} }
} }
absl::FormatConvertResult<absl::FormatConversionCharSet::kString |
absl::FormatConversionCharSet::kIntegral>
AbslFormatConvert(InternalErrorType value,
const absl::FormatConversionSpec& spec,
absl::FormatSink* s) {
if (spec.conversion_char() == absl::FormatConversionChar::s) {
if (!static_cast<bool>(value)) {
s->Append("None");
return {true};
}
bool moreThanOneBit = !HasZeroOrOneBits(value);
if (moreThanOneBit) {
s->Append("(");
}
bool first = true;
if (value & InternalErrorType::Validation) {
if (!first) {
s->Append("|");
}
first = false;
s->Append("Validation");
value &= ~InternalErrorType::Validation;
}
if (value & InternalErrorType::DeviceLost) {
if (!first) {
s->Append("|");
}
first = false;
s->Append("DeviceLost");
value &= ~InternalErrorType::DeviceLost;
}
if (value & InternalErrorType::Internal) {
if (!first) {
s->Append("|");
}
first = false;
s->Append("Internal");
value &= ~InternalErrorType::Internal;
}
if (value & InternalErrorType::OutOfMemory) {
if (!first) {
s->Append("|");
}
first = false;
s->Append("OutOfMemory");
value &= ~InternalErrorType::OutOfMemory;
}
if (moreThanOneBit) {
s->Append(")");
}
} else {
s->Append(absl::StrFormat(
"%u", static_cast<typename std::underlying_type<InternalErrorType>::type>(value)));
}
return {true};
}
} // namespace dawn::native } // namespace dawn::native

View File

@ -19,7 +19,6 @@
#include <string> #include <string>
#include <utility> #include <utility>
#include "absl/strings/str_format.h"
#include "dawn/common/Result.h" #include "dawn/common/Result.h"
#include "dawn/native/ErrorData.h" #include "dawn/native/ErrorData.h"
#include "dawn/native/Toggles.h" #include "dawn/native/Toggles.h"
@ -27,7 +26,13 @@
namespace dawn::native { namespace dawn::native {
enum class InternalErrorType : uint32_t { Validation, DeviceLost, Internal, OutOfMemory }; enum class InternalErrorType : uint32_t {
None = 0,
Validation = 1,
DeviceLost = 2,
Internal = 4,
OutOfMemory = 8
};
// MaybeError and ResultOrError are meant to be used as return value for function that are not // MaybeError and ResultOrError are meant to be used as return value for function that are not
// expected to, but might fail. The handling of error is potentially much slower than successes. // expected to, but might fail. The handling of error is potentially much slower than successes.
@ -204,6 +209,22 @@ void IgnoreErrors(MaybeError maybeError);
wgpu::ErrorType ToWGPUErrorType(InternalErrorType type); wgpu::ErrorType ToWGPUErrorType(InternalErrorType type);
InternalErrorType FromWGPUErrorType(wgpu::ErrorType type); InternalErrorType FromWGPUErrorType(wgpu::ErrorType type);
absl::FormatConvertResult<absl::FormatConversionCharSet::kString |
absl::FormatConversionCharSet::kIntegral>
AbslFormatConvert(InternalErrorType value,
const absl::FormatConversionSpec& spec,
absl::FormatSink* s);
} // namespace dawn::native } // namespace dawn::native
// Enable dawn enum bitmask for error types.
namespace dawn {
template <>
struct IsDawnBitmask<native::InternalErrorType> {
static constexpr bool enable = true;
};
} // namespace dawn
#endif // SRC_DAWN_NATIVE_ERROR_H_ #endif // SRC_DAWN_NATIVE_ERROR_H_

View File

@ -18,8 +18,10 @@
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <string> #include <string>
#include <utility>
#include <vector> #include <vector>
#include "absl/strings/str_format.h"
#include "dawn/common/Compiler.h" #include "dawn/common/Compiler.h"
namespace wgpu { namespace wgpu {
@ -50,6 +52,16 @@ class [[nodiscard]] ErrorData {
}; };
void AppendBacktrace(const char* file, const char* function, int line); void AppendBacktrace(const char* file, const char* function, int line);
void AppendContext(std::string context); void AppendContext(std::string context);
template <typename... Args>
void AppendContext(const char* formatStr, const Args&... args) {
std::string out;
absl::UntypedFormatSpec format(formatStr);
if (absl::FormatUntyped(&out, format, {absl::FormatArg(args)...})) {
AppendContext(std::move(out));
} else {
AppendContext(absl::StrFormat("[Failed to format error: \"%s\"]", formatStr));
}
}
void AppendDebugGroup(std::string label); void AppendDebugGroup(std::string label);
void AppendBackendMessage(std::string message); void AppendBackendMessage(std::string message);

View File

@ -141,6 +141,8 @@ MaybeError ValidateProgrammableStage(DeviceBase* device,
WGPUCreatePipelineAsyncStatus CreatePipelineAsyncStatusFromErrorType(InternalErrorType error) { WGPUCreatePipelineAsyncStatus CreatePipelineAsyncStatusFromErrorType(InternalErrorType error) {
switch (error) { switch (error) {
case InternalErrorType::None:
return WGPUCreatePipelineAsyncStatus_Success;
case InternalErrorType::Validation: case InternalErrorType::Validation:
return WGPUCreatePipelineAsyncStatus_ValidationError; return WGPUCreatePipelineAsyncStatus_ValidationError;
case InternalErrorType::DeviceLost: case InternalErrorType::DeviceLost:

View File

@ -30,7 +30,7 @@ thread_local DeviceBase* tlDevice = nullptr;
void TintICEReporter(const tint::diag::List& diagnostics) { void TintICEReporter(const tint::diag::List& diagnostics) {
if (tlDevice) { if (tlDevice) {
tlDevice->HandleError(InternalErrorType::Internal, diagnostics.str().c_str()); tlDevice->HandleError(DAWN_INTERNAL_ERROR(diagnostics.str()));
#if DAWN_ENABLE_ASSERTS #if DAWN_ENABLE_ASSERTS
for (const tint::diag::Diagnostic& diag : diagnostics) { for (const tint::diag::Diagnostic& diag : diagnostics) {
if (diag.severity >= tint::diag::Severity::InternalCompilerError) { if (diag.severity >= tint::diag::Severity::InternalCompilerError) {

View File

@ -99,7 +99,7 @@ TextureBase* OldSwapChain::GetNextTextureImpl(const TextureDescriptor* descripto
DawnSwapChainNextTexture next = {}; DawnSwapChainNextTexture next = {};
DawnSwapChainError error = im.GetNextTexture(im.userData, &next); DawnSwapChainError error = im.GetNextTexture(im.userData, &next);
if (error) { if (error) {
device->HandleError(InternalErrorType::Internal, error); device->HandleError(DAWN_INTERNAL_ERROR(error));
return nullptr; return nullptr;
} }

View File

@ -47,7 +47,7 @@ TextureBase* OldSwapChain::GetNextTextureImpl(const TextureDescriptor* descripto
DawnSwapChainNextTexture next = {}; DawnSwapChainNextTexture next = {};
DawnSwapChainError error = im.GetNextTexture(im.userData, &next); DawnSwapChainError error = im.GetNextTexture(im.userData, &next);
if (error) { if (error) {
GetDevice()->HandleError(InternalErrorType::Internal, error); GetDevice()->HandleError(DAWN_INTERNAL_ERROR(error));
return nullptr; return nullptr;
} }

View File

@ -35,7 +35,7 @@ TextureBase* SwapChain::GetNextTextureImpl(const TextureDescriptor* descriptor)
DawnSwapChainNextTexture next = {}; DawnSwapChainNextTexture next = {};
DawnSwapChainError error = im.GetNextTexture(im.userData, &next); DawnSwapChainError error = im.GetNextTexture(im.userData, &next);
if (error) { if (error) {
GetDevice()->HandleError(InternalErrorType::Internal, error); GetDevice()->HandleError(DAWN_INTERNAL_ERROR(error));
return nullptr; return nullptr;
} }
GLuint nativeTexture = next.texture.u32; GLuint nativeTexture = next.texture.u32;

View File

@ -59,7 +59,7 @@ TextureBase* OldSwapChain::GetNextTextureImpl(const TextureDescriptor* descripto
DawnSwapChainError error = im.GetNextTexture(im.userData, &next); DawnSwapChainError error = im.GetNextTexture(im.userData, &next);
if (error) { if (error) {
GetDevice()->HandleError(InternalErrorType::Internal, error); GetDevice()->HandleError(DAWN_INTERNAL_ERROR(error));
return nullptr; return nullptr;
} }

View File

@ -309,6 +309,7 @@ dawn_test("dawn_unittests") {
"unittests/ToBackendTests.cpp", "unittests/ToBackendTests.cpp",
"unittests/TypedIntegerTests.cpp", "unittests/TypedIntegerTests.cpp",
"unittests/UnicodeTests.cpp", "unittests/UnicodeTests.cpp",
"unittests/native/AllowedErrorTests.cpp",
"unittests/native/BlobTests.cpp", "unittests/native/BlobTests.cpp",
"unittests/native/CacheRequestTests.cpp", "unittests/native/CacheRequestTests.cpp",
"unittests/native/CommandBufferEncodingTests.cpp", "unittests/native/CommandBufferEncodingTests.cpp",

View File

@ -0,0 +1,367 @@
// Copyright 2023 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 <gtest/gtest.h>
#include <string_view>
#include <utility>
#include "dawn/tests/MockCallback.h"
#include "dawn/webgpu_cpp.h"
#include "mocks/BufferMock.h"
#include "mocks/ComputePipelineMock.h"
#include "mocks/DawnMockTest.h"
#include "mocks/DeviceMock.h"
#include "mocks/ExternalTextureMock.h"
#include "mocks/PipelineLayoutMock.h"
#include "mocks/RenderPipelineMock.h"
#include "mocks/ShaderModuleMock.h"
#include "mocks/TextureMock.h"
namespace dawn::native {
namespace {
using ::testing::_;
using ::testing::ByMove;
using ::testing::HasSubstr;
using ::testing::MockCallback;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::StrictMock;
using ::testing::Test;
static constexpr char kOomErrorMessage[] = "Out of memory error";
static constexpr std::string_view kComputeShader = R"(
@compute @workgroup_size(1) fn main() {}
)";
static constexpr std::string_view kVertexShader = R"(
@vertex fn main() -> @builtin(position) vec4f {
return vec4f(0.0, 0.0, 0.0, 0.0);
}
)";
class AllowedErrorTests : public DawnMockTest {
public:
AllowedErrorTests() : DawnMockTest() {
device.SetDeviceLostCallback(mDeviceLostCb.Callback(), mDeviceLostCb.MakeUserdata(this));
device.SetUncapturedErrorCallback(mDeviceErrorCb.Callback(),
mDeviceErrorCb.MakeUserdata(this));
}
~AllowedErrorTests() override { device = nullptr; }
protected:
// Device mock callbacks used throughout the tests.
StrictMock<MockCallback<wgpu::DeviceLostCallback>> mDeviceLostCb;
StrictMock<MockCallback<wgpu::ErrorCallback>> mDeviceErrorCb;
};
//
// Exercise APIs where OOM errors cause a device lost.
//
TEST_F(AllowedErrorTests, QueueSubmit) {
EXPECT_CALL(*(mDeviceMock->GetQueueMock()), SubmitImpl)
.WillOnce(Return(DAWN_OUT_OF_MEMORY_ERROR(kOomErrorMessage)));
// Expect the device lost because of the error.
EXPECT_CALL(mDeviceLostCb,
Call(WGPUDeviceLostReason_Undefined, HasSubstr(kOomErrorMessage), this))
.Times(1);
device.GetQueue().Submit(0, nullptr);
}
TEST_F(AllowedErrorTests, QueueWriteBuffer) {
BufferDescriptor desc = {};
desc.size = 1;
desc.usage = wgpu::BufferUsage::CopyDst;
BufferMock* bufferMock = new NiceMock<BufferMock>(mDeviceMock, &desc);
wgpu::Buffer buffer = wgpu::Buffer::Acquire(ToAPI(bufferMock));
EXPECT_CALL(*(mDeviceMock->GetQueueMock()), WriteBufferImpl)
.WillOnce(Return(DAWN_OUT_OF_MEMORY_ERROR(kOomErrorMessage)));
// Expect the device lost because of the error.
EXPECT_CALL(mDeviceLostCb,
Call(WGPUDeviceLostReason_Undefined, HasSubstr(kOomErrorMessage), this))
.Times(1);
constexpr uint8_t data = 8;
device.GetQueue().WriteBuffer(buffer, 0, &data, 0);
}
TEST_F(AllowedErrorTests, QueueWriteTexture) {
TextureDescriptor desc = {};
desc.size.width = 1;
desc.size.height = 1;
desc.usage = wgpu::TextureUsage::CopyDst;
desc.format = wgpu::TextureFormat::RGBA8Unorm;
TextureMock* textureMock = new NiceMock<TextureMock>(mDeviceMock, &desc);
wgpu::Texture texture = wgpu::Texture::Acquire(ToAPI(textureMock));
EXPECT_CALL(*(mDeviceMock->GetQueueMock()), WriteTextureImpl)
.WillOnce(Return(DAWN_OUT_OF_MEMORY_ERROR(kOomErrorMessage)));
// Expect the device lost because of the error.
EXPECT_CALL(mDeviceLostCb,
Call(WGPUDeviceLostReason_Undefined, HasSubstr(kOomErrorMessage), this))
.Times(1);
constexpr uint8_t data[] = {1, 2, 4, 8};
wgpu::ImageCopyTexture dest = {};
dest.texture = texture;
wgpu::TextureDataLayout layout = {};
wgpu::Extent3D size = {1, 1};
device.GetQueue().WriteTexture(&dest, &data, 4, &layout, &size);
}
// Even though OOM is allowed in buffer creation, when creating a buffer in internal workaround the
// OOM should be masked as a device loss.
TEST_F(AllowedErrorTests, QueueCopyTextureForBrowserOomBuffer) {
wgpu::TextureDescriptor desc = {};
desc.size = {4, 4};
desc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst |
wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding;
desc.format = wgpu::TextureFormat::RGBA8Unorm;
wgpu::ImageCopyTexture src = {};
src.texture = device.CreateTexture(&desc);
wgpu::ImageCopyTexture dst = {};
dst.texture = device.CreateTexture(&desc);
wgpu::Extent3D size = {4, 4};
wgpu::CopyTextureForBrowserOptions options = {};
// Copying texture for browser internally allocates a buffer which we will cause to fail here.
EXPECT_CALL(*mDeviceMock, CreateBufferImpl)
.WillOnce(Return(DAWN_OUT_OF_MEMORY_ERROR(kOomErrorMessage)));
// Expect the device lost because of the error.
EXPECT_CALL(mDeviceLostCb,
Call(WGPUDeviceLostReason_Undefined, HasSubstr(kOomErrorMessage), this))
.Times(1);
device.GetQueue().CopyTextureForBrowser(&src, &dst, &size, &options);
}
// Even though OOM is allowed in buffer creation, when creating a buffer in internal workaround the
// OOM should be masked as a device loss.
TEST_F(AllowedErrorTests, QueueCopyExternalTextureForBrowserOomBuffer) {
wgpu::TextureDescriptor textureDesc = {};
textureDesc.size = {4, 4};
textureDesc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst |
wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding;
textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
wgpu::TextureViewDescriptor textureViewDesc = {};
textureViewDesc.format = wgpu::TextureFormat::RGBA8Unorm;
wgpu::ExternalTextureDescriptor externalTextureDesc = {};
std::array<float, 12> placeholderConstantArray;
externalTextureDesc.yuvToRgbConversionMatrix = placeholderConstantArray.data();
externalTextureDesc.gamutConversionMatrix = placeholderConstantArray.data();
externalTextureDesc.srcTransferFunctionParameters = placeholderConstantArray.data();
externalTextureDesc.dstTransferFunctionParameters = placeholderConstantArray.data();
externalTextureDesc.visibleSize = {4, 4};
externalTextureDesc.plane0 = device.CreateTexture(&textureDesc).CreateView(&textureViewDesc);
wgpu::ImageCopyExternalTexture src = {};
src.externalTexture = device.CreateExternalTexture(&externalTextureDesc);
wgpu::ImageCopyTexture dst = {};
dst.texture = device.CreateTexture(&textureDesc);
wgpu::Extent3D size = {4, 4};
wgpu::CopyTextureForBrowserOptions options = {};
// Copying texture for browser internally allocates a buffer which we will cause to fail here.
EXPECT_CALL(*mDeviceMock, CreateBufferImpl)
.WillOnce(Return(DAWN_OUT_OF_MEMORY_ERROR(kOomErrorMessage)));
// Expect the device lost because of the error.
EXPECT_CALL(mDeviceLostCb,
Call(WGPUDeviceLostReason_Undefined, HasSubstr(kOomErrorMessage), this))
.Times(1);
device.GetQueue().CopyExternalTextureForBrowser(&src, &dst, &size, &options);
}
// OOM error from synchronously initializing a compute pipeline should result in a device loss.
TEST_F(AllowedErrorTests, CreateComputePipeline) {
Ref<ShaderModuleMock> csModule = ShaderModuleMock::Create(mDeviceMock, kComputeShader.data());
ComputePipelineDescriptor desc = {};
desc.compute.module = csModule.Get();
desc.compute.entryPoint = "main";
Ref<ComputePipelineMock> computePipelineMock = ComputePipelineMock::Create(mDeviceMock, &desc);
EXPECT_CALL(*computePipelineMock.Get(), Initialize)
.WillOnce(Return(DAWN_OUT_OF_MEMORY_ERROR(kOomErrorMessage)));
EXPECT_CALL(*mDeviceMock, CreateUninitializedComputePipelineImpl)
.WillOnce(Return(ByMove(std::move(computePipelineMock))));
// Expect the device lost because of the error.
EXPECT_CALL(mDeviceLostCb,
Call(WGPUDeviceLostReason_Undefined, HasSubstr(kOomErrorMessage), this))
.Times(1);
device.CreateComputePipeline(ToCppAPI(&desc));
}
// OOM error from synchronously initializing a render pipeline should result in a device loss.
TEST_F(AllowedErrorTests, CreateRenderPipeline) {
Ref<ShaderModuleMock> vsModule = ShaderModuleMock::Create(mDeviceMock, kVertexShader.data());
RenderPipelineDescriptor desc = {};
desc.vertex.module = vsModule.Get();
desc.vertex.entryPoint = "main";
Ref<RenderPipelineMock> renderPipelineMock = RenderPipelineMock::Create(mDeviceMock, &desc);
EXPECT_CALL(*renderPipelineMock.Get(), Initialize)
.WillOnce(Return(DAWN_OUT_OF_MEMORY_ERROR(kOomErrorMessage)));
EXPECT_CALL(*mDeviceMock, CreateUninitializedRenderPipelineImpl)
.WillOnce(Return(ByMove(std::move(renderPipelineMock))));
// Expect the device lost because of the error.
EXPECT_CALL(mDeviceLostCb,
Call(WGPUDeviceLostReason_Undefined, HasSubstr(kOomErrorMessage), this))
.Times(1);
device.CreateRenderPipeline(ToCppAPI(&desc));
}
//
// Exercise async APIs where OOM errors do NOT currently cause a device lost.
//
// OOM error from asynchronously initializing a compute pipeline should not result in a device loss.
TEST_F(AllowedErrorTests, CreateComputePipelineAsync) {
Ref<ShaderModuleMock> csModule = ShaderModuleMock::Create(mDeviceMock, kComputeShader.data());
ComputePipelineDescriptor desc = {};
desc.compute.module = csModule.Get();
desc.compute.entryPoint = "main";
Ref<ComputePipelineMock> computePipelineMock = ComputePipelineMock::Create(mDeviceMock, &desc);
EXPECT_CALL(*computePipelineMock.Get(), Initialize)
.WillOnce(Return(DAWN_OUT_OF_MEMORY_ERROR(kOomErrorMessage)));
EXPECT_CALL(*mDeviceMock, CreateUninitializedComputePipelineImpl)
.WillOnce(Return(ByMove(std::move(computePipelineMock))));
MockCallback<wgpu::CreateComputePipelineAsyncCallback> cb;
EXPECT_CALL(
cb, Call(WGPUCreatePipelineAsyncStatus_InternalError, _, HasSubstr(kOomErrorMessage), this))
.Times(1);
device.CreateComputePipelineAsync(ToCppAPI(&desc), cb.Callback(), cb.MakeUserdata(this));
device.Tick();
// Device lost should only happen because of destruction.
EXPECT_CALL(mDeviceLostCb, Call(WGPUDeviceLostReason_Destroyed, _, this)).Times(1);
}
// OOM error from asynchronously initializing a render pipeline should not result in a device loss.
TEST_F(AllowedErrorTests, CreateRenderPipelineAsync) {
Ref<ShaderModuleMock> vsModule = ShaderModuleMock::Create(mDeviceMock, kVertexShader.data());
RenderPipelineDescriptor desc = {};
desc.vertex.module = vsModule.Get();
desc.vertex.entryPoint = "main";
Ref<RenderPipelineMock> renderPipelineMock = RenderPipelineMock::Create(mDeviceMock, &desc);
EXPECT_CALL(*renderPipelineMock.Get(), Initialize)
.WillOnce(Return(DAWN_OUT_OF_MEMORY_ERROR(kOomErrorMessage)));
EXPECT_CALL(*mDeviceMock, CreateUninitializedRenderPipelineImpl)
.WillOnce(Return(ByMove(std::move(renderPipelineMock))));
MockCallback<wgpu::CreateRenderPipelineAsyncCallback> cb;
EXPECT_CALL(
cb, Call(WGPUCreatePipelineAsyncStatus_InternalError, _, HasSubstr(kOomErrorMessage), this))
.Times(1);
device.CreateRenderPipelineAsync(ToCppAPI(&desc), cb.Callback(), cb.MakeUserdata(this));
device.Tick();
// Device lost should only happen because of destruction.
EXPECT_CALL(mDeviceLostCb, Call(WGPUDeviceLostReason_Destroyed, _, this)).Times(1);
}
//
// Exercise APIs where OOM error are allowed and surfaced.
//
// OOM error from buffer creation is allowed and surfaced directly.
TEST_F(AllowedErrorTests, CreateBuffer) {
EXPECT_CALL(*mDeviceMock, CreateBufferImpl)
.WillOnce(Return(DAWN_OUT_OF_MEMORY_ERROR(kOomErrorMessage)));
// Expect the OOM error.
EXPECT_CALL(mDeviceErrorCb, Call(WGPUErrorType_OutOfMemory, HasSubstr(kOomErrorMessage), this))
.Times(1);
wgpu::BufferDescriptor desc = {};
desc.usage = wgpu::BufferUsage::Uniform;
desc.size = 16;
device.CreateBuffer(&desc);
// Device lost should only happen because of destruction.
EXPECT_CALL(mDeviceLostCb, Call(WGPUDeviceLostReason_Destroyed, _, this)).Times(1);
}
// OOM error from texture creation is allowed and surfaced directly.
TEST_F(AllowedErrorTests, CreateTexture) {
EXPECT_CALL(*mDeviceMock, CreateTextureImpl)
.WillOnce(Return(DAWN_OUT_OF_MEMORY_ERROR(kOomErrorMessage)));
// Expect the OOM error.
EXPECT_CALL(mDeviceErrorCb, Call(WGPUErrorType_OutOfMemory, HasSubstr(kOomErrorMessage), this))
.Times(1);
wgpu::TextureDescriptor desc = {};
desc.usage = wgpu::TextureUsage::CopySrc;
desc.size = {4, 4};
desc.format = wgpu::TextureFormat::RGBA8Unorm;
device.CreateTexture(&desc);
// Device lost should only happen because of destruction.
EXPECT_CALL(mDeviceLostCb, Call(WGPUDeviceLostReason_Destroyed, _, this)).Times(1);
}
// OOM error from query set creation is allowed and surfaced directly.
TEST_F(AllowedErrorTests, CreateQuerySet) {
EXPECT_CALL(*mDeviceMock, CreateQuerySetImpl)
.WillOnce(Return(DAWN_OUT_OF_MEMORY_ERROR(kOomErrorMessage)));
// Expect the OOM error.
EXPECT_CALL(mDeviceErrorCb, Call(WGPUErrorType_OutOfMemory, HasSubstr(kOomErrorMessage), this))
.Times(1);
wgpu::QuerySetDescriptor desc = {};
desc.type = wgpu::QueryType::Occlusion;
desc.count = 1;
device.CreateQuerySet(&desc);
// Device lost should only happen because of destruction.
EXPECT_CALL(mDeviceLostCb, Call(WGPUDeviceLostReason_Destroyed, _, this)).Times(1);
}
TEST_F(AllowedErrorTests, InjectError) {
// Expect the OOM error.
EXPECT_CALL(mDeviceErrorCb, Call(WGPUErrorType_OutOfMemory, HasSubstr(kOomErrorMessage), this))
.Times(1);
device.InjectError(wgpu::ErrorType::OutOfMemory, kOomErrorMessage);
// Device lost should only happen because of destruction.
EXPECT_CALL(mDeviceLostCb, Call(WGPUDeviceLostReason_Destroyed, _, this)).Times(1);
}
} // namespace
} // namespace dawn::native

View File

@ -16,9 +16,18 @@
namespace dawn::native { namespace dawn::native {
using ::testing::Return;
BufferMock::BufferMock(DeviceMock* device, const BufferDescriptor* descriptor) BufferMock::BufferMock(DeviceMock* device, const BufferDescriptor* descriptor)
: BufferBase(device, descriptor) { : BufferBase(device, descriptor) {
mBackingData = std::unique_ptr<uint8_t[]>(new uint8_t[GetSize()]);
mAllocatedSize = GetSize();
ON_CALL(*this, DestroyImpl).WillByDefault([this]() { this->BufferBase::DestroyImpl(); }); ON_CALL(*this, DestroyImpl).WillByDefault([this]() { this->BufferBase::DestroyImpl(); });
ON_CALL(*this, GetMappedPointer).WillByDefault(Return(mBackingData.get()));
ON_CALL(*this, IsCPUWritableAtCreation).WillByDefault([this]() {
return (GetUsage() & (wgpu::BufferUsage::MapRead | wgpu::BufferUsage::MapWrite)) != 0;
});
} }
BufferMock::~BufferMock() = default; BufferMock::~BufferMock() = default;

View File

@ -15,6 +15,8 @@
#ifndef SRC_DAWN_TESTS_UNITTESTS_NATIVE_MOCKS_BUFFERMOCK_H_ #ifndef SRC_DAWN_TESTS_UNITTESTS_NATIVE_MOCKS_BUFFERMOCK_H_
#define SRC_DAWN_TESTS_UNITTESTS_NATIVE_MOCKS_BUFFERMOCK_H_ #define SRC_DAWN_TESTS_UNITTESTS_NATIVE_MOCKS_BUFFERMOCK_H_
#include <memory>
#include "gmock/gmock.h" #include "gmock/gmock.h"
#include "dawn/native/Buffer.h" #include "dawn/native/Buffer.h"
@ -38,6 +40,9 @@ class BufferMock : public BufferBase {
MOCK_METHOD(void*, GetMappedPointer, (), (override)); MOCK_METHOD(void*, GetMappedPointer, (), (override));
MOCK_METHOD(bool, IsCPUWritableAtCreation, (), (const, override)); MOCK_METHOD(bool, IsCPUWritableAtCreation, (), (const, override));
private:
std::unique_ptr<uint8_t[]> mBackingData;
}; };
} // namespace dawn::native } // namespace dawn::native

View File

@ -17,6 +17,7 @@
#include "dawn/tests/unittests/native/mocks/BindGroupLayoutMock.h" #include "dawn/tests/unittests/native/mocks/BindGroupLayoutMock.h"
#include "dawn/tests/unittests/native/mocks/BindGroupMock.h" #include "dawn/tests/unittests/native/mocks/BindGroupMock.h"
#include "dawn/tests/unittests/native/mocks/BufferMock.h" #include "dawn/tests/unittests/native/mocks/BufferMock.h"
#include "dawn/tests/unittests/native/mocks/CommandBufferMock.h"
#include "dawn/tests/unittests/native/mocks/ComputePipelineMock.h" #include "dawn/tests/unittests/native/mocks/ComputePipelineMock.h"
#include "dawn/tests/unittests/native/mocks/ExternalTextureMock.h" #include "dawn/tests/unittests/native/mocks/ExternalTextureMock.h"
#include "dawn/tests/unittests/native/mocks/PipelineLayoutMock.h" #include "dawn/tests/unittests/native/mocks/PipelineLayoutMock.h"
@ -53,6 +54,12 @@ DeviceMock::DeviceMock() {
[this](const BufferDescriptor* descriptor) -> ResultOrError<Ref<BufferBase>> { [this](const BufferDescriptor* descriptor) -> ResultOrError<Ref<BufferBase>> {
return AcquireRef(new NiceMock<BufferMock>(this, descriptor)); return AcquireRef(new NiceMock<BufferMock>(this, descriptor));
})); }));
ON_CALL(*this, CreateCommandBuffer)
.WillByDefault(WithArgs<0, 1>(
[this](CommandEncoder* encoder, const CommandBufferDescriptor* descriptor)
-> ResultOrError<Ref<CommandBufferBase>> {
return AcquireRef(new NiceMock<CommandBufferMock>(this, encoder, descriptor));
}));
ON_CALL(*this, CreateExternalTextureImpl) ON_CALL(*this, CreateExternalTextureImpl)
.WillByDefault(WithArgs<0>([this](const ExternalTextureDescriptor* descriptor) .WillByDefault(WithArgs<0>([this](const ExternalTextureDescriptor* descriptor)
-> ResultOrError<Ref<ExternalTextureBase>> { -> ResultOrError<Ref<ExternalTextureBase>> {