mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-06-06 14:43:31 +00:00
Expose device.destroy upwards and add end2end tests
Bug: dawn:628 Change-Id: I0820d6855ac928c25f5720a2ccf0f21ae3f88d79 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/68120 Commit-Queue: Loko Kung <lokokung@google.com> Reviewed-by: Corentin Wallez <cwallez@chromium.org> Reviewed-by: Austin Eng <enga@chromium.org>
This commit is contained in:
parent
365c7b66e6
commit
2494c9be8f
@ -1016,9 +1016,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "destroy",
|
"name": "destroy"
|
||||||
"_TODO": "crbug.com/dawn/628: Implement in Dawn",
|
|
||||||
"tags": ["upstream"]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "get limits",
|
"name": "get limits",
|
||||||
|
@ -312,8 +312,20 @@ namespace dawn_native {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DeviceBase::Destroy() {
|
void DeviceBase::Destroy() {
|
||||||
|
// Skip if we are already destroyed.
|
||||||
|
if (mState == State::Destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Skip handling device facilities if they haven't even been created (or failed doing so)
|
// Skip handling device facilities if they haven't even been created (or failed doing so)
|
||||||
if (mState != State::BeingCreated) {
|
if (mState != State::BeingCreated) {
|
||||||
|
// The device is being destroyed so it will be lost, call the application callback.
|
||||||
|
if (mDeviceLostCallback != nullptr) {
|
||||||
|
mDeviceLostCallback(WGPUDeviceLostReason_Destroyed, "Device was destroyed.",
|
||||||
|
mDeviceLostUserdata);
|
||||||
|
mDeviceLostCallback = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// Call all the callbacks immediately as the device is about to shut down.
|
// Call all the callbacks immediately as the device is about to shut down.
|
||||||
// TODO(crbug.com/dawn/826): Cancel the tasks that are in flight if possible.
|
// TODO(crbug.com/dawn/826): Cancel the tasks that are in flight if possible.
|
||||||
mAsyncTaskManager->WaitAllPendingTasks();
|
mAsyncTaskManager->WaitAllPendingTasks();
|
||||||
@ -346,15 +358,21 @@ namespace dawn_native {
|
|||||||
|
|
||||||
case State::Disconnected:
|
case State::Disconnected:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case State::Destroyed:
|
||||||
|
// If we are already destroyed we should've skipped this work entirely.
|
||||||
|
UNREACHABLE();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
ASSERT(mCompletedSerial == mLastSubmittedSerial);
|
ASSERT(mCompletedSerial == mLastSubmittedSerial);
|
||||||
ASSERT(mFutureSerial <= mCompletedSerial);
|
ASSERT(mFutureSerial <= mCompletedSerial);
|
||||||
|
|
||||||
if (mState != State::BeingCreated) {
|
if (mState != State::BeingCreated) {
|
||||||
// The GPU timeline is finished.
|
// The GPU timeline is finished.
|
||||||
// Tick the queue-related tasks since they should be complete. This must be done before
|
// Finish destroying all objects owned by the device and tick the queue-related tasks
|
||||||
// DestroyImpl() it may relinquish resources that will be freed by backends in the
|
// since they should be complete. This must be done before DestroyImpl() it may
|
||||||
// DestroyImpl() call.
|
// relinquish resources that will be freed by backends in the DestroyImpl() call.
|
||||||
|
DestroyObjects();
|
||||||
mQueue->Tick(GetCompletedCommandSerial());
|
mQueue->Tick(GetCompletedCommandSerial());
|
||||||
// Call TickImpl once last time to clean up resources
|
// Call TickImpl once last time to clean up resources
|
||||||
// Ignore errors so that we can continue with destruction
|
// Ignore errors so that we can continue with destruction
|
||||||
@ -362,6 +380,8 @@ namespace dawn_native {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// At this point GPU operations are always finished, so we are in the disconnected state.
|
// At this point GPU operations are always finished, so we are in the disconnected state.
|
||||||
|
// Note that currently this state change is required because some of the backend
|
||||||
|
// implementations of DestroyImpl checks that we are disconnected before doing work.
|
||||||
mState = State::Disconnected;
|
mState = State::Disconnected;
|
||||||
|
|
||||||
mDynamicUploader = nullptr;
|
mDynamicUploader = nullptr;
|
||||||
@ -373,12 +393,15 @@ namespace dawn_native {
|
|||||||
|
|
||||||
AssumeCommandsComplete();
|
AssumeCommandsComplete();
|
||||||
|
|
||||||
// Now that the GPU timeline is empty, destroy all objects owned by the device, and then the
|
// Now that the GPU timeline is empty, destroy the backend device.
|
||||||
// backend device.
|
|
||||||
DestroyObjects();
|
|
||||||
DestroyImpl();
|
DestroyImpl();
|
||||||
|
|
||||||
mCaches = nullptr;
|
mCaches = nullptr;
|
||||||
|
mState = State::Destroyed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeviceBase::APIDestroy() {
|
||||||
|
Destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceBase::HandleError(InternalErrorType type, const char* message) {
|
void DeviceBase::HandleError(InternalErrorType type, const char* message) {
|
||||||
@ -421,8 +444,6 @@ namespace dawn_native {
|
|||||||
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) {
|
||||||
// TODO(crbug.com/dawn/628): Make sure the "Destroyed" reason is passed if
|
|
||||||
// the device was destroyed.
|
|
||||||
mDeviceLostCallback(WGPUDeviceLostReason_Undefined, message, mDeviceLostUserdata);
|
mDeviceLostCallback(WGPUDeviceLostReason_Undefined, message, mDeviceLostUserdata);
|
||||||
mDeviceLostCallback = nullptr;
|
mDeviceLostCallback = nullptr;
|
||||||
}
|
}
|
||||||
@ -461,6 +482,9 @@ namespace dawn_native {
|
|||||||
// resetting) the resources pointed by such pointer may be freed. Flush all deferred
|
// resetting) the resources pointed by such pointer may be freed. Flush all deferred
|
||||||
// callback tasks to guarantee we are never going to use the previous callback after
|
// callback tasks to guarantee we are never going to use the previous callback after
|
||||||
// this call.
|
// this call.
|
||||||
|
if (IsLost()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
FlushCallbackTaskQueue();
|
FlushCallbackTaskQueue();
|
||||||
mLoggingCallback = callback;
|
mLoggingCallback = callback;
|
||||||
mLoggingUserdata = userdata;
|
mLoggingUserdata = userdata;
|
||||||
@ -472,6 +496,9 @@ namespace dawn_native {
|
|||||||
// resetting) the resources pointed by such pointer may be freed. Flush all deferred
|
// resetting) the resources pointed by such pointer may be freed. Flush all deferred
|
||||||
// callback tasks to guarantee we are never going to use the previous callback after
|
// callback tasks to guarantee we are never going to use the previous callback after
|
||||||
// this call.
|
// this call.
|
||||||
|
if (IsLost()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
FlushCallbackTaskQueue();
|
FlushCallbackTaskQueue();
|
||||||
mUncapturedErrorCallback = callback;
|
mUncapturedErrorCallback = callback;
|
||||||
mUncapturedErrorUserdata = userdata;
|
mUncapturedErrorUserdata = userdata;
|
||||||
@ -483,6 +510,9 @@ namespace dawn_native {
|
|||||||
// resetting) the resources pointed by such pointer may be freed. Flush all deferred
|
// resetting) the resources pointed by such pointer may be freed. Flush all deferred
|
||||||
// callback tasks to guarantee we are never going to use the previous callback after
|
// callback tasks to guarantee we are never going to use the previous callback after
|
||||||
// this call.
|
// this call.
|
||||||
|
if (IsLost()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
FlushCallbackTaskQueue();
|
FlushCallbackTaskQueue();
|
||||||
mDeviceLostCallback = callback;
|
mDeviceLostCallback = callback;
|
||||||
mDeviceLostUserdata = userdata;
|
mDeviceLostUserdata = userdata;
|
||||||
|
@ -297,11 +297,13 @@ namespace dawn_native {
|
|||||||
// Disconnected)
|
// Disconnected)
|
||||||
// - Disconnected: there is no longer work happening on the GPU timeline and the CPU data
|
// - Disconnected: there is no longer work happening on the GPU timeline and the CPU data
|
||||||
// structures can be safely destroyed without additional synchronization.
|
// structures can be safely destroyed without additional synchronization.
|
||||||
|
// - Destroyed: the device is disconnected and resources have been reclaimed.
|
||||||
enum class State {
|
enum class State {
|
||||||
BeingCreated,
|
BeingCreated,
|
||||||
Alive,
|
Alive,
|
||||||
BeingDisconnected,
|
BeingDisconnected,
|
||||||
Disconnected,
|
Disconnected,
|
||||||
|
Destroyed,
|
||||||
};
|
};
|
||||||
State GetState() const;
|
State GetState() const;
|
||||||
bool IsLost() const;
|
bool IsLost() const;
|
||||||
@ -365,6 +367,7 @@ namespace dawn_native {
|
|||||||
|
|
||||||
const std::string& GetLabel() const;
|
const std::string& GetLabel() const;
|
||||||
void APISetLabel(const char* label);
|
void APISetLabel(const char* label);
|
||||||
|
void APIDestroy();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Constructor used only for mocking and testing.
|
// Constructor used only for mocking and testing.
|
||||||
|
@ -1007,6 +1007,9 @@ void DawnTestBase::TearDown() {
|
|||||||
EXPECT_EQ(mLastWarningCount,
|
EXPECT_EQ(mLastWarningCount,
|
||||||
dawn_native::GetDeprecationWarningCountForTesting(device.Get()));
|
dawn_native::GetDeprecationWarningCountForTesting(device.Get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The device will be destroyed soon after, so we want to set the expectation.
|
||||||
|
ExpectDeviceDestruction();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DawnTestBase::StartExpectDeviceError() {
|
void DawnTestBase::StartExpectDeviceError() {
|
||||||
@ -1018,6 +1021,10 @@ bool DawnTestBase::EndExpectDeviceError() {
|
|||||||
return mError;
|
return mError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DawnTestBase::ExpectDeviceDestruction() {
|
||||||
|
mExpectDestruction = true;
|
||||||
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
void DawnTestBase::OnDeviceError(WGPUErrorType type, const char* message, void* userdata) {
|
void DawnTestBase::OnDeviceError(WGPUErrorType type, const char* message, void* userdata) {
|
||||||
ASSERT(type != WGPUErrorType_NoError);
|
ASSERT(type != WGPUErrorType_NoError);
|
||||||
@ -1029,9 +1036,14 @@ void DawnTestBase::OnDeviceError(WGPUErrorType type, const char* message, void*
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DawnTestBase::OnDeviceLost(WGPUDeviceLostReason reason, const char* message, void* userdata) {
|
void DawnTestBase::OnDeviceLost(WGPUDeviceLostReason reason, const char* message, void* userdata) {
|
||||||
|
DawnTestBase* self = static_cast<DawnTestBase*>(userdata);
|
||||||
|
if (self->mExpectDestruction) {
|
||||||
|
EXPECT_EQ(reason, WGPUDeviceLostReason_Destroyed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Using ADD_FAILURE + ASSERT instead of FAIL to prevent the current test from continuing with a
|
// Using ADD_FAILURE + ASSERT instead of FAIL to prevent the current test from continuing with a
|
||||||
// corrupt state.
|
// corrupt state.
|
||||||
ADD_FAILURE() << "Device Lost during test: " << message;
|
ADD_FAILURE() << "Device lost during test: " << message;
|
||||||
ASSERT(false);
|
ASSERT(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,6 +308,8 @@ class DawnTestBase {
|
|||||||
void StartExpectDeviceError();
|
void StartExpectDeviceError();
|
||||||
bool EndExpectDeviceError();
|
bool EndExpectDeviceError();
|
||||||
|
|
||||||
|
void ExpectDeviceDestruction();
|
||||||
|
|
||||||
bool HasVendorIdFilter() const;
|
bool HasVendorIdFilter() const;
|
||||||
uint32_t GetVendorIdFilter() const;
|
uint32_t GetVendorIdFilter() const;
|
||||||
|
|
||||||
@ -505,6 +507,7 @@ class DawnTestBase {
|
|||||||
static void OnDeviceLost(WGPUDeviceLostReason reason, const char* message, void* userdata);
|
static void OnDeviceLost(WGPUDeviceLostReason reason, const char* message, void* userdata);
|
||||||
bool mExpectError = false;
|
bool mExpectError = false;
|
||||||
bool mError = false;
|
bool mError = false;
|
||||||
|
bool mExpectDestruction = false;
|
||||||
|
|
||||||
std::ostringstream& AddTextureExpectationImpl(const char* file,
|
std::ostringstream& AddTextureExpectationImpl(const char* file,
|
||||||
int line,
|
int line,
|
||||||
|
@ -417,6 +417,66 @@ TEST_P(CreatePipelineAsyncTest, ReleaseDeviceBeforeCallbackOfCreateRenderPipelin
|
|||||||
&task);
|
&task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify there is no error when the device is destroyed before the callback of
|
||||||
|
// CreateComputePipelineAsync() is called.
|
||||||
|
TEST_P(CreatePipelineAsyncTest, DestroyDeviceBeforeCallbackOfCreateComputePipelineAsync) {
|
||||||
|
wgpu::ComputePipelineDescriptor csDesc;
|
||||||
|
csDesc.compute.module = utils::CreateShaderModule(device, R"(
|
||||||
|
[[stage(compute), workgroup_size(1)]] fn main() {
|
||||||
|
})");
|
||||||
|
csDesc.compute.entryPoint = "main";
|
||||||
|
|
||||||
|
device.CreateComputePipelineAsync(
|
||||||
|
&csDesc,
|
||||||
|
[](WGPUCreatePipelineAsyncStatus status, WGPUComputePipeline returnPipeline,
|
||||||
|
const char* message, void* userdata) {
|
||||||
|
EXPECT_EQ(WGPUCreatePipelineAsyncStatus::WGPUCreatePipelineAsyncStatus_DeviceDestroyed,
|
||||||
|
status);
|
||||||
|
|
||||||
|
CreatePipelineAsyncTask* task = static_cast<CreatePipelineAsyncTask*>(userdata);
|
||||||
|
task->computePipeline = wgpu::ComputePipeline::Acquire(returnPipeline);
|
||||||
|
task->isCompleted = true;
|
||||||
|
task->message = message;
|
||||||
|
},
|
||||||
|
&task);
|
||||||
|
ExpectDeviceDestruction();
|
||||||
|
device.Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify there is no error when the device is destroyed before the callback of
|
||||||
|
// CreateRenderPipelineAsync() is called.
|
||||||
|
TEST_P(CreatePipelineAsyncTest, DestroyDeviceBeforeCallbackOfCreateRenderPipelineAsync) {
|
||||||
|
utils::ComboRenderPipelineDescriptor renderPipelineDescriptor;
|
||||||
|
wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"(
|
||||||
|
[[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> {
|
||||||
|
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
||||||
|
})");
|
||||||
|
wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"(
|
||||||
|
[[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> {
|
||||||
|
return vec4<f32>(0.0, 1.0, 0.0, 1.0);
|
||||||
|
})");
|
||||||
|
renderPipelineDescriptor.vertex.module = vsModule;
|
||||||
|
renderPipelineDescriptor.cFragment.module = fsModule;
|
||||||
|
renderPipelineDescriptor.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
|
||||||
|
renderPipelineDescriptor.primitive.topology = wgpu::PrimitiveTopology::PointList;
|
||||||
|
|
||||||
|
device.CreateRenderPipelineAsync(
|
||||||
|
&renderPipelineDescriptor,
|
||||||
|
[](WGPUCreatePipelineAsyncStatus status, WGPURenderPipeline returnPipeline,
|
||||||
|
const char* message, void* userdata) {
|
||||||
|
EXPECT_EQ(WGPUCreatePipelineAsyncStatus::WGPUCreatePipelineAsyncStatus_DeviceDestroyed,
|
||||||
|
status);
|
||||||
|
|
||||||
|
CreatePipelineAsyncTask* task = static_cast<CreatePipelineAsyncTask*>(userdata);
|
||||||
|
task->renderPipeline = wgpu::RenderPipeline::Acquire(returnPipeline);
|
||||||
|
task->isCompleted = true;
|
||||||
|
task->message = message;
|
||||||
|
},
|
||||||
|
&task);
|
||||||
|
ExpectDeviceDestruction();
|
||||||
|
device.Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
// Verify the code path of CreateComputePipelineAsync() to directly return the compute pipeline
|
// Verify the code path of CreateComputePipelineAsync() to directly return the compute pipeline
|
||||||
// object from cache works correctly.
|
// object from cache works correctly.
|
||||||
TEST_P(CreatePipelineAsyncTest, CreateSameComputePipelineTwice) {
|
TEST_P(CreatePipelineAsyncTest, CreateSameComputePipelineTwice) {
|
||||||
|
@ -164,6 +164,19 @@ TEST_P(DestroyTest, DestroyThenSetLabel) {
|
|||||||
buffer.SetLabel(label.c_str());
|
buffer.SetLabel(label.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Device destroy before buffer submit will result in error.
|
||||||
|
TEST_P(DestroyTest, DestroyDeviceBeforeSubmit) {
|
||||||
|
// TODO(crbug.com/dawn/628) Add more comprehensive tests with destroy and backends.
|
||||||
|
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
|
||||||
|
wgpu::CommandBuffer commands = CreateTriangleCommandBuffer();
|
||||||
|
|
||||||
|
// Tests normally don't expect a device lost error, but since we are destroying the device, we
|
||||||
|
// actually do, so we need to override the default device lost callback.
|
||||||
|
ExpectDeviceDestruction();
|
||||||
|
device.Destroy();
|
||||||
|
ASSERT_DEVICE_ERROR(queue.Submit(1, &commands));
|
||||||
|
}
|
||||||
|
|
||||||
DAWN_INSTANTIATE_TEST(DestroyTest,
|
DAWN_INSTANTIATE_TEST(DestroyTest,
|
||||||
D3D12Backend(),
|
D3D12Backend(),
|
||||||
MetalBackend(),
|
MetalBackend(),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user