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:
parent
8812918fa4
commit
53893a3d77
|
@ -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) {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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_
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>> {
|
||||||
|
|
Loading…
Reference in New Issue