Moves DestroyApiObject call into ApiObjectBase::DeleteThis

- Moving the call into DeleteThis should make it so that derived classes don't need to explicitly implement a destructor that calls DestroyApiObject.

Bug: dawn:628
Change-Id: I145f42e7e4c144cc0d2d7c7f609744399d514fe1
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/66840
Commit-Queue: Loko Kung <lokokung@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
This commit is contained in:
Loko Kung 2021-10-19 22:43:13 +00:00 committed by Dawn LUCI CQ
parent 74635bc6e9
commit bf9b3cc5a9
14 changed files with 54 additions and 29 deletions

View File

@ -200,7 +200,11 @@ namespace dawn_native {
mCaches = std::make_unique<DeviceBase::Caches>(); mCaches = std::make_unique<DeviceBase::Caches>();
} }
DeviceBase::~DeviceBase() = default; DeviceBase::~DeviceBase() {
// We need to explicitly release the Queue before we complete the destructor so that the
// Queue does not get destroyed after the Device.
mQueue = nullptr;
}
MaybeError DeviceBase::Initialize(QueueBase* defaultQueue) { MaybeError DeviceBase::Initialize(QueueBase* defaultQueue) {
mQueue = AcquireRef(defaultQueue); mQueue = AcquireRef(defaultQueue);

View File

@ -70,15 +70,22 @@ namespace dawn_native {
return IsInList(); return IsInList();
} }
void ApiObjectBase::DeleteThis() {
DestroyApiObject();
RefCounted::DeleteThis();
}
void ApiObjectBase::TrackInDevice() { void ApiObjectBase::TrackInDevice() {
ASSERT(GetDevice() != nullptr); ASSERT(GetDevice() != nullptr);
GetDevice()->TrackObject(this); GetDevice()->TrackObject(this);
} }
bool ApiObjectBase::DestroyApiObject() { bool ApiObjectBase::DestroyApiObject() {
const std::lock_guard<std::mutex> lock(*GetDevice()->GetObjectListMutex(GetType())); {
if (!RemoveFromList()) { const std::lock_guard<std::mutex> lock(*GetDevice()->GetObjectListMutex(GetType()));
return false; if (!RemoveFromList()) {
return false;
}
} }
DestroyApiObjectImpl(); DestroyApiObjectImpl();
return true; return true;

View File

@ -71,6 +71,17 @@ namespace dawn_native {
void APISetLabel(const char* label); void APISetLabel(const char* label);
protected: protected:
// Overriding of the RefCounted's DeleteThis function ensures that instances of objects
// always call their derived class implementation of DestroyApiObject prior to the derived
// class being destroyed. This guarantees that when ApiObjects' reference counts drop to 0,
// then the underlying backend's Destroy calls are executed. We cannot naively put the call
// to DestroyApiObject in the destructor of this class because it calls DestroyApiObjectImpl
// which is a virtual function often implemented in the Derived class which would already
// have been destroyed by the time ApiObject's destructor is called by C++'s destruction
// order. Note that some classes like BindGroup may override the DeleteThis function again,
// and they should ensure that their overriding versions call this underlying version
// somewhere.
void DeleteThis() override;
void TrackInDevice(); void TrackInDevice();
virtual void DestroyApiObjectImpl(); virtual void DestroyApiObjectImpl();

View File

@ -131,10 +131,6 @@ namespace dawn_native { namespace d3d12 {
device->GetSamplerStagingDescriptorAllocator(GetSamplerDescriptorCount()); device->GetSamplerStagingDescriptorAllocator(GetSamplerDescriptorCount());
} }
BindGroupLayout::~BindGroupLayout() {
DestroyApiObject();
}
ResultOrError<Ref<BindGroup>> BindGroupLayout::AllocateBindGroup( ResultOrError<Ref<BindGroup>> BindGroupLayout::AllocateBindGroup(
Device* device, Device* device,
const BindGroupDescriptor* descriptor) { const BindGroupDescriptor* descriptor) {

View File

@ -64,7 +64,7 @@ namespace dawn_native { namespace d3d12 {
BindGroupLayout(Device* device, BindGroupLayout(Device* device,
const BindGroupLayoutDescriptor* descriptor, const BindGroupLayoutDescriptor* descriptor,
PipelineCompatibilityToken pipelineCompatibilityToken); PipelineCompatibilityToken pipelineCompatibilityToken);
~BindGroupLayout() override; ~BindGroupLayout() override = default;
// Contains the offset into the descriptor heap for the given resource view. Samplers and // Contains the offset into the descriptor heap for the given resource view. Samplers and
// non-samplers are stored in separate descriptor heaps, so the offsets should be unique // non-samplers are stored in separate descriptor heaps, so the offsets should be unique

View File

@ -36,7 +36,7 @@ namespace dawn_native { namespace metal {
BindGroupLayout(DeviceBase* device, BindGroupLayout(DeviceBase* device,
const BindGroupLayoutDescriptor* descriptor, const BindGroupLayoutDescriptor* descriptor,
PipelineCompatibilityToken pipelineCompatibilityToken); PipelineCompatibilityToken pipelineCompatibilityToken);
~BindGroupLayout() override; ~BindGroupLayout() override = default;
SlabAllocator<BindGroup> mBindGroupAllocator; SlabAllocator<BindGroup> mBindGroupAllocator;
}; };

View File

@ -33,10 +33,6 @@ namespace dawn_native { namespace metal {
mBindGroupAllocator(MakeFrontendBindGroupAllocator<BindGroup>(4096)) { mBindGroupAllocator(MakeFrontendBindGroupAllocator<BindGroup>(4096)) {
} }
BindGroupLayout::~BindGroupLayout() {
DestroyApiObject();
}
Ref<BindGroup> BindGroupLayout::AllocateBindGroup(Device* device, Ref<BindGroup> BindGroupLayout::AllocateBindGroup(Device* device,
const BindGroupDescriptor* descriptor) { const BindGroupDescriptor* descriptor) {
return AcquireRef(mBindGroupAllocator.Allocate(device, descriptor)); return AcquireRef(mBindGroupAllocator.Allocate(device, descriptor));

View File

@ -271,10 +271,6 @@ namespace dawn_native { namespace null {
: BindGroupLayoutBase(device, descriptor, pipelineCompatibilityToken) { : BindGroupLayoutBase(device, descriptor, pipelineCompatibilityToken) {
} }
BindGroupLayout::~BindGroupLayout() {
DestroyApiObject();
}
// Buffer // Buffer
Buffer::Buffer(Device* device, const BufferDescriptor* descriptor) Buffer::Buffer(Device* device, const BufferDescriptor* descriptor)

View File

@ -207,7 +207,7 @@ namespace dawn_native { namespace null {
PipelineCompatibilityToken pipelineCompatibilityToken); PipelineCompatibilityToken pipelineCompatibilityToken);
private: private:
~BindGroupLayout() override; ~BindGroupLayout() override = default;
}; };
class Buffer final : public BufferBase { class Buffer final : public BufferBase {

View File

@ -25,10 +25,6 @@ namespace dawn_native { namespace opengl {
mBindGroupAllocator(MakeFrontendBindGroupAllocator<BindGroup>(4096)) { mBindGroupAllocator(MakeFrontendBindGroupAllocator<BindGroup>(4096)) {
} }
BindGroupLayout::~BindGroupLayout() {
DestroyApiObject();
}
Ref<BindGroup> BindGroupLayout::AllocateBindGroup(Device* device, Ref<BindGroup> BindGroupLayout::AllocateBindGroup(Device* device,
const BindGroupDescriptor* descriptor) { const BindGroupDescriptor* descriptor) {
return AcquireRef(mBindGroupAllocator.Allocate(device, descriptor)); return AcquireRef(mBindGroupAllocator.Allocate(device, descriptor));

View File

@ -33,7 +33,7 @@ namespace dawn_native { namespace opengl {
void DeallocateBindGroup(BindGroup* bindGroup); void DeallocateBindGroup(BindGroup* bindGroup);
private: private:
~BindGroupLayout() override; ~BindGroupLayout() override = default;
SlabAllocator<BindGroup> mBindGroupAllocator; SlabAllocator<BindGroup> mBindGroupAllocator;
}; };

View File

@ -161,7 +161,6 @@ namespace dawn_native { namespace vulkan {
device->fn.DestroyDescriptorSetLayout(device->GetVkDevice(), mHandle, nullptr); device->fn.DestroyDescriptorSetLayout(device->GetVkDevice(), mHandle, nullptr);
mHandle = VK_NULL_HANDLE; mHandle = VK_NULL_HANDLE;
} }
DestroyApiObject();
} }
VkDescriptorSetLayout BindGroupLayout::GetHandle() const { VkDescriptorSetLayout BindGroupLayout::GetHandle() const {

View File

@ -25,7 +25,7 @@ namespace dawn_native { namespace {
using ::testing::InSequence; using ::testing::InSequence;
using ::testing::Return; using ::testing::Return;
TEST(DestroyObjectTests, BindGroupLayout) { TEST(DestroyObjectTests, BindGroupLayoutExplicit) {
// Skipping validation on descriptors as coverage for validation is already present. // Skipping validation on descriptors as coverage for validation is already present.
DeviceMock device; DeviceMock device;
device.SetToggle(Toggle::SkipValidation, true); device.SetToggle(Toggle::SkipValidation, true);
@ -46,6 +46,28 @@ namespace dawn_native { namespace {
EXPECT_FALSE(bindGroupLayout->IsAlive()); EXPECT_FALSE(bindGroupLayout->IsAlive());
} }
// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
// will also complain if there is a memory leak.
TEST(DestroyObjectTests, BindGroupLayoutImplicit) {
// Skipping validation on descriptors as coverage for validation is already present.
DeviceMock device;
device.SetToggle(Toggle::SkipValidation, true);
BindGroupLayoutMock* bindGroupLayoutMock = new BindGroupLayoutMock(&device);
EXPECT_CALL(*bindGroupLayoutMock, DestroyApiObjectImpl).Times(1);
{
BindGroupLayoutDescriptor desc = {};
Ref<BindGroupLayoutBase> bindGroupLayout;
EXPECT_CALL(device, CreateBindGroupLayoutImpl)
.WillOnce(Return(ByMove(AcquireRef(bindGroupLayoutMock))));
DAWN_ASSERT_AND_ASSIGN(bindGroupLayout, device.CreateBindGroupLayout(&desc));
EXPECT_TRUE(bindGroupLayout->IsAlive());
EXPECT_TRUE(bindGroupLayout->IsCachedReference());
}
}
// Destroying the objects on the device should result in all created objects being destroyed in // Destroying the objects on the device should result in all created objects being destroyed in
// order. // order.
TEST(DestroyObjectTests, DestroyObjects) { TEST(DestroyObjectTests, DestroyObjects) {

View File

@ -26,9 +26,7 @@ namespace dawn_native {
public: public:
BindGroupLayoutMock(DeviceBase* device) : BindGroupLayoutBase(device) { BindGroupLayoutMock(DeviceBase* device) : BindGroupLayoutBase(device) {
} }
~BindGroupLayoutMock() override { ~BindGroupLayoutMock() override = default;
DestroyApiObject();
}
MOCK_METHOD(void, DestroyApiObjectImpl, (), (override)); MOCK_METHOD(void, DestroyApiObjectImpl, (), (override));
}; };