Simplify the device lifecycle.
What was previously the Device's loss status is now a state that also contains the "being created" state. Its transitions are entirely handled in the frontend which enforces somewhat uniform lifecycles between backends. The backend devices' ShutDownImpl() function is now guaranteed to be called only during the destructor, which leads to further simplification. Previously Destroy() could also be called when the device was first lost. This require complications because, for example, a WGPUBuffer could still exist, and would want to call some resource allocator service after the call to Destroy(). Now destruction of the device's backing API objects is deferred to the destructor. (that's ok as long as the application can't submit any more work). WaitForCompletion is now guaranteed to be called before ShutDownImpl() iff the call to DeviceBase::Initialize was succesful and the backing device not lost. The idea is that after DeviceBase::Initialize, the GPU can have some work enqueued and we need to wait for it to complete before deleting backing API objects. In the future we might also have backend be reentrant, using WebGPU itself to implement parts of the backend. Reentrant calls would only be allowed after DeviceBase::Initialize. Also the DynamicUploader that was special-cased in all backends is now handled entirely by the frontend. Bug: dawn:373 Change-Id: I985417d67727ea3bc11849c999c5ef0e02403223 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/18801 Commit-Queue: Corentin Wallez <cwallez@chromium.org> Reviewed-by: Austin Eng <enga@chromium.org>
This commit is contained in:
parent
7dbfc91d30
commit
73ea1f1106
|
@ -54,6 +54,16 @@ namespace dawn_native {
|
|||
std::unordered_set<Object*, typename Object::HashFunc, typename Object::EqualityFunc>;
|
||||
|
||||
struct DeviceBase::Caches {
|
||||
~Caches() {
|
||||
ASSERT(attachmentStates.empty());
|
||||
ASSERT(bindGroupLayouts.empty());
|
||||
ASSERT(computePipelines.empty());
|
||||
ASSERT(pipelineLayouts.empty());
|
||||
ASSERT(renderPipelines.empty());
|
||||
ASSERT(samplers.empty());
|
||||
ASSERT(shaderModules.empty());
|
||||
}
|
||||
|
||||
ContentLessObjectCache<AttachmentStateBlueprint> attachmentStates;
|
||||
ContentLessObjectCache<BindGroupLayoutBase> bindGroupLayouts;
|
||||
ContentLessObjectCache<ComputePipelineBase> computePipelines;
|
||||
|
@ -77,17 +87,7 @@ namespace dawn_native {
|
|||
}
|
||||
|
||||
DeviceBase::~DeviceBase() {
|
||||
// Devices must explicitly free the uploader
|
||||
ASSERT(mDynamicUploader == nullptr);
|
||||
ASSERT(mDeferredCreateBufferMappedAsyncResults.empty());
|
||||
|
||||
ASSERT(mCaches->attachmentStates.empty());
|
||||
ASSERT(mCaches->bindGroupLayouts.empty());
|
||||
ASSERT(mCaches->computePipelines.empty());
|
||||
ASSERT(mCaches->pipelineLayouts.empty());
|
||||
ASSERT(mCaches->renderPipelines.empty());
|
||||
ASSERT(mCaches->samplers.empty());
|
||||
ASSERT(mCaches->shaderModules.empty());
|
||||
}
|
||||
|
||||
MaybeError DeviceBase::Initialize() {
|
||||
|
@ -99,21 +99,54 @@ namespace dawn_native {
|
|||
mFenceSignalTracker = std::make_unique<FenceSignalTracker>(this);
|
||||
mDynamicUploader = std::make_unique<DynamicUploader>(this);
|
||||
|
||||
// Starting from now the backend can start doing reentrant calls so the device is marked as
|
||||
// alive.
|
||||
mState = State::Alive;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void DeviceBase::BaseDestructor() {
|
||||
if (mLossStatus != LossStatus::Alive) {
|
||||
// if device is already lost, we may still have fences and error scopes to clear since
|
||||
// the time the device was lost, clear them now before we destruct the device.
|
||||
mErrorScopeTracker->Tick(GetCompletedCommandSerial());
|
||||
mFenceSignalTracker->Tick(GetCompletedCommandSerial());
|
||||
return;
|
||||
void DeviceBase::ShutDownBase() {
|
||||
// Disconnect the device, depending on which state we are currently in.
|
||||
switch (mState) {
|
||||
case State::BeingCreated:
|
||||
// The GPU timeline was never started so we don't have to wait.
|
||||
mState = State::Disconnected;
|
||||
break;
|
||||
|
||||
case State::Alive:
|
||||
// Alive is the only state which can have GPU work happening. Wait for all of it to
|
||||
// complete before proceeding with destruction.
|
||||
// Assert that errors are device loss so that we can continue with destruction
|
||||
AssertAndIgnoreDeviceLossError(WaitForIdleForDestruction());
|
||||
mState = State::Disconnected;
|
||||
break;
|
||||
|
||||
case State::BeingDisconnected:
|
||||
// Getting disconnected is a transient state happening in a single API call so there
|
||||
// is always an external reference keeping the Device alive, which means the
|
||||
// destructor cannot run while BeingDisconnected.
|
||||
UNREACHABLE();
|
||||
break;
|
||||
|
||||
case State::Disconnected:
|
||||
break;
|
||||
}
|
||||
// Assert that errors are device loss so that we can continue with destruction
|
||||
AssertAndIgnoreDeviceLossError(WaitForIdleForDestruction());
|
||||
Destroy();
|
||||
mLossStatus = LossStatus::AlreadyLost;
|
||||
|
||||
// The GPU timeline is finished so all services can be freed immediately. They need to be
|
||||
// freed before ShutDownImpl() because they might relinquish resources that will be freed by
|
||||
// backends in the ShutDownImpl() call.
|
||||
// Still tick the ones that might have pending callbacks.
|
||||
mErrorScopeTracker->Tick(GetCompletedCommandSerial());
|
||||
mErrorScopeTracker = nullptr;
|
||||
mFenceSignalTracker->Tick(GetCompletedCommandSerial());
|
||||
mFenceSignalTracker = nullptr;
|
||||
mDynamicUploader = nullptr;
|
||||
|
||||
// Tell the backend that it can free all the objects now that the GPU timeline is empty.
|
||||
ShutDownImpl();
|
||||
|
||||
mCaches = nullptr;
|
||||
}
|
||||
|
||||
void DeviceBase::HandleError(InternalErrorType type, const char* message) {
|
||||
|
@ -121,16 +154,24 @@ namespace dawn_native {
|
|||
// device destruction. We first wait for all previous commands to be completed so that
|
||||
// backend objects can be freed immediately, before handling the loss.
|
||||
if (type == InternalErrorType::Internal) {
|
||||
mLossStatus = LossStatus::BeingLost;
|
||||
// Assert that errors are device loss so that we can continue with destruction.
|
||||
// Move away from the Alive state so that the application cannot use this device
|
||||
// anymore.
|
||||
// TODO(cwallez@chromium.org): Do we need atomics for this to become visible to other
|
||||
// threads in a multithreaded scenario?
|
||||
mState = State::BeingDisconnected;
|
||||
|
||||
// Assert that errors are device losses so that we can continue with destruction.
|
||||
AssertAndIgnoreDeviceLossError(WaitForIdleForDestruction());
|
||||
HandleLoss(message);
|
||||
mState = State::Disconnected;
|
||||
|
||||
// Now everything is as if the device was lost.
|
||||
type = InternalErrorType::DeviceLost;
|
||||
}
|
||||
|
||||
// The device was lost for real, call the loss handler because all the backend objects are
|
||||
// as if no longer in use.
|
||||
if (type == InternalErrorType::DeviceLost) {
|
||||
HandleLoss(message);
|
||||
// The device was lost, call the application callback.
|
||||
if (type == InternalErrorType::DeviceLost && mDeviceLostCallback != nullptr) {
|
||||
mDeviceLostCallback(message, mDeviceLostUserdata);
|
||||
mDeviceLostCallback = nullptr;
|
||||
}
|
||||
|
||||
// Still forward device loss and internal errors to the error scopes so they all reject.
|
||||
|
@ -201,35 +242,27 @@ namespace dawn_native {
|
|||
}
|
||||
|
||||
MaybeError DeviceBase::ValidateIsAlive() const {
|
||||
if (DAWN_LIKELY(mLossStatus == LossStatus::Alive)) {
|
||||
if (DAWN_LIKELY(mState == State::Alive)) {
|
||||
return {};
|
||||
}
|
||||
return DAWN_DEVICE_LOST_ERROR("Device is lost");
|
||||
}
|
||||
|
||||
void DeviceBase::HandleLoss(const char* message) {
|
||||
if (mLossStatus == LossStatus::AlreadyLost) {
|
||||
return;
|
||||
}
|
||||
|
||||
Destroy();
|
||||
mLossStatus = LossStatus::AlreadyLost;
|
||||
|
||||
if (mDeviceLostCallback) {
|
||||
mDeviceLostCallback(message, mDeviceLostUserdata);
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceBase::LoseForTesting() {
|
||||
if (mLossStatus == LossStatus::AlreadyLost) {
|
||||
if (mState != State::Alive) {
|
||||
return;
|
||||
}
|
||||
|
||||
HandleError(InternalErrorType::Internal, "Device lost for testing");
|
||||
}
|
||||
|
||||
DeviceBase::State DeviceBase::GetState() const {
|
||||
return mState;
|
||||
}
|
||||
|
||||
bool DeviceBase::IsLost() const {
|
||||
return mLossStatus != LossStatus::Alive;
|
||||
ASSERT(mState != State::BeingCreated);
|
||||
return mState != State::Alive;
|
||||
}
|
||||
|
||||
AdapterBase* DeviceBase::GetAdapter() const {
|
||||
|
@ -645,6 +678,10 @@ namespace dawn_native {
|
|||
return;
|
||||
}
|
||||
|
||||
// TODO(cwallez@chromium.org): decouple TickImpl from updating the serial so that we can
|
||||
// tick the dynamic uploader before the backend resource allocators. This would allow
|
||||
// reclaiming resources one tick earlier.
|
||||
mDynamicUploader->Deallocate(GetCompletedCommandSerial());
|
||||
mErrorScopeTracker->Tick(GetCompletedCommandSerial());
|
||||
mFenceSignalTracker->Tick(GetCompletedCommandSerial());
|
||||
}
|
||||
|
|
|
@ -158,7 +158,6 @@ namespace dawn_native {
|
|||
const TextureViewDescriptor* descriptor);
|
||||
|
||||
void InjectError(wgpu::ErrorType type, const char* message);
|
||||
|
||||
void Tick();
|
||||
|
||||
void SetDeviceLostCallback(wgpu::DeviceLostCallback callback, void* userdata);
|
||||
|
@ -183,6 +182,27 @@ namespace dawn_native {
|
|||
|
||||
DynamicUploader* GetDynamicUploader() const;
|
||||
|
||||
// The device state which is a combination of creation state and loss state.
|
||||
//
|
||||
// - BeingCreated: the device didn't finish creation yet and the frontend cannot be used
|
||||
// (both for the application calling WebGPU, or re-entrant calls). No work exists on
|
||||
// the GPU timeline.
|
||||
// - Alive: the device is usable and might have work happening on the GPU timeline.
|
||||
// - BeingDisconnected: the device is no longer usable because we are waiting for all
|
||||
// work on the GPU timeline to finish. (this is to make validation prevent the
|
||||
// application from adding more work during the transition from Available to
|
||||
// Disconnected)
|
||||
// - Disconnected: there is no longer work happening on the GPU timeline and the CPU data
|
||||
// structures can be safely destroyed without additional synchronization.
|
||||
enum class State {
|
||||
BeingCreated,
|
||||
Alive,
|
||||
BeingDisconnected,
|
||||
Disconnected,
|
||||
};
|
||||
State GetState() const;
|
||||
bool IsLost() const;
|
||||
|
||||
std::vector<const char*> GetEnabledExtensions() const;
|
||||
std::vector<const char*> GetTogglesUsed() const;
|
||||
bool IsExtensionEnabled(Extension extension) const;
|
||||
|
@ -191,23 +211,13 @@ namespace dawn_native {
|
|||
size_t GetLazyClearCountForTesting();
|
||||
void IncrementLazyClearCountForTesting();
|
||||
void LoseForTesting();
|
||||
bool IsLost() const;
|
||||
|
||||
protected:
|
||||
void SetToggle(Toggle toggle, bool isEnabled);
|
||||
void ForceSetToggle(Toggle toggle, bool isEnabled);
|
||||
|
||||
MaybeError Initialize();
|
||||
void BaseDestructor();
|
||||
|
||||
std::unique_ptr<DynamicUploader> mDynamicUploader;
|
||||
// LossStatus::Alive means the device is alive and can be used normally.
|
||||
// LossStatus::BeingLost means the device is in the process of being lost and should not
|
||||
// accept any new commands.
|
||||
// LossStatus::AlreadyLost means the device has been lost and can no longer be used,
|
||||
// all resources have been freed.
|
||||
enum class LossStatus { Alive, BeingLost, AlreadyLost };
|
||||
LossStatus mLossStatus = LossStatus::Alive;
|
||||
void ShutDownBase();
|
||||
|
||||
private:
|
||||
virtual ResultOrError<BindGroupBase*> CreateBindGroupImpl(
|
||||
|
@ -272,9 +282,9 @@ namespace dawn_native {
|
|||
|
||||
void ConsumeError(std::unique_ptr<ErrorData> error);
|
||||
|
||||
// Destroy is used to clean up and release resources used by device, does not wait for GPU
|
||||
// or check errors.
|
||||
virtual void Destroy() = 0;
|
||||
// ShutDownImpl is used to clean up and release resources used by device, does not wait for
|
||||
// GPU or check errors.
|
||||
virtual void ShutDownImpl() = 0;
|
||||
|
||||
// WaitForIdleForDestruction waits for GPU to finish, checks errors and gets ready for
|
||||
// destruction. This is only used when properly destructing the device. For a real
|
||||
|
@ -282,9 +292,8 @@ namespace dawn_native {
|
|||
// resources.
|
||||
virtual MaybeError WaitForIdleForDestruction() = 0;
|
||||
|
||||
void HandleLoss(const char* message);
|
||||
wgpu::DeviceLostCallback mDeviceLostCallback = nullptr;
|
||||
void* mDeviceLostUserdata;
|
||||
void* mDeviceLostUserdata = nullptr;
|
||||
|
||||
AdapterBase* mAdapter = nullptr;
|
||||
|
||||
|
@ -303,11 +312,13 @@ namespace dawn_native {
|
|||
void* userdata;
|
||||
};
|
||||
|
||||
std::unique_ptr<DynamicUploader> mDynamicUploader;
|
||||
std::unique_ptr<ErrorScopeTracker> mErrorScopeTracker;
|
||||
std::unique_ptr<FenceSignalTracker> mFenceSignalTracker;
|
||||
std::vector<DeferredCreateBufferMappedAsync> mDeferredCreateBufferMappedAsyncResults;
|
||||
|
||||
uint32_t mRefCount = 1;
|
||||
State mState = State::BeingCreated;
|
||||
|
||||
FormatTable mFormatTable;
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
#include "common/Assert.h"
|
||||
#include "dawn_native/BackendConnection.h"
|
||||
#include "dawn_native/DynamicUploader.h"
|
||||
#include "dawn_native/ErrorData.h"
|
||||
#include "dawn_native/d3d12/AdapterD3D12.h"
|
||||
#include "dawn_native/d3d12/BackendD3D12.h"
|
||||
|
@ -118,7 +117,7 @@ namespace dawn_native { namespace d3d12 {
|
|||
}
|
||||
|
||||
Device::~Device() {
|
||||
BaseDestructor();
|
||||
ShutDownBase();
|
||||
}
|
||||
|
||||
ComPtr<ID3D12Device> Device::GetD3D12Device() const {
|
||||
|
@ -194,10 +193,6 @@ namespace dawn_native { namespace d3d12 {
|
|||
// Perform cleanup operations to free unused objects
|
||||
mCompletedSerial = mFence->GetCompletedValue();
|
||||
|
||||
// Uploader should tick before the resource allocator
|
||||
// as it enqueued resources to be released.
|
||||
mDynamicUploader->Deallocate(mCompletedSerial);
|
||||
|
||||
mResourceAllocatorManager->Tick(mCompletedSerial);
|
||||
DAWN_TRY(mCommandAllocatorManager->Tick(mCompletedSerial));
|
||||
mShaderVisibleDescriptorAllocator->Tick(mCompletedSerial);
|
||||
|
@ -439,16 +434,12 @@ namespace dawn_native { namespace d3d12 {
|
|||
return {};
|
||||
}
|
||||
|
||||
void Device::Destroy() {
|
||||
ASSERT(mLossStatus != LossStatus::AlreadyLost);
|
||||
void Device::ShutDownImpl() {
|
||||
ASSERT(GetState() == State::Disconnected);
|
||||
|
||||
// Immediately forget about all pending commands
|
||||
mPendingCommands.Release();
|
||||
|
||||
// Free services explicitly so that they can free D3D12 resources before destruction of the
|
||||
// device.
|
||||
mDynamicUploader = nullptr;
|
||||
|
||||
// GPU is no longer executing commands. Existing objects do not get freed until the device
|
||||
// is destroyed. To ensure objects are always released, force the completed serial to be
|
||||
// MAX.
|
||||
|
|
|
@ -140,7 +140,7 @@ namespace dawn_native { namespace d3d12 {
|
|||
TextureBase* texture,
|
||||
const TextureViewDescriptor* descriptor) override;
|
||||
|
||||
void Destroy() override;
|
||||
void ShutDownImpl() override;
|
||||
MaybeError WaitForIdleForDestruction() override;
|
||||
|
||||
Serial mCompletedSerial = 0;
|
||||
|
|
|
@ -101,7 +101,7 @@ namespace dawn_native { namespace metal {
|
|||
const TextureViewDescriptor* descriptor) override;
|
||||
|
||||
void InitTogglesFromDriver();
|
||||
void Destroy() override;
|
||||
void ShutDownImpl() override;
|
||||
MaybeError WaitForIdleForDestruction() override;
|
||||
|
||||
id<MTLDevice> mMtlDevice = nil;
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
#include "dawn_native/BackendConnection.h"
|
||||
#include "dawn_native/BindGroupLayout.h"
|
||||
#include "dawn_native/DynamicUploader.h"
|
||||
#include "dawn_native/ErrorData.h"
|
||||
#include "dawn_native/metal/BindGroupLayoutMTL.h"
|
||||
#include "dawn_native/metal/BindGroupMTL.h"
|
||||
|
@ -58,7 +57,7 @@ namespace dawn_native { namespace metal {
|
|||
}
|
||||
|
||||
Device::~Device() {
|
||||
BaseDestructor();
|
||||
ShutDownBase();
|
||||
}
|
||||
|
||||
MaybeError Device::Initialize() {
|
||||
|
@ -174,7 +173,6 @@ namespace dawn_native { namespace metal {
|
|||
MaybeError Device::TickImpl() {
|
||||
Serial completedSerial = GetCompletedCommandSerial();
|
||||
|
||||
mDynamicUploader->Deallocate(completedSerial);
|
||||
mMapTracker->Tick(completedSerial);
|
||||
|
||||
if (mCommandContext.GetCommands() != nil) {
|
||||
|
@ -317,13 +315,12 @@ namespace dawn_native { namespace metal {
|
|||
return {};
|
||||
}
|
||||
|
||||
void Device::Destroy() {
|
||||
ASSERT(mLossStatus != LossStatus::AlreadyLost);
|
||||
void Device::ShutDownImpl() {
|
||||
ASSERT(GetState() == State::Disconnected);
|
||||
|
||||
[mCommandContext.AcquireCommands() release];
|
||||
|
||||
mMapTracker = nullptr;
|
||||
mDynamicUploader = nullptr;
|
||||
|
||||
[mCommandQueue release];
|
||||
mCommandQueue = nil;
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
#include "dawn_native/BackendConnection.h"
|
||||
#include "dawn_native/Commands.h"
|
||||
#include "dawn_native/DynamicUploader.h"
|
||||
#include "dawn_native/ErrorData.h"
|
||||
#include "dawn_native/Instance.h"
|
||||
#include "dawn_native/Surface.h"
|
||||
|
@ -86,10 +85,7 @@ namespace dawn_native { namespace null {
|
|||
}
|
||||
|
||||
Device::~Device() {
|
||||
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);
|
||||
ShutDownBase();
|
||||
}
|
||||
|
||||
MaybeError Device::Initialize() {
|
||||
|
@ -182,12 +178,13 @@ namespace dawn_native { namespace null {
|
|||
return std::move(stagingBuffer);
|
||||
}
|
||||
|
||||
void Device::Destroy() {
|
||||
ASSERT(mLossStatus != LossStatus::AlreadyLost);
|
||||
|
||||
mDynamicUploader = nullptr;
|
||||
void Device::ShutDownImpl() {
|
||||
ASSERT(GetState() == State::Disconnected);
|
||||
|
||||
// Clear pending operations before checking mMemoryUsage because some operations keep a
|
||||
// reference to Buffers.
|
||||
mPendingOperations.clear();
|
||||
ASSERT(mMemoryUsage == 0);
|
||||
}
|
||||
|
||||
MaybeError Device::WaitForIdleForDestruction() {
|
||||
|
|
|
@ -138,7 +138,7 @@ namespace dawn_native { namespace null {
|
|||
TextureBase* texture,
|
||||
const TextureViewDescriptor* descriptor) override;
|
||||
|
||||
void Destroy() override;
|
||||
void ShutDownImpl() override;
|
||||
MaybeError WaitForIdleForDestruction() override;
|
||||
|
||||
Serial mCompletedSerial = 0;
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
|
||||
#include "dawn_native/BackendConnection.h"
|
||||
#include "dawn_native/BindGroupLayout.h"
|
||||
#include "dawn_native/DynamicUploader.h"
|
||||
#include "dawn_native/ErrorData.h"
|
||||
#include "dawn_native/StagingBuffer.h"
|
||||
#include "dawn_native/opengl/BindGroupGL.h"
|
||||
#include "dawn_native/opengl/BindGroupLayoutGL.h"
|
||||
#include "dawn_native/opengl/BufferGL.h"
|
||||
|
@ -49,7 +49,7 @@ namespace dawn_native { namespace opengl {
|
|||
}
|
||||
|
||||
Device::~Device() {
|
||||
BaseDestructor();
|
||||
ShutDownBase();
|
||||
}
|
||||
|
||||
MaybeError Device::Initialize() {
|
||||
|
@ -204,15 +204,13 @@ namespace dawn_native { namespace opengl {
|
|||
return DAWN_UNIMPLEMENTED_ERROR("Device unable to copy from staging buffer.");
|
||||
}
|
||||
|
||||
void Device::Destroy() {
|
||||
ASSERT(mLossStatus != LossStatus::AlreadyLost);
|
||||
void Device::ShutDownImpl() {
|
||||
ASSERT(GetState() == State::Disconnected);
|
||||
|
||||
// Some operations might have been started since the last submit and waiting
|
||||
// on a serial that doesn't have a corresponding fence enqueued. Force all
|
||||
// operations to look as if they were completed (because they were).
|
||||
mCompletedSerial = mLastSubmittedSerial + 1;
|
||||
|
||||
mDynamicUploader = nullptr;
|
||||
}
|
||||
|
||||
MaybeError Device::WaitForIdleForDestruction() {
|
||||
|
|
|
@ -97,7 +97,7 @@ namespace dawn_native { namespace opengl {
|
|||
|
||||
void InitTogglesFromDriver();
|
||||
void CheckPassedFences();
|
||||
void Destroy() override;
|
||||
void ShutDownImpl() override;
|
||||
MaybeError WaitForIdleForDestruction() override;
|
||||
|
||||
Serial mCompletedSerial = 0;
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
#include "common/Platform.h"
|
||||
#include "dawn_native/BackendConnection.h"
|
||||
#include "dawn_native/Commands.h"
|
||||
#include "dawn_native/DynamicUploader.h"
|
||||
#include "dawn_native/Error.h"
|
||||
#include "dawn_native/ErrorData.h"
|
||||
#include "dawn_native/VulkanBackend.h"
|
||||
|
@ -54,29 +53,37 @@ namespace dawn_native { namespace vulkan {
|
|||
Device::Device(Adapter* adapter, const DeviceDescriptor* descriptor)
|
||||
: DeviceBase(adapter, descriptor) {
|
||||
InitTogglesFromDriver();
|
||||
|
||||
// Set the device as lost until successfully created.
|
||||
mLossStatus = LossStatus::AlreadyLost;
|
||||
}
|
||||
|
||||
MaybeError Device::Initialize() {
|
||||
// Copy the adapter's device info to the device so that we can change the "knobs"
|
||||
mDeviceInfo = ToBackend(GetAdapter())->GetDeviceInfo();
|
||||
|
||||
// Initialize the "instance" procs of our local function table.
|
||||
VulkanFunctions* functions = GetMutableFunctions();
|
||||
*functions = ToBackend(GetAdapter())->GetBackend()->GetFunctions();
|
||||
|
||||
VkPhysicalDevice physicalDevice = ToBackend(GetAdapter())->GetPhysicalDevice();
|
||||
// Two things are crucial if device initialization fails: the function pointers to destroy
|
||||
// objects, and the fence deleter that calls these functions. Do not do anything before
|
||||
// these two are set up, so that a failed initialization doesn't cause a crash in
|
||||
// ShutDownImpl()
|
||||
{
|
||||
VkPhysicalDevice physicalDevice = ToBackend(GetAdapter())->GetPhysicalDevice();
|
||||
|
||||
VulkanDeviceKnobs usedDeviceKnobs = {};
|
||||
DAWN_TRY_ASSIGN(usedDeviceKnobs, CreateDevice(physicalDevice));
|
||||
*static_cast<VulkanDeviceKnobs*>(&mDeviceInfo) = usedDeviceKnobs;
|
||||
VulkanDeviceKnobs usedDeviceKnobs = {};
|
||||
DAWN_TRY_ASSIGN(usedDeviceKnobs, CreateDevice(physicalDevice));
|
||||
*static_cast<VulkanDeviceKnobs*>(&mDeviceInfo) = usedDeviceKnobs;
|
||||
|
||||
DAWN_TRY(functions->LoadDeviceProcs(mVkDevice, mDeviceInfo));
|
||||
DAWN_TRY(functions->LoadDeviceProcs(mVkDevice, mDeviceInfo));
|
||||
|
||||
// The queue can be loaded before the fenced deleter because their lifetime is tied to
|
||||
// the device.
|
||||
GatherQueueFromDevice();
|
||||
|
||||
mDeleter = std::make_unique<FencedDeleter>(this);
|
||||
}
|
||||
|
||||
GatherQueueFromDevice();
|
||||
mDescriptorSetService = std::make_unique<DescriptorSetService>(this);
|
||||
mDeleter = std::make_unique<FencedDeleter>(this);
|
||||
mMapRequestTracker = std::make_unique<MapRequestTracker>(this);
|
||||
mRenderPassCache = std::make_unique<RenderPassCache>(this);
|
||||
mResourceMemoryAllocator = std::make_unique<ResourceMemoryAllocator>(this);
|
||||
|
@ -94,34 +101,7 @@ namespace dawn_native { namespace vulkan {
|
|||
}
|
||||
|
||||
Device::~Device() {
|
||||
BaseDestructor();
|
||||
|
||||
mDescriptorSetService = nullptr;
|
||||
|
||||
// The frontend asserts DynamicUploader is destructed by the backend.
|
||||
// It is usually destructed in Destroy(), but Destroy isn't always called if device
|
||||
// initialization failed.
|
||||
mDynamicUploader = nullptr;
|
||||
|
||||
// We still need to properly handle Vulkan object deletion even if the device has been lost,
|
||||
// so the Deleter and vkDevice cannot be destroyed in Device::Destroy().
|
||||
// We need handle deleting all child objects by calling Tick() again with a large serial to
|
||||
// force all operations to look as if they were completed, and delete all objects before
|
||||
// destroying the Deleter and vkDevice.
|
||||
// The Deleter may be null if initialization failed.
|
||||
if (mDeleter != nullptr) {
|
||||
mCompletedSerial = std::numeric_limits<Serial>::max();
|
||||
mDeleter->Tick(mCompletedSerial);
|
||||
mDeleter = nullptr;
|
||||
}
|
||||
|
||||
// VkQueues are destroyed when the VkDevice is destroyed
|
||||
// The VkDevice is needed to destroy child objects, so it must be destroyed last after all
|
||||
// child objects have been deleted.
|
||||
if (mVkDevice != VK_NULL_HANDLE) {
|
||||
fn.DestroyDevice(mVkDevice, nullptr);
|
||||
mVkDevice = VK_NULL_HANDLE;
|
||||
}
|
||||
ShutDownBase();
|
||||
}
|
||||
|
||||
ResultOrError<BindGroupBase*> Device::CreateBindGroupImpl(
|
||||
|
@ -198,13 +178,7 @@ namespace dawn_native { namespace vulkan {
|
|||
|
||||
mDescriptorSetService->Tick(mCompletedSerial);
|
||||
mMapRequestTracker->Tick(mCompletedSerial);
|
||||
|
||||
// Uploader should tick before the resource allocator
|
||||
// as it enqueues resources to be released.
|
||||
mDynamicUploader->Deallocate(mCompletedSerial);
|
||||
|
||||
mResourceMemoryAllocator->Tick(mCompletedSerial);
|
||||
|
||||
mDeleter->Tick(mCompletedSerial);
|
||||
|
||||
if (mRecordingContext.used) {
|
||||
|
@ -422,8 +396,6 @@ namespace dawn_native { namespace vulkan {
|
|||
DAWN_TRY(CheckVkSuccess(fn.CreateDevice(physicalDevice, &createInfo, nullptr, &mVkDevice),
|
||||
"vkCreateDevice"));
|
||||
|
||||
// Device created. Mark it as alive.
|
||||
mLossStatus = LossStatus::Alive;
|
||||
return usedKnobs;
|
||||
}
|
||||
|
||||
|
@ -774,8 +746,27 @@ namespace dawn_native { namespace vulkan {
|
|||
return {};
|
||||
}
|
||||
|
||||
void Device::Destroy() {
|
||||
ASSERT(mLossStatus != LossStatus::AlreadyLost);
|
||||
void Device::ShutDownImpl() {
|
||||
ASSERT(GetState() == State::Disconnected);
|
||||
|
||||
// We failed during initialization so early that we don't even have a VkDevice. There is
|
||||
// nothing to do.
|
||||
if (mVkDevice == VK_NULL_HANDLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The deleter is the second thing we initialize. If it is not present, it means that
|
||||
// only the VkDevice was created and nothing else. Destroy the device and do nothing else
|
||||
// because the function pointers might not have been loaded (and there is nothing to
|
||||
// destroy anyway).
|
||||
if (mDeleter == nullptr) {
|
||||
fn.DestroyDevice(mVkDevice, nullptr);
|
||||
mVkDevice = VK_NULL_HANDLE;
|
||||
return;
|
||||
}
|
||||
|
||||
// Enough of the Device's initialization happened that we can now do regular robust
|
||||
// deinitialization.
|
||||
|
||||
// Immediately tag the recording context as unused so we don't try to submit it in Tick.
|
||||
mRecordingContext.used = false;
|
||||
|
@ -810,18 +801,30 @@ namespace dawn_native { namespace vulkan {
|
|||
}
|
||||
mUnusedFences.clear();
|
||||
|
||||
// Free services explicitly so that they can free Vulkan objects before vkDestroyDevice
|
||||
mDynamicUploader = nullptr;
|
||||
|
||||
// Releasing the uploader enqueues buffers to be released.
|
||||
// Call Tick() again to clear them before releasing the deleter.
|
||||
mDeleter->Tick(mCompletedSerial);
|
||||
|
||||
mMapRequestTracker = nullptr;
|
||||
mDescriptorSetService = nullptr;
|
||||
|
||||
// The VkRenderPasses in the cache can be destroyed immediately since all commands referring
|
||||
// to them are guaranteed to be finished executing.
|
||||
mRenderPassCache = nullptr;
|
||||
|
||||
// We need handle deleting all child objects by calling Tick() again with a large serial to
|
||||
// force all operations to look as if they were completed, and delete all objects before
|
||||
// destroying the Deleter and vkDevice.
|
||||
ASSERT(mDeleter != nullptr);
|
||||
mDeleter->Tick(std::numeric_limits<Serial>::max());
|
||||
mDeleter = nullptr;
|
||||
|
||||
// VkQueues are destroyed when the VkDevice is destroyed
|
||||
// The VkDevice is needed to destroy child objects, so it must be destroyed last after all
|
||||
// child objects have been deleted.
|
||||
ASSERT(mVkDevice != VK_NULL_HANDLE);
|
||||
fn.DestroyDevice(mVkDevice, nullptr);
|
||||
mVkDevice = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
}} // namespace dawn_native::vulkan
|
||||
|
|
|
@ -135,7 +135,7 @@ namespace dawn_native { namespace vulkan {
|
|||
void InitTogglesFromDriver();
|
||||
void ApplyDepth24PlusS8Toggle();
|
||||
|
||||
void Destroy() override;
|
||||
void ShutDownImpl() override;
|
||||
MaybeError WaitForIdleForDestruction() override;
|
||||
|
||||
// To make it easier to use fn it is a public const member. However
|
||||
|
|
Loading…
Reference in New Issue