Reland "Allow internal errors for pipeline creation failure"

This is a reland of commit e241d64d25
It adds handling and tests for internal errors in the wire

Original change's description:
> Allow internal errors for pipeline creation failure
>
> Change-Id: I6b8c109ae67e230fea3fb14511c2b3562191c0fa
> Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/132300
> Kokoro: Kokoro <noreply+kokoro@google.com>
> Commit-Queue: Austin Eng <enga@chromium.org>
> Reviewed-by: Loko Kung <lokokung@google.com>

Change-Id: Icfda2d04bbb340fc4fdacf5ae65593bf958172fb
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/132441
Reviewed-by: Loko Kung <lokokung@google.com>
Commit-Queue: Austin Eng <enga@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
Austin Eng 2023-05-11 00:06:38 +00:00 committed by Dawn LUCI CQ
parent 0214a30479
commit 039e886f9b
6 changed files with 192 additions and 9 deletions

View File

@ -1111,7 +1111,7 @@ ComputePipelineBase* DeviceBase::APICreateComputePipeline(
utils::GetLabelForTrace(descriptor->label));
Ref<ComputePipelineBase> result;
if (ConsumedError(CreateComputePipeline(descriptor), &result,
if (ConsumedError(CreateComputePipeline(descriptor), &result, InternalErrorType::Internal,
"calling %s.CreateComputePipeline(%s).", this, descriptor)) {
return ComputePipelineBase::MakeError(this, descriptor ? descriptor->label : nullptr);
}
@ -1201,7 +1201,7 @@ RenderPipelineBase* DeviceBase::APICreateRenderPipeline(
utils::GetLabelForTrace(descriptor->label));
Ref<RenderPipelineBase> result;
if (ConsumedError(CreateRenderPipeline(descriptor), &result,
if (ConsumedError(CreateRenderPipeline(descriptor), &result, InternalErrorType::Internal,
"calling %s.CreateRenderPipeline(%s).", this, descriptor)) {
return RenderPipelineBase::MakeError(this, descriptor ? descriptor->label : nullptr);
}

View File

@ -36,10 +36,8 @@ wgpu::ErrorType ToWGPUErrorType(InternalErrorType type) {
return wgpu::ErrorType::Validation;
case InternalErrorType::OutOfMemory:
return wgpu::ErrorType::OutOfMemory;
// There is no equivalent of Internal errors in the WebGPU API. Internal errors cause
// the device at the API level to be lost, so treat it like a DeviceLost error.
case InternalErrorType::Internal:
return wgpu::ErrorType::Internal;
case InternalErrorType::DeviceLost:
return wgpu::ErrorType::DeviceLost;

View File

@ -42,6 +42,7 @@ using ::testing::StrictMock;
using ::testing::Test;
static constexpr char kOomErrorMessage[] = "Out of memory error";
static constexpr char kInternalErrorMessage[] = "Internal error";
static constexpr std::string_view kComputeShader = R"(
@compute @workgroup_size(1) fn main() {}
@ -237,6 +238,56 @@ TEST_F(AllowedErrorTests, CreateRenderPipeline) {
device.CreateRenderPipeline(ToCppAPI(&desc));
}
// Internal error from synchronously initializing a compute pipeline should not result in a device
// loss.
TEST_F(AllowedErrorTests, CreateComputePipelineInternalError) {
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(ByMove(DAWN_INTERNAL_ERROR(kInternalErrorMessage))));
EXPECT_CALL(*mDeviceMock, CreateUninitializedComputePipelineImpl)
.WillOnce(Return(ByMove(std::move(computePipelineMock))));
// Expect the internal error.
EXPECT_CALL(mDeviceErrorCb,
Call(WGPUErrorType_Internal, HasSubstr(kInternalErrorMessage), this))
.Times(1);
device.CreateComputePipeline(ToCppAPI(&desc));
// Device lost should only happen due to destruction.
EXPECT_CALL(mDeviceLostCb, Call(WGPUDeviceLostReason_Destroyed, _, this)).Times(1);
}
// Internal error from synchronously initializing a render pipeline should not result in a device
// loss.
TEST_F(AllowedErrorTests, CreateRenderPipelineInternalError) {
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(ByMove(DAWN_INTERNAL_ERROR(kInternalErrorMessage))));
EXPECT_CALL(*mDeviceMock, CreateUninitializedRenderPipelineImpl)
.WillOnce(Return(ByMove(std::move(renderPipelineMock))));
// Expect the internal error.
EXPECT_CALL(mDeviceErrorCb,
Call(WGPUErrorType_Internal, HasSubstr(kInternalErrorMessage), this))
.Times(1);
device.CreateRenderPipeline(ToCppAPI(&desc));
// Device lost should only happen due to destruction.
EXPECT_CALL(mDeviceLostCb, Call(WGPUDeviceLostReason_Destroyed, _, this)).Times(1);
}
//
// Exercise async APIs where OOM errors do NOT currently cause a device lost.
//
@ -293,6 +344,60 @@ TEST_F(AllowedErrorTests, CreateRenderPipelineAsync) {
EXPECT_CALL(mDeviceLostCb, Call(WGPUDeviceLostReason_Destroyed, _, this)).Times(1);
}
// Internal error from asynchronously initializing a compute pipeline should not result in a device
// loss.
TEST_F(AllowedErrorTests, CreateComputePipelineAsyncInternalError) {
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(ByMove(DAWN_INTERNAL_ERROR(kInternalErrorMessage))));
EXPECT_CALL(*mDeviceMock, CreateUninitializedComputePipelineImpl)
.WillOnce(Return(ByMove(std::move(computePipelineMock))));
MockCallback<wgpu::CreateComputePipelineAsyncCallback> cb;
EXPECT_CALL(cb, Call(WGPUCreatePipelineAsyncStatus_InternalError, _,
HasSubstr(kInternalErrorMessage), 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);
}
// Internal error from asynchronously initializing a render pipeline should not result in a device
// loss.
TEST_F(AllowedErrorTests, CreateRenderPipelineAsyncInternalError) {
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(ByMove(DAWN_INTERNAL_ERROR(kInternalErrorMessage))));
EXPECT_CALL(*mDeviceMock, CreateUninitializedRenderPipelineImpl)
.WillOnce(Return(ByMove(std::move(renderPipelineMock))));
MockCallback<wgpu::CreateRenderPipelineAsyncCallback> cb;
EXPECT_CALL(cb, Call(WGPUCreatePipelineAsyncStatus_InternalError, _,
HasSubstr(kInternalErrorMessage), 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.
//

View File

@ -104,8 +104,8 @@ class WireErrorCallbackTests : public WireTest {
}
};
// Test the return wire for device error callbacks
TEST_F(WireErrorCallbackTests, DeviceErrorCallback) {
// Test the return wire for device validation error callbacks
TEST_F(WireErrorCallbackTests, DeviceValidationErrorCallback) {
wgpuDeviceSetUncapturedErrorCallback(device, ToMockDeviceErrorCallback, this);
// Setting the error callback should stay on the client side and do nothing
@ -123,6 +123,44 @@ TEST_F(WireErrorCallbackTests, DeviceErrorCallback) {
FlushServer();
}
// Test the return wire for device OOM error callbacks
TEST_F(WireErrorCallbackTests, DeviceOutOfMemoryErrorCallback) {
wgpuDeviceSetUncapturedErrorCallback(device, ToMockDeviceErrorCallback, this);
// Setting the error callback should stay on the client side and do nothing
FlushClient();
// Calling the callback on the server side will result in the callback being called on the
// client side
api.CallDeviceSetUncapturedErrorCallbackCallback(apiDevice, WGPUErrorType_OutOfMemory,
"Some error message");
EXPECT_CALL(*mockDeviceErrorCallback,
Call(WGPUErrorType_OutOfMemory, StrEq("Some error message"), this))
.Times(1);
FlushServer();
}
// Test the return wire for device internal error callbacks
TEST_F(WireErrorCallbackTests, DeviceInternalErrorCallback) {
wgpuDeviceSetUncapturedErrorCallback(device, ToMockDeviceErrorCallback, this);
// Setting the error callback should stay on the client side and do nothing
FlushClient();
// Calling the callback on the server side will result in the callback being called on the
// client side
api.CallDeviceSetUncapturedErrorCallbackCallback(apiDevice, WGPUErrorType_Internal,
"Some error message");
EXPECT_CALL(*mockDeviceErrorCallback,
Call(WGPUErrorType_Internal, StrEq("Some error message"), this))
.Times(1);
FlushServer();
}
// Test the return wire for device user warning callbacks
TEST_F(WireErrorCallbackTests, DeviceLoggingCallback) {
wgpuDeviceSetLoggingCallback(device, ToMockDeviceLoggingCallback, this);
@ -140,8 +178,8 @@ TEST_F(WireErrorCallbackTests, DeviceLoggingCallback) {
FlushServer();
}
// Test the return wire for error scopes.
TEST_F(WireErrorCallbackTests, PushPopErrorScopeCallback) {
// Test the return wire for validation error scopes.
TEST_F(WireErrorCallbackTests, PushPopValidationErrorScopeCallback) {
EXPECT_CALL(api, DevicePushErrorScope(apiDevice, WGPUErrorFilter_Validation)).Times(1);
wgpuDevicePushErrorScope(device, WGPUErrorFilter_Validation);
FlushClient();
@ -160,6 +198,46 @@ TEST_F(WireErrorCallbackTests, PushPopErrorScopeCallback) {
FlushServer();
}
// Test the return wire for OOM error scopes.
TEST_F(WireErrorCallbackTests, PushPopOOMErrorScopeCallback) {
EXPECT_CALL(api, DevicePushErrorScope(apiDevice, WGPUErrorFilter_OutOfMemory)).Times(1);
wgpuDevicePushErrorScope(device, WGPUErrorFilter_OutOfMemory);
FlushClient();
WGPUErrorCallback callback;
void* userdata;
EXPECT_CALL(api, OnDevicePopErrorScope(apiDevice, _, _))
.WillOnce(DoAll(SaveArg<1>(&callback), SaveArg<2>(&userdata)));
wgpuDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this);
FlushClient();
EXPECT_CALL(*mockDevicePopErrorScopeCallback,
Call(WGPUErrorType_OutOfMemory, StrEq("Some error message"), this))
.Times(1);
callback(WGPUErrorType_OutOfMemory, "Some error message", userdata);
FlushServer();
}
// Test the return wire for internal error scopes.
TEST_F(WireErrorCallbackTests, PushPopInternalErrorScopeCallback) {
EXPECT_CALL(api, DevicePushErrorScope(apiDevice, WGPUErrorFilter_Internal)).Times(1);
wgpuDevicePushErrorScope(device, WGPUErrorFilter_Internal);
FlushClient();
WGPUErrorCallback callback;
void* userdata;
EXPECT_CALL(api, OnDevicePopErrorScope(apiDevice, _, _))
.WillOnce(DoAll(SaveArg<1>(&callback), SaveArg<2>(&userdata)));
wgpuDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this);
FlushClient();
EXPECT_CALL(*mockDevicePopErrorScopeCallback,
Call(WGPUErrorType_Internal, StrEq("Some error message"), this))
.Times(1);
callback(WGPUErrorType_Internal, "Some error message", userdata);
FlushServer();
}
// Test the return wire for error scopes when callbacks return in a various orders.
TEST_F(WireErrorCallbackTests, PopErrorScopeCallbackOrdering) {
// Two error scopes are popped, and the first one returns first.

View File

@ -27,6 +27,7 @@ bool Client::DoDeviceUncapturedErrorCallback(Device* device,
case WGPUErrorType_NoError:
case WGPUErrorType_Validation:
case WGPUErrorType_OutOfMemory:
case WGPUErrorType_Internal:
case WGPUErrorType_Unknown:
case WGPUErrorType_DeviceLost:
break;

View File

@ -169,6 +169,7 @@ bool Device::OnPopErrorScopeCallback(uint64_t requestSerial,
case WGPUErrorType_NoError:
case WGPUErrorType_Validation:
case WGPUErrorType_OutOfMemory:
case WGPUErrorType_Internal:
case WGPUErrorType_Unknown:
case WGPUErrorType_DeviceLost:
break;