Make implicit resource destruction thread-safe
- synchronize access to device memory allocators,
deletion queues, and residency manager
- Remove IsAlive check from ApiObjectBase::Destroy.
Untrack already checks if an object is live and removes
it from the list in a thread-safe way.
- Add basic atomics to ExecutionQueue serials.
They may be read during object deletion concurrently
while they are updated.
Bug: dawn:2069
Change-Id: I48a2121ca27dfbab1db97303b80b1a0039880483
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/153481
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Loko Kung <lokokung@google.com>
diff --git a/src/dawn/native/Buffer.cpp b/src/dawn/native/Buffer.cpp
index c88da57..f90786d 100644
--- a/src/dawn/native/Buffer.cpp
+++ b/src/dawn/native/Buffer.cpp
@@ -239,6 +239,13 @@
}
void BufferBase::DestroyImpl() {
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the buffer is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the buffer.
+ // - It may be called when the last ref to the buffer is dropped and the buffer
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the buffer since there are no other live refs.
if (mState == BufferState::Mapped || mState == BufferState::PendingMap) {
UnmapInternal(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback);
} else if (mState == BufferState::MappedAtCreation) {
diff --git a/src/dawn/native/ExecutionQueue.cpp b/src/dawn/native/ExecutionQueue.cpp
index 0aa1518..198ae95 100644
--- a/src/dawn/native/ExecutionQueue.cpp
+++ b/src/dawn/native/ExecutionQueue.cpp
@@ -17,11 +17,11 @@
namespace dawn::native {
ExecutionSerial ExecutionQueueBase::GetPendingCommandSerial() const {
- return mLastSubmittedSerial + ExecutionSerial(1);
+ return ExecutionSerial(mLastSubmittedSerial.load(std::memory_order_acquire) + 1);
}
ExecutionSerial ExecutionQueueBase::GetLastSubmittedCommandSerial() const {
- return mLastSubmittedSerial;
+ return ExecutionSerial(mLastSubmittedSerial.load(std::memory_order_acquire));
}
ExecutionSerial ExecutionQueueBase::GetCompletedCommandSerial() const {
@@ -32,7 +32,8 @@
ExecutionSerial completedSerial;
DAWN_TRY_ASSIGN(completedSerial, CheckAndUpdateCompletedSerials());
- DAWN_ASSERT(completedSerial <= mLastSubmittedSerial);
+ DAWN_ASSERT(completedSerial <=
+ ExecutionSerial(mLastSubmittedSerial.load(std::memory_order_acquire)));
// completedSerial should not be less than mCompletedSerial unless it is 0.
// It can be 0 when there's no fences to check.
DAWN_ASSERT(completedSerial >= mCompletedSerial || completedSerial == ExecutionSerial(0));
@@ -46,16 +47,18 @@
void ExecutionQueueBase::AssumeCommandsComplete() {
// Bump serials so any pending callbacks can be fired.
- mLastSubmittedSerial++;
- mCompletedSerial = mLastSubmittedSerial;
+ uint64_t prev = mLastSubmittedSerial.fetch_add(1u, std::memory_order_release);
+ mCompletedSerial = ExecutionSerial(prev + 1);
}
void ExecutionQueueBase::IncrementLastSubmittedCommandSerial() {
- mLastSubmittedSerial++;
+ mLastSubmittedSerial.fetch_add(1u, std::memory_order_release);
}
bool ExecutionQueueBase::HasScheduledCommands() const {
- return mLastSubmittedSerial > mCompletedSerial || HasPendingCommands();
+ return ExecutionSerial(mLastSubmittedSerial.load(std::memory_order_acquire)) >
+ mCompletedSerial ||
+ HasPendingCommands();
}
// All prevously submitted works at the moment will supposedly complete at this serial.
diff --git a/src/dawn/native/ExecutionQueue.h b/src/dawn/native/ExecutionQueue.h
index 3c29a08..80664fb 100644
--- a/src/dawn/native/ExecutionQueue.h
+++ b/src/dawn/native/ExecutionQueue.h
@@ -15,6 +15,8 @@
#ifndef SRC_DAWN_NATIVE_EXECUTIONQUEUE_H_
#define SRC_DAWN_NATIVE_EXECUTIONQUEUE_H_
+#include <atomic>
+
#include "dawn/native/Error.h"
#include "dawn/native/IntegerTypes.h"
@@ -22,6 +24,8 @@
// Represents an engine which processes a stream of GPU work. It handles the tracking and
// update of the various ExecutionSerials related to that work.
+// TODO(dawn:831, dawn:1413): Make usage of the ExecutionQueue thread-safe. Right now it is
+// only partially safe - where observation of the last-submitted and pending serials is atomic.
class ExecutionQueueBase {
public:
// The latest serial known to have completed execution on the queue.
@@ -70,7 +74,7 @@
// During device removal, the serials could be artificially incremented
// to make it appear as if commands have been compeleted.
ExecutionSerial mCompletedSerial = kBeginningOfGPUTime;
- ExecutionSerial mLastSubmittedSerial = kBeginningOfGPUTime;
+ std::atomic<uint64_t> mLastSubmittedSerial = static_cast<uint64_t>(kBeginningOfGPUTime);
// Indicates whether the backend has pending commands to be submitted as soon as possible.
virtual bool HasPendingCommands() const = 0;
diff --git a/src/dawn/native/ExternalTexture.cpp b/src/dawn/native/ExternalTexture.cpp
index 1f64fdf..3a296a2 100644
--- a/src/dawn/native/ExternalTexture.cpp
+++ b/src/dawn/native/ExternalTexture.cpp
@@ -368,6 +368,13 @@
}
void ExternalTextureBase::DestroyImpl() {
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the texture is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the texture.
+ // - It may be called when the last ref to the texture is dropped and the texture
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the texture since there are no other live refs.
mState = ExternalTextureState::Destroyed;
}
diff --git a/src/dawn/native/ObjectBase.cpp b/src/dawn/native/ObjectBase.cpp
index 3f7f11c..974ad10 100644
--- a/src/dawn/native/ObjectBase.cpp
+++ b/src/dawn/native/ObjectBase.cpp
@@ -136,9 +136,6 @@
}
void ApiObjectBase::Destroy() {
- if (!IsAlive()) {
- return;
- }
ApiObjectList* list = GetObjectTrackingList();
DAWN_ASSERT(list != nullptr);
if (list->Untrack(this)) {
diff --git a/src/dawn/native/QuerySet.cpp b/src/dawn/native/QuerySet.cpp
index a58f85f..6e76615 100644
--- a/src/dawn/native/QuerySet.cpp
+++ b/src/dawn/native/QuerySet.cpp
@@ -124,6 +124,13 @@
}
void QuerySetBase::DestroyImpl() {
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the query set is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the query set.
+ // - It may be called when the last ref to the query set is dropped and it
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the query set since there are no other live refs.
mState = QuerySetState::Destroyed;
}
diff --git a/src/dawn/native/Queue.cpp b/src/dawn/native/Queue.cpp
index 6c1c3ca..183a92b 100644
--- a/src/dawn/native/Queue.cpp
+++ b/src/dawn/native/Queue.cpp
@@ -238,7 +238,7 @@
: ApiObjectBase(device, tag, label) {}
QueueBase::~QueueBase() {
- DAWN_ASSERT(mTasksInFlight.Empty());
+ DAWN_ASSERT(mTasksInFlight->Empty());
}
void QueueBase::DestroyImpl() {}
@@ -336,7 +336,7 @@
task->SetFinishedSerial(GetCompletedCommandSerial());
GetDevice()->GetCallbackTaskManager()->AddCallbackTask(std::move(task));
} else {
- mTasksInFlight.Enqueue(std::move(task), serial);
+ mTasksInFlight->Enqueue(std::move(task), serial);
}
}
@@ -346,7 +346,7 @@
}
void QueueBase::TrackPendingTask(std::unique_ptr<TrackTaskCallback> task) {
- mTasksInFlight.Enqueue(std::move(task), GetPendingCommandSerial());
+ mTasksInFlight->Enqueue(std::move(task), GetPendingCommandSerial());
}
void QueueBase::Tick(ExecutionSerial finishedSerial) {
@@ -359,11 +359,12 @@
uint64_t(finishedSerial));
std::vector<std::unique_ptr<TrackTaskCallback>> tasks;
- for (auto& task : mTasksInFlight.IterateUpTo(finishedSerial)) {
- tasks.push_back(std::move(task));
- }
- mTasksInFlight.ClearUpTo(finishedSerial);
-
+ mTasksInFlight.Use([&](auto tasksInFlight) {
+ for (auto& task : tasksInFlight->IterateUpTo(finishedSerial)) {
+ tasks.push_back(std::move(task));
+ }
+ tasksInFlight->ClearUpTo(finishedSerial);
+ });
// Tasks' serials have passed. Move them to the callback task manager. They
// are ready to be called.
for (auto& task : tasks) {
@@ -373,11 +374,13 @@
}
void QueueBase::HandleDeviceLoss() {
- for (auto& task : mTasksInFlight.IterateAll()) {
- task->OnDeviceLoss();
- GetDevice()->GetCallbackTaskManager()->AddCallbackTask(std::move(task));
- }
- mTasksInFlight.Clear();
+ mTasksInFlight.Use([&](auto tasksInFlight) {
+ for (auto& task : tasksInFlight->IterateAll()) {
+ task->OnDeviceLoss();
+ GetDevice()->GetCallbackTaskManager()->AddCallbackTask(std::move(task));
+ }
+ tasksInFlight->Clear();
+ });
}
void QueueBase::APIWriteBuffer(BufferBase* buffer,
diff --git a/src/dawn/native/Queue.h b/src/dawn/native/Queue.h
index ce473f8..6fd2260 100644
--- a/src/dawn/native/Queue.h
+++ b/src/dawn/native/Queue.h
@@ -17,6 +17,7 @@
#include <memory>
+#include "dawn/common/MutexProtected.h"
#include "dawn/common/SerialMap.h"
#include "dawn/native/CallbackTaskManager.h"
#include "dawn/native/Error.h"
@@ -137,7 +138,7 @@
MaybeError SubmitInternal(uint32_t commandCount, CommandBufferBase* const* commands);
- SerialMap<ExecutionSerial, std::unique_ptr<TrackTaskCallback>> mTasksInFlight;
+ MutexProtected<SerialMap<ExecutionSerial, std::unique_ptr<TrackTaskCallback>>> mTasksInFlight;
};
} // namespace dawn::native
diff --git a/src/dawn/native/SwapChain.h b/src/dawn/native/SwapChain.h
index 362e002..fd2a1b1 100644
--- a/src/dawn/native/SwapChain.h
+++ b/src/dawn/native/SwapChain.h
@@ -63,6 +63,14 @@
TextureViewBase* APIGetCurrentTextureView();
void APIPresent();
+ // TODO(crbug.com/dawn/831):
+ // APIRelease() can be called without any synchronization guarantees so we need to use a Release
+ // method that will call LockAndDeleteThis() on destruction.
+ // This is because losing the last reference to the SwapChain will detach its surface which
+ // explicitly destroys the current texture. Explicit destruction of textures is not thread safe
+ // yet.
+ void APIRelease() { ReleaseAndLockBeforeDestroy(); }
+
uint32_t GetWidth() const;
uint32_t GetHeight() const;
wgpu::TextureFormat GetFormat() const;
diff --git a/src/dawn/native/Texture.cpp b/src/dawn/native/Texture.cpp
index 5972cef..22743de 100644
--- a/src/dawn/native/Texture.cpp
+++ b/src/dawn/native/Texture.cpp
@@ -713,6 +713,16 @@
mFormatEnumForReflection(descriptor->format) {}
void TextureBase::DestroyImpl() {
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the texture is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the texture.
+ // - Losing the last reference to a swap chain will also call APIDestroy on its
+ // current texture. This is protected by acquiring the global device lock on
+ // the last release. That lock can be removed when APIDestroy is made thread-safe.
+ // - It may be called when the last ref to the texture is dropped and the texture
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the texture since there are no other live refs.
mState.destroyed = true;
// Destroy all of the views associated with the texture as well.
@@ -1158,8 +1168,14 @@
}
ApiObjectList* TextureViewBase::GetObjectTrackingList() {
- DAWN_ASSERT(!IsError());
- return mTexture->GetViewTrackingList();
+ if (mTexture != nullptr) {
+ return mTexture->GetViewTrackingList();
+ }
+ // Return the base device list for error objects so that
+ // the list is never null. Error texture views are never tracked,
+ // so liveness checks will always return false.
+ DAWN_ASSERT(IsError());
+ return ApiObjectBase::GetObjectTrackingList();
}
} // namespace dawn::native
diff --git a/src/dawn/native/d3d/DeviceD3D.cpp b/src/dawn/native/d3d/DeviceD3D.cpp
index 3f80955..dddc36f 100644
--- a/src/dawn/native/d3d/DeviceD3D.cpp
+++ b/src/dawn/native/d3d/DeviceD3D.cpp
@@ -39,6 +39,13 @@
}
void Device::DestroyImpl() {
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the device is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the device.
+ // - It may be called when the last ref to the device is dropped and the device
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the device since there are no other live refs.
while (!mExternalImageList.empty()) {
d3d::ExternalImageDXGIImpl* externalImage = mExternalImageList.head()->value();
// ExternalImageDXGIImpl::DestroyInternal() calls RemoveFromList().
diff --git a/src/dawn/native/d3d11/BufferD3D11.cpp b/src/dawn/native/d3d11/BufferD3D11.cpp
index a58b322..0f34450 100644
--- a/src/dawn/native/d3d11/BufferD3D11.cpp
+++ b/src/dawn/native/d3d11/BufferD3D11.cpp
@@ -287,6 +287,13 @@
}
void Buffer::DestroyImpl() {
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the buffer is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the buffer.
+ // - It may be called when the last ref to the buffer is dropped and the buffer
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the buffer since there are no other live refs.
BufferBase::DestroyImpl();
if (mMappedData) {
UnmapInternal();
diff --git a/src/dawn/native/d3d11/DeviceD3D11.cpp b/src/dawn/native/d3d11/DeviceD3D11.cpp
index a35922b..47ba460 100644
--- a/src/dawn/native/d3d11/DeviceD3D11.cpp
+++ b/src/dawn/native/d3d11/DeviceD3D11.cpp
@@ -434,6 +434,13 @@
}
void Device::DestroyImpl() {
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the device is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the device.
+ // - It may be called when the last ref to the device is dropped and the device
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the device since there are no other live refs.
DAWN_ASSERT(GetState() == State::Disconnected);
Base::DestroyImpl();
diff --git a/src/dawn/native/d3d11/QuerySetD3D11.cpp b/src/dawn/native/d3d11/QuerySetD3D11.cpp
index 107da2e..986055b 100644
--- a/src/dawn/native/d3d11/QuerySetD3D11.cpp
+++ b/src/dawn/native/d3d11/QuerySetD3D11.cpp
@@ -48,6 +48,13 @@
}
void QuerySet::DestroyImpl() {
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the query set is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the query set.
+ // - It may be called when the last ref to the query set is dropped and it
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the query set since there are no other live refs.
QuerySetBase::DestroyImpl();
mPredicates.clear();
}
diff --git a/src/dawn/native/d3d11/TextureD3D11.cpp b/src/dawn/native/d3d11/TextureD3D11.cpp
index b56b0f4..a7f74fe 100644
--- a/src/dawn/native/d3d11/TextureD3D11.cpp
+++ b/src/dawn/native/d3d11/TextureD3D11.cpp
@@ -365,6 +365,13 @@
Texture::~Texture() = default;
void Texture::DestroyImpl() {
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the texture is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the texture.
+ // - It may be called when the last ref to the texture is dropped and the texture
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the texture since there are no other live refs.
TextureBase::DestroyImpl();
mD3d11Resource = nullptr;
}
diff --git a/src/dawn/native/d3d12/BufferD3D12.cpp b/src/dawn/native/d3d12/BufferD3D12.cpp
index 0985c6a..e082948 100644
--- a/src/dawn/native/d3d12/BufferD3D12.cpp
+++ b/src/dawn/native/d3d12/BufferD3D12.cpp
@@ -459,6 +459,13 @@
}
void Buffer::DestroyImpl() {
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the buffer is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the buffer.
+ // - It may be called when the last ref to the buffer is dropped and the buffer
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the buffer since there are no other live refs.
if (mMappedData != nullptr) {
// If the buffer is currently mapped, unmap without flushing the writes to the GPU
// since the buffer cannot be used anymore. UnmapImpl checks mWrittenRange to know
diff --git a/src/dawn/native/d3d12/DeviceD3D12.cpp b/src/dawn/native/d3d12/DeviceD3D12.cpp
index 7da08d2..781c0f9 100644
--- a/src/dawn/native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn/native/d3d12/DeviceD3D12.cpp
@@ -141,8 +141,8 @@
mSamplerHeapCache = std::make_unique<SamplerHeapCache>(this);
- mResidencyManager = std::make_unique<ResidencyManager>(this);
- mResourceAllocatorManager = std::make_unique<ResourceAllocatorManager>(this);
+ mResidencyManager = std::make_unique<MutexProtected<ResidencyManager>>(this);
+ mResourceAllocatorManager = std::make_unique<MutexProtected<ResourceAllocatorManager>>(this);
// ShaderVisibleDescriptorAllocators use the ResidencyManager and must be initialized after.
DAWN_TRY_ASSIGN(
@@ -243,8 +243,8 @@
return mCommandAllocatorManager.get();
}
-ResidencyManager* Device::GetResidencyManager() const {
- return mResidencyManager.get();
+MutexProtected<ResidencyManager>& Device::GetResidencyManager() const {
+ return *mResidencyManager;
}
ResultOrError<CommandRecordingContext*> Device::GetPendingCommandContext(
@@ -315,13 +315,13 @@
// Perform cleanup operations to free unused objects
ExecutionSerial completedSerial = GetQueue()->GetCompletedCommandSerial();
- mResourceAllocatorManager->Tick(completedSerial);
+ (*mResourceAllocatorManager)->Tick(completedSerial);
DAWN_TRY(mCommandAllocatorManager->Tick(completedSerial));
(*mViewShaderVisibleDescriptorAllocator)->Tick(completedSerial);
(*mSamplerShaderVisibleDescriptorAllocator)->Tick(completedSerial);
(*mRenderTargetViewAllocator)->Tick(completedSerial);
(*mDepthStencilViewAllocator)->Tick(completedSerial);
- mUsedComObjectRefs.ClearUpTo(completedSerial);
+ mUsedComObjectRefs->ClearUpTo(completedSerial);
if (mPendingCommands.IsOpen() && mPendingCommands.NeedsSubmit()) {
DAWN_TRY(ExecutePendingCommandContext());
@@ -374,7 +374,7 @@
}
void Device::ReferenceUntilUnused(ComPtr<IUnknown> object) {
- mUsedComObjectRefs.Enqueue(object, GetPendingCommandSerial());
+ mUsedComObjectRefs->Enqueue(std::move(object), GetPendingCommandSerial());
}
bool Device::HasPendingCommands() const {
@@ -567,7 +567,7 @@
}
void Device::DeallocateMemory(ResourceHeapAllocation& allocation) {
- mResourceAllocatorManager->DeallocateMemory(allocation);
+ (*mResourceAllocatorManager)->DeallocateMemory(allocation);
}
ResultOrError<ResourceHeapAllocation> Device::AllocateMemory(
@@ -577,9 +577,9 @@
uint32_t formatBytesPerBlock,
bool forceAllocateAsCommittedResource) {
// formatBytesPerBlock is needed only for color non-compressed formats for a workaround.
- return mResourceAllocatorManager->AllocateMemory(heapType, resourceDescriptor, initialUsage,
- formatBytesPerBlock,
- forceAllocateAsCommittedResource);
+ return (*mResourceAllocatorManager)
+ ->AllocateMemory(heapType, resourceDescriptor, initialUsage, formatBytesPerBlock,
+ forceAllocateAsCommittedResource);
}
ResultOrError<Ref<d3d::Fence>> Device::CreateFence(
@@ -744,6 +744,14 @@
void Device::DestroyImpl() {
DAWN_ASSERT(GetState() == State::Disconnected);
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the device is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the device.
+ // - It may be called when the last ref to the device is dropped and the device
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the device since there are no other live refs.
+
Base::DestroyImpl();
mZeroBuffer = nullptr;
@@ -761,9 +769,9 @@
mResourceAllocatorManager.reset();
// We need to handle clearing up com object refs that were enqeued after TickImpl
- mUsedComObjectRefs.ClearUpTo(std::numeric_limits<ExecutionSerial>::max());
+ mUsedComObjectRefs->ClearUpTo(std::numeric_limits<ExecutionSerial>::max());
- DAWN_ASSERT(mUsedComObjectRefs.Empty());
+ DAWN_ASSERT(mUsedComObjectRefs->Empty());
DAWN_ASSERT(!mPendingCommands.IsOpen());
// Now that we've cleared out pending work from the queue, we can safely release it and reclaim
diff --git a/src/dawn/native/d3d12/DeviceD3D12.h b/src/dawn/native/d3d12/DeviceD3D12.h
index 2072ad5..c10ff15 100644
--- a/src/dawn/native/d3d12/DeviceD3D12.h
+++ b/src/dawn/native/d3d12/DeviceD3D12.h
@@ -71,7 +71,7 @@
ComPtr<ID3D12CommandSignature> GetDrawIndexedIndirectSignature() const;
CommandAllocatorManager* GetCommandAllocatorManager() const;
- ResidencyManager* GetResidencyManager() const;
+ MutexProtected<ResidencyManager>& GetResidencyManager() const;
const PlatformFunctions* GetFunctions() const;
@@ -243,11 +243,11 @@
CommandRecordingContext mPendingCommands;
- SerialQueue<ExecutionSerial, ComPtr<IUnknown>> mUsedComObjectRefs;
+ MutexProtected<SerialQueue<ExecutionSerial, ComPtr<IUnknown>>> mUsedComObjectRefs;
std::unique_ptr<CommandAllocatorManager> mCommandAllocatorManager;
- std::unique_ptr<ResourceAllocatorManager> mResourceAllocatorManager;
- std::unique_ptr<ResidencyManager> mResidencyManager;
+ std::unique_ptr<MutexProtected<ResourceAllocatorManager>> mResourceAllocatorManager;
+ std::unique_ptr<MutexProtected<ResidencyManager>> mResidencyManager;
static constexpr uint32_t kMaxSamplerDescriptorsPerBindGroup = 3 * kMaxSamplersPerShaderStage;
static constexpr uint32_t kMaxViewDescriptorsPerBindGroup =
diff --git a/src/dawn/native/d3d12/QuerySetD3D12.cpp b/src/dawn/native/d3d12/QuerySetD3D12.cpp
index 9f7ed07..893d345 100644
--- a/src/dawn/native/d3d12/QuerySetD3D12.cpp
+++ b/src/dawn/native/d3d12/QuerySetD3D12.cpp
@@ -65,6 +65,13 @@
QuerySet::~QuerySet() = default;
void QuerySet::DestroyImpl() {
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the query set is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the query set.
+ // - It may be called when the last ref to the query set is dropped and it
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the query set since there are no other live refs.
QuerySetBase::DestroyImpl();
ToBackend(GetDevice())->ReferenceUntilUnused(mQueryHeap);
mQueryHeap = nullptr;
diff --git a/src/dawn/native/metal/BufferMTL.mm b/src/dawn/native/metal/BufferMTL.mm
index ae49bca..c7dbcac 100644
--- a/src/dawn/native/metal/BufferMTL.mm
+++ b/src/dawn/native/metal/BufferMTL.mm
@@ -199,6 +199,13 @@
}
void Buffer::DestroyImpl() {
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the buffer is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the buffer.
+ // - It may be called when the last ref to the buffer is dropped and the buffer
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the buffer since there are no other live refs.
BufferBase::DestroyImpl();
mMtlBuffer = nullptr;
}
diff --git a/src/dawn/native/metal/DeviceMTL.mm b/src/dawn/native/metal/DeviceMTL.mm
index 0aa58e2..519a277 100644
--- a/src/dawn/native/metal/DeviceMTL.mm
+++ b/src/dawn/native/metal/DeviceMTL.mm
@@ -379,7 +379,13 @@
void Device::DestroyImpl() {
DAWN_ASSERT(GetState() == State::Disconnected);
-
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the device is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the device.
+ // - It may be called when the last ref to the device is dropped and the device
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the device since there are no other live refs.
mMtlDevice = nullptr;
mMockBlitMtlBuffer = nullptr;
}
diff --git a/src/dawn/native/metal/QuerySetMTL.mm b/src/dawn/native/metal/QuerySetMTL.mm
index 4a5a24c..7b58ed8 100644
--- a/src/dawn/native/metal/QuerySetMTL.mm
+++ b/src/dawn/native/metal/QuerySetMTL.mm
@@ -136,6 +136,13 @@
QuerySet::~QuerySet() = default;
void QuerySet::DestroyImpl() {
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the query set is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the query set.
+ // - It may be called when the last ref to the query set is dropped andit
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the query set since there are no other live refs.
QuerySetBase::DestroyImpl();
mVisibilityBuffer = nullptr;
diff --git a/src/dawn/native/metal/TextureMTL.mm b/src/dawn/native/metal/TextureMTL.mm
index 18be29e..a26037c 100644
--- a/src/dawn/native/metal/TextureMTL.mm
+++ b/src/dawn/native/metal/TextureMTL.mm
@@ -892,6 +892,13 @@
Texture::~Texture() {}
void Texture::DestroyImpl() {
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the texture is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the texture.
+ // - It may be called when the last ref to the texture is dropped and the texture
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the texture since there are no other live refs.
TextureBase::DestroyImpl();
mMtlTexture = nullptr;
mIOSurface = nullptr;
diff --git a/src/dawn/native/null/DeviceNull.cpp b/src/dawn/native/null/DeviceNull.cpp
index 25891c2..998bcf5 100644
--- a/src/dawn/native/null/DeviceNull.cpp
+++ b/src/dawn/native/null/DeviceNull.cpp
@@ -210,6 +210,13 @@
void Device::DestroyImpl() {
DAWN_ASSERT(GetState() == State::Disconnected);
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the device is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the device.
+ // - It may be called when the last ref to the device is dropped and the device
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the device since there are no other live refs.
// Clear pending operations before checking mMemoryUsage because some operations keep a
// reference to Buffers.
@@ -348,6 +355,13 @@
void Buffer::UnmapImpl() {}
void Buffer::DestroyImpl() {
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the buffer is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the buffer.
+ // - It may be called when the last ref to the buffer is dropped and the buffer
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the buffer since there are no other live refs.
BufferBase::DestroyImpl();
ToBackend(GetDevice())->DecrementMemoryUsage(GetSize());
}
diff --git a/src/dawn/native/vulkan/BufferVk.cpp b/src/dawn/native/vulkan/BufferVk.cpp
index 4744cf3..ad3196e 100644
--- a/src/dawn/native/vulkan/BufferVk.cpp
+++ b/src/dawn/native/vulkan/BufferVk.cpp
@@ -471,6 +471,13 @@
}
void Buffer::DestroyImpl() {
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the buffer is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the buffer.
+ // - It may be called when the last ref to the buffer is dropped and the buffer
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the buffer since there are no other live refs.
BufferBase::DestroyImpl();
ToBackend(GetDevice())->GetResourceMemoryAllocator()->Deallocate(&mMemoryAllocation);
diff --git a/src/dawn/native/vulkan/DeviceVk.cpp b/src/dawn/native/vulkan/DeviceVk.cpp
index 455add0..99cc1ec 100644
--- a/src/dawn/native/vulkan/DeviceVk.cpp
+++ b/src/dawn/native/vulkan/DeviceVk.cpp
@@ -128,11 +128,11 @@
// the device.
GatherQueueFromDevice();
- mDeleter = std::make_unique<FencedDeleter>(this);
+ mDeleter = std::make_unique<MutexProtected<FencedDeleter>>(this);
}
mRenderPassCache = std::make_unique<RenderPassCache>(this);
- mResourceMemoryAllocator = std::make_unique<ResourceMemoryAllocator>(this);
+ mResourceMemoryAllocator = std::make_unique<MutexProtected<ResourceMemoryAllocator>>(this);
mExternalMemoryService = std::make_unique<external_memory::Service>(this);
mExternalSemaphoreService = std::make_unique<external_semaphore::Service>(this);
@@ -232,8 +232,8 @@
allocator->FinishDeallocation(completedSerial);
}
- mResourceMemoryAllocator->Tick(completedSerial);
- mDeleter->Tick(completedSerial);
+ GetResourceMemoryAllocator()->Tick(completedSerial);
+ GetFencedDeleter()->Tick(completedSerial);
mDescriptorAllocatorsPendingDeallocation.ClearUpTo(completedSerial);
if (mRecordingContext.needsSubmit) {
@@ -268,16 +268,16 @@
return mQueue;
}
-FencedDeleter* Device::GetFencedDeleter() const {
- return mDeleter.get();
+MutexProtected<FencedDeleter>& Device::GetFencedDeleter() const {
+ return *mDeleter;
}
RenderPassCache* Device::GetRenderPassCache() const {
return mRenderPassCache.get();
}
-ResourceMemoryAllocator* Device::GetResourceMemoryAllocator() const {
- return mResourceMemoryAllocator.get();
+MutexProtected<ResourceMemoryAllocator>& Device::GetResourceMemoryAllocator() const {
+ return *mResourceMemoryAllocator;
}
external_semaphore::Service* Device::GetExternalSemaphoreService() const {
@@ -366,7 +366,7 @@
// Enqueue the semaphores before incrementing the serial, so that they can be deleted as
// soon as the current submission is finished.
for (VkSemaphore semaphore : mRecordingContext.waitSemaphores) {
- mDeleter->DeleteWhenUnused(semaphore);
+ GetFencedDeleter()->DeleteWhenUnused(semaphore);
}
GetQueue()->IncrementLastSubmittedCommandSerial();
ExecutionSerial lastSubmittedSerial = GetLastSubmittedCommandSerial();
@@ -1054,6 +1054,14 @@
return;
}
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the device is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the device.
+ // - It may be called when the last ref to the device is dropped and the device
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the device since there are no other live refs.
+
// 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
@@ -1111,11 +1119,11 @@
// Releasing the uploader enqueues buffers to be released.
// Call Tick() again to clear them before releasing the deleter.
- mResourceMemoryAllocator->Tick(kMaxExecutionSerial);
+ GetResourceMemoryAllocator()->Tick(kMaxExecutionSerial);
mDescriptorAllocatorsPendingDeallocation.ClearUpTo(kMaxExecutionSerial);
// Allow recycled memory to be deleted.
- mResourceMemoryAllocator->DestroyPool();
+ GetResourceMemoryAllocator()->DestroyPool();
// The VkRenderPasses in the cache can be destroyed immediately since all commands referring
// to them are guaranteed to be finished executing.
@@ -1123,7 +1131,7 @@
// Delete all the remaining VkDevice child objects immediately since the GPU timeline is
// finished.
- mDeleter->Tick(kMaxExecutionSerial);
+ GetFencedDeleter()->Tick(kMaxExecutionSerial);
mDeleter = nullptr;
// VkQueues are destroyed when the VkDevice is destroyed
diff --git a/src/dawn/native/vulkan/DeviceVk.h b/src/dawn/native/vulkan/DeviceVk.h
index 9376d76..ef842d6 100644
--- a/src/dawn/native/vulkan/DeviceVk.h
+++ b/src/dawn/native/vulkan/DeviceVk.h
@@ -21,6 +21,7 @@
#include <utility>
#include <vector>
+#include "dawn/common/MutexProtected.h"
#include "dawn/common/SerialQueue.h"
#include "dawn/native/Commands.h"
#include "dawn/native/Device.h"
@@ -60,9 +61,9 @@
uint32_t GetGraphicsQueueFamily() const;
VkQueue GetVkQueue() const;
- FencedDeleter* GetFencedDeleter() const;
+ MutexProtected<FencedDeleter>& GetFencedDeleter() const;
RenderPassCache* GetRenderPassCache() const;
- ResourceMemoryAllocator* GetResourceMemoryAllocator() const;
+ MutexProtected<ResourceMemoryAllocator>& GetResourceMemoryAllocator() const;
external_semaphore::Service* GetExternalSemaphoreService() const;
CommandRecordingContext* GetPendingRecordingContext(
@@ -183,8 +184,8 @@
SerialQueue<ExecutionSerial, Ref<DescriptorSetAllocator>>
mDescriptorAllocatorsPendingDeallocation;
- std::unique_ptr<FencedDeleter> mDeleter;
- std::unique_ptr<ResourceMemoryAllocator> mResourceMemoryAllocator;
+ std::unique_ptr<MutexProtected<FencedDeleter>> mDeleter;
+ std::unique_ptr<MutexProtected<ResourceMemoryAllocator>> mResourceMemoryAllocator;
std::unique_ptr<RenderPassCache> mRenderPassCache;
std::unique_ptr<external_memory::Service> mExternalMemoryService;
diff --git a/src/dawn/native/vulkan/QuerySetVk.cpp b/src/dawn/native/vulkan/QuerySetVk.cpp
index 36eaec6..338af95 100644
--- a/src/dawn/native/vulkan/QuerySetVk.cpp
+++ b/src/dawn/native/vulkan/QuerySetVk.cpp
@@ -101,6 +101,13 @@
QuerySet::~QuerySet() = default;
void QuerySet::DestroyImpl() {
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the query set is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the query set.
+ // - It may be called when the last ref to the query set is dropped and it
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the query set since there are no other live refs.
QuerySetBase::DestroyImpl();
if (mHandle != VK_NULL_HANDLE) {
ToBackend(GetDevice())->GetFencedDeleter()->DeleteWhenUnused(mHandle);
diff --git a/src/dawn/native/vulkan/TextureVk.cpp b/src/dawn/native/vulkan/TextureVk.cpp
index d71df30..ecb7493 100644
--- a/src/dawn/native/vulkan/TextureVk.cpp
+++ b/src/dawn/native/vulkan/TextureVk.cpp
@@ -1020,6 +1020,13 @@
}
void Texture::DestroyImpl() {
+ // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
+ // - It may be called if the texture is explicitly destroyed with APIDestroy.
+ // This case is NOT thread-safe and needs proper synchronization with other
+ // simultaneous uses of the texture.
+ // - It may be called when the last ref to the texture is dropped and the texture
+ // is implicitly destroyed. This case is thread-safe because there are no
+ // other threads using the texture since there are no other live refs.
Device* device = ToBackend(GetDevice());
if (mOwnsHandle) {