Handle Device Lost for Buffer

Bug: dawn:68, chromium:1042998, chromium:1043468
Change-Id: I4faa46b0d2e8f814b9d353a75489d3c8ca0b2e89
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/15340
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Natasha Lee <natlee@microsoft.com>
This commit is contained in:
Natasha Lee 2020-01-28 22:18:58 +00:00 committed by Commit Bot service account
parent f329c78b6c
commit 74f5054ec9
6 changed files with 193 additions and 17 deletions

View File

@ -202,11 +202,17 @@ namespace dawn_native {
ASSERT(!IsError()); ASSERT(!IsError());
if (mMapReadCallback != nullptr && serial == mMapSerial) { if (mMapReadCallback != nullptr && serial == mMapSerial) {
ASSERT(mMapWriteCallback == nullptr); ASSERT(mMapWriteCallback == nullptr);
// Tag the callback as fired before firing it, otherwise it could fire a second time if // Tag the callback as fired before firing it, otherwise it could fire a second time if
// for example buffer.Unmap() is called inside the application-provided callback. // for example buffer.Unmap() is called inside the application-provided callback.
WGPUBufferMapReadCallback callback = mMapReadCallback; WGPUBufferMapReadCallback callback = mMapReadCallback;
mMapReadCallback = nullptr; mMapReadCallback = nullptr;
callback(status, pointer, dataLength, mMapUserdata);
if (GetDevice()->IsLost()) {
callback(WGPUBufferMapAsyncStatus_DeviceLost, nullptr, 0, mMapUserdata);
} else {
callback(status, pointer, dataLength, mMapUserdata);
}
} }
} }
@ -217,11 +223,17 @@ namespace dawn_native {
ASSERT(!IsError()); ASSERT(!IsError());
if (mMapWriteCallback != nullptr && serial == mMapSerial) { if (mMapWriteCallback != nullptr && serial == mMapSerial) {
ASSERT(mMapReadCallback == nullptr); ASSERT(mMapReadCallback == nullptr);
// Tag the callback as fired before firing it, otherwise it could fire a second time if // Tag the callback as fired before firing it, otherwise it could fire a second time if
// for example buffer.Unmap() is called inside the application-provided callback. // for example buffer.Unmap() is called inside the application-provided callback.
WGPUBufferMapWriteCallback callback = mMapWriteCallback; WGPUBufferMapWriteCallback callback = mMapWriteCallback;
mMapWriteCallback = nullptr; mMapWriteCallback = nullptr;
callback(status, pointer, dataLength, mMapUserdata);
if (GetDevice()->IsLost()) {
callback(WGPUBufferMapAsyncStatus_DeviceLost, nullptr, 0, mMapUserdata);
} else {
callback(status, pointer, dataLength, mMapUserdata);
}
} }
} }
@ -237,8 +249,9 @@ namespace dawn_native {
} }
void BufferBase::MapReadAsync(WGPUBufferMapReadCallback callback, void* userdata) { void BufferBase::MapReadAsync(WGPUBufferMapReadCallback callback, void* userdata) {
if (GetDevice()->ConsumedError(ValidateMap(wgpu::BufferUsage::MapRead))) { WGPUBufferMapAsyncStatus status;
callback(WGPUBufferMapAsyncStatus_Error, nullptr, 0, userdata); if (GetDevice()->ConsumedError(ValidateMap(wgpu::BufferUsage::MapRead, &status))) {
callback(status, nullptr, 0, userdata);
return; return;
} }
ASSERT(!IsError()); ASSERT(!IsError());
@ -273,8 +286,9 @@ namespace dawn_native {
} }
void BufferBase::MapWriteAsync(WGPUBufferMapWriteCallback callback, void* userdata) { void BufferBase::MapWriteAsync(WGPUBufferMapWriteCallback callback, void* userdata) {
if (GetDevice()->ConsumedError(ValidateMap(wgpu::BufferUsage::MapWrite))) { WGPUBufferMapAsyncStatus status;
callback(WGPUBufferMapAsyncStatus_Error, nullptr, 0, userdata); if (GetDevice()->ConsumedError(ValidateMap(wgpu::BufferUsage::MapWrite, &status))) {
callback(status, nullptr, 0, userdata);
return; return;
} }
ASSERT(!IsError()); ASSERT(!IsError());
@ -389,8 +403,12 @@ namespace dawn_native {
return {}; return {};
} }
MaybeError BufferBase::ValidateMap(wgpu::BufferUsage requiredUsage) const { MaybeError BufferBase::ValidateMap(wgpu::BufferUsage requiredUsage,
WGPUBufferMapAsyncStatus* status) const {
*status = WGPUBufferMapAsyncStatus_DeviceLost;
DAWN_TRY(GetDevice()->ValidateIsAlive()); DAWN_TRY(GetDevice()->ValidateIsAlive());
*status = WGPUBufferMapAsyncStatus_Error;
DAWN_TRY(GetDevice()->ValidateObject(this)); DAWN_TRY(GetDevice()->ValidateObject(this));
switch (mState) { switch (mState) {
@ -406,6 +424,7 @@ namespace dawn_native {
return DAWN_VALIDATION_ERROR("Buffer needs the correct map usage bit"); return DAWN_VALIDATION_ERROR("Buffer needs the correct map usage bit");
} }
*status = WGPUBufferMapAsyncStatus_Success;
return {}; return {};
} }

View File

@ -94,7 +94,8 @@ namespace dawn_native {
MaybeError CopyFromStagingBuffer(); MaybeError CopyFromStagingBuffer();
MaybeError ValidateSetSubData(uint32_t start, uint32_t count) const; MaybeError ValidateSetSubData(uint32_t start, uint32_t count) const;
MaybeError ValidateMap(wgpu::BufferUsage requiredUsage) const; MaybeError ValidateMap(wgpu::BufferUsage requiredUsage,
WGPUBufferMapAsyncStatus* status) const;
MaybeError ValidateUnmap() const; MaybeError ValidateUnmap() const;
MaybeError ValidateDestroy() const; MaybeError ValidateDestroy() const;

View File

@ -198,6 +198,10 @@ namespace dawn_native {
HandleError(wgpu::ErrorType::DeviceLost, "Device lost for testing"); HandleError(wgpu::ErrorType::DeviceLost, "Device lost for testing");
} }
bool DeviceBase::IsLost() const {
return mLossStatus != LossStatus::Alive;
}
AdapterBase* DeviceBase::GetAdapter() const { AdapterBase* DeviceBase::GetAdapter() const {
return mAdapter; return mAdapter;
} }
@ -478,7 +482,9 @@ namespace dawn_native {
WGPUCreateBufferMappedResult result = CreateBufferMapped(descriptor); WGPUCreateBufferMappedResult result = CreateBufferMapped(descriptor);
WGPUBufferMapAsyncStatus status = WGPUBufferMapAsyncStatus_Success; WGPUBufferMapAsyncStatus status = WGPUBufferMapAsyncStatus_Success;
if (result.data == nullptr || result.dataLength != descriptor->size) { if (IsLost()) {
status = WGPUBufferMapAsyncStatus_DeviceLost;
} else if (result.data == nullptr || result.dataLength != descriptor->size) {
status = WGPUBufferMapAsyncStatus_Error; status = WGPUBufferMapAsyncStatus_Error;
} }
@ -594,6 +600,14 @@ namespace dawn_native {
// Other Device API methods // Other Device API methods
void DeviceBase::Tick() { void DeviceBase::Tick() {
// We need to do the deferred callback even if Device is lost since Buffer Map Async will
// send callback with device lost status when device is lost.
{
auto deferredResults = std::move(mDeferredCreateBufferMappedAsyncResults);
for (const auto& deferred : deferredResults) {
deferred.callback(deferred.status, deferred.result, deferred.userdata);
}
}
if (ConsumedError(ValidateIsAlive())) { if (ConsumedError(ValidateIsAlive())) {
return; return;
} }
@ -601,12 +615,6 @@ namespace dawn_native {
return; return;
} }
{
auto deferredResults = std::move(mDeferredCreateBufferMappedAsyncResults);
for (const auto& deferred : deferredResults) {
deferred.callback(deferred.status, deferred.result, deferred.userdata);
}
}
mErrorScopeTracker->Tick(GetCompletedCommandSerial()); mErrorScopeTracker->Tick(GetCompletedCommandSerial());
mFenceSignalTracker->Tick(GetCompletedCommandSerial()); mFenceSignalTracker->Tick(GetCompletedCommandSerial());
} }

View File

@ -190,6 +190,7 @@ namespace dawn_native {
size_t GetLazyClearCountForTesting(); size_t GetLazyClearCountForTesting();
void IncrementLazyClearCountForTesting(); void IncrementLazyClearCountForTesting();
void LoseForTesting(); void LoseForTesting();
bool IsLost() const;
protected: protected:
void SetToggle(Toggle toggle, bool isEnabled); void SetToggle(Toggle toggle, bool isEnabled);

View File

@ -88,6 +88,9 @@ namespace dawn_native { namespace null {
Device::~Device() { Device::~Device() {
BaseDestructor(); BaseDestructor();
// This assert is in the destructor rather than Device::Destroy() because it needs to make
// sure buffers have been destroyed before the device.
ASSERT(mMemoryUsage == 0);
} }
ResultOrError<BindGroupBase*> Device::CreateBindGroupImpl( ResultOrError<BindGroupBase*> Device::CreateBindGroupImpl(
@ -181,7 +184,6 @@ namespace dawn_native { namespace null {
mDynamicUploader = nullptr; mDynamicUploader = nullptr;
mPendingOperations.clear(); mPendingOperations.clear();
ASSERT(mMemoryUsage == 0);
} }
MaybeError Device::WaitForIdleForDestruction() { MaybeError Device::WaitForIdleForDestruction() {

View File

@ -34,6 +34,8 @@ static void ToMockDeviceLostCallback(const char* message, void* userdata) {
self->StartExpectDeviceError(); self->StartExpectDeviceError();
} }
static const int fakeUserData = 0;
class DeviceLostTest : public DawnTest { class DeviceLostTest : public DawnTest {
protected: protected:
void TestSetUp() override { void TestSetUp() override {
@ -52,6 +54,26 @@ class DeviceLostTest : public DawnTest {
EXPECT_CALL(*mockDeviceLostCallback, Call(_, this)).Times(1); EXPECT_CALL(*mockDeviceLostCallback, Call(_, this)).Times(1);
device.LoseForTesting(); device.LoseForTesting();
} }
static void CheckMapWriteFail(WGPUBufferMapAsyncStatus status,
void* data,
uint64_t datalength,
void* userdata) {
EXPECT_EQ(WGPUBufferMapAsyncStatus_DeviceLost, status);
EXPECT_EQ(nullptr, data);
EXPECT_EQ(0u, datalength);
EXPECT_EQ(&fakeUserData, userdata);
}
static void CheckMapReadFail(WGPUBufferMapAsyncStatus status,
const void* data,
uint64_t datalength,
void* userdata) {
EXPECT_EQ(WGPUBufferMapAsyncStatus_DeviceLost, status);
EXPECT_EQ(nullptr, data);
EXPECT_EQ(0u, datalength);
EXPECT_EQ(&fakeUserData, userdata);
}
}; };
// Test that DeviceLostCallback is invoked when LostForTestimg is called // Test that DeviceLostCallback is invoked when LostForTestimg is called
@ -212,4 +234,127 @@ TEST_P(DeviceLostTest, TickFails) {
ASSERT_DEVICE_ERROR(device.Tick()); ASSERT_DEVICE_ERROR(device.Tick());
} }
// Test that CreateBuffer fails when device is lost
TEST_P(DeviceLostTest, CreateBufferFails) {
SetCallbackAndLoseForTesting();
wgpu::BufferDescriptor bufferDescriptor;
bufferDescriptor.size = sizeof(float);
bufferDescriptor.usage = wgpu::BufferUsage::CopySrc;
ASSERT_DEVICE_ERROR(device.CreateBuffer(&bufferDescriptor));
}
// Test that buffer.MapWriteAsync fails after device is lost
TEST_P(DeviceLostTest, BufferMapWriteAsyncFails) {
wgpu::BufferDescriptor bufferDescriptor;
bufferDescriptor.size = sizeof(float);
bufferDescriptor.usage = wgpu::BufferUsage::MapWrite;
wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor);
SetCallbackAndLoseForTesting();
ASSERT_DEVICE_ERROR(buffer.MapWriteAsync(CheckMapWriteFail, const_cast<int*>(&fakeUserData)));
}
// Test that buffer.MapWriteAsync calls back with device loss status
TEST_P(DeviceLostTest, BufferMapWriteAsyncBeforeLossFails) {
wgpu::BufferDescriptor bufferDescriptor;
bufferDescriptor.size = sizeof(float);
bufferDescriptor.usage = wgpu::BufferUsage::MapWrite;
wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor);
buffer.MapWriteAsync(CheckMapWriteFail, const_cast<int*>(&fakeUserData));
SetCallbackAndLoseForTesting();
}
// Test that buffer.Unmap fails after device is lost
TEST_P(DeviceLostTest, BufferUnmapFails) {
wgpu::BufferDescriptor bufferDescriptor;
bufferDescriptor.size = sizeof(float);
bufferDescriptor.usage = wgpu::BufferUsage::MapWrite;
wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor);
wgpu::CreateBufferMappedResult result = device.CreateBufferMapped(&bufferDescriptor);
SetCallbackAndLoseForTesting();
ASSERT_DEVICE_ERROR(result.buffer.Unmap());
}
// Test that CreateBufferMapped fails after device is lost
TEST_P(DeviceLostTest, CreateBufferMappedFails) {
wgpu::BufferDescriptor bufferDescriptor;
bufferDescriptor.size = sizeof(float);
bufferDescriptor.usage = wgpu::BufferUsage::MapWrite;
SetCallbackAndLoseForTesting();
ASSERT_DEVICE_ERROR(device.CreateBufferMapped(&bufferDescriptor));
}
// Test that CreateBufferMappedAsync fails after device is lost
TEST_P(DeviceLostTest, CreateBufferMappedAsyncFails) {
wgpu::BufferDescriptor bufferDescriptor;
bufferDescriptor.size = sizeof(float);
bufferDescriptor.usage = wgpu::BufferUsage::MapWrite;
SetCallbackAndLoseForTesting();
struct ResultInfo {
wgpu::CreateBufferMappedResult result;
bool done = false;
} resultInfo;
ASSERT_DEVICE_ERROR(device.CreateBufferMappedAsync(
&bufferDescriptor,
[](WGPUBufferMapAsyncStatus status, WGPUCreateBufferMappedResult result, void* userdata) {
auto* resultInfo = static_cast<ResultInfo*>(userdata);
EXPECT_EQ(WGPUBufferMapAsyncStatus_DeviceLost, status);
EXPECT_NE(nullptr, result.data);
resultInfo->result.buffer = wgpu::Buffer::Acquire(result.buffer);
resultInfo->result.data = result.data;
resultInfo->result.dataLength = result.dataLength;
resultInfo->done = true;
},
&resultInfo));
while (!resultInfo.done) {
ASSERT_DEVICE_ERROR(WaitABit());
}
ASSERT_DEVICE_ERROR(resultInfo.result.buffer.Unmap());
}
// Test that BufferMapReadAsync fails after device is lost
TEST_P(DeviceLostTest, BufferMapReadAsyncFails) {
wgpu::BufferDescriptor bufferDescriptor;
bufferDescriptor.size = sizeof(float);
bufferDescriptor.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor);
SetCallbackAndLoseForTesting();
ASSERT_DEVICE_ERROR(buffer.MapReadAsync(CheckMapReadFail, const_cast<int*>(&fakeUserData)));
}
// Test that BufferMapReadAsync calls back with device lost status when device lost after map read
TEST_P(DeviceLostTest, BufferMapReadAsyncBeforeLossFails) {
wgpu::BufferDescriptor bufferDescriptor;
bufferDescriptor.size = sizeof(float);
bufferDescriptor.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor);
buffer.MapReadAsync(CheckMapReadFail, const_cast<int*>(&fakeUserData));
SetCallbackAndLoseForTesting();
}
// Test that SetSubData fails after device is lost
TEST_P(DeviceLostTest, SetSubDataFails) {
wgpu::BufferDescriptor bufferDescriptor;
bufferDescriptor.size = sizeof(float);
bufferDescriptor.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor);
SetCallbackAndLoseForTesting();
std::array<float, 1> data = {12};
ASSERT_DEVICE_ERROR(buffer.SetSubData(0, sizeof(float), data.data()));
}
DAWN_INSTANTIATE_TEST(DeviceLostTest, D3D12Backend, VulkanBackend); DAWN_INSTANTIATE_TEST(DeviceLostTest, D3D12Backend, VulkanBackend);