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>();
}
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) {
mQueue = AcquireRef(defaultQueue);

View File

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

View File

@ -71,6 +71,17 @@ namespace dawn_native {
void APISetLabel(const char* label);
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();
virtual void DestroyApiObjectImpl();

View File

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

View File

@ -64,7 +64,7 @@ namespace dawn_native { namespace d3d12 {
BindGroupLayout(Device* device,
const BindGroupLayoutDescriptor* descriptor,
PipelineCompatibilityToken pipelineCompatibilityToken);
~BindGroupLayout() override;
~BindGroupLayout() override = default;
// 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -25,7 +25,7 @@ namespace dawn_native { namespace {
using ::testing::InSequence;
using ::testing::Return;
TEST(DestroyObjectTests, BindGroupLayout) {
TEST(DestroyObjectTests, BindGroupLayoutExplicit) {
// Skipping validation on descriptors as coverage for validation is already present.
DeviceMock device;
device.SetToggle(Toggle::SkipValidation, true);
@ -46,6 +46,28 @@ namespace dawn_native { namespace {
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
// order.
TEST(DestroyObjectTests, DestroyObjects) {

View File

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