Adds destruction handling for Textures when device is destroyed.

Bug: dawn:628
Change-Id: Iaaa62507592e12a724673a0226783c0425b04f35
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/67601
Commit-Queue: Loko Kung <lokokung@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
This commit is contained in:
Loko Kung 2021-11-10 22:25:03 +00:00 committed by Dawn LUCI CQ
parent 75c978f9b9
commit 6b6262dde0
17 changed files with 296 additions and 26 deletions

View File

@ -269,7 +269,7 @@ namespace dawn_native {
// TODO(dawn/628) Add types into the array as they are implemented.
// clang-format off
static constexpr std::array<ObjectType, 10> kObjectTypeDependencyOrder = {
static constexpr std::array<ObjectType, 13> kObjectTypeDependencyOrder = {
ObjectType::RenderPipeline,
ObjectType::ComputePipeline,
ObjectType::PipelineLayout,
@ -277,6 +277,9 @@ namespace dawn_native {
ObjectType::BindGroup,
ObjectType::BindGroupLayout,
ObjectType::ShaderModule,
ObjectType::ExternalTexture,
ObjectType::TextureView,
ObjectType::Texture,
ObjectType::QuerySet,
ObjectType::Sampler,
ObjectType::Buffer,

View File

@ -91,6 +91,12 @@ namespace dawn_native {
const ExternalTextureDescriptor* descriptor)
: ApiObjectBase(device, kLabelNotImplemented), mState(ExternalTextureState::Alive) {
textureViews[0] = descriptor->plane0;
TrackInDevice();
}
ExternalTextureBase::ExternalTextureBase(DeviceBase* device)
: ApiObjectBase(device, kLabelNotImplemented), mState(ExternalTextureState::Alive) {
TrackInDevice();
}
ExternalTextureBase::ExternalTextureBase(DeviceBase* device, ObjectBase::ErrorTag tag)
@ -113,8 +119,12 @@ namespace dawn_native {
if (GetDevice()->ConsumedError(GetDevice()->ValidateObject(this))) {
return;
}
DestroyApiObject();
}
bool ExternalTextureBase::DestroyApiObject() {
mState = ExternalTextureState::Destroyed;
ASSERT(!IsError());
return ApiObjectBase::DestroyApiObject();
}
// static

View File

@ -42,10 +42,15 @@ namespace dawn_native {
static ExternalTextureBase* MakeError(DeviceBase* device);
bool DestroyApiObject() override;
ObjectType GetType() const override;
void APIDestroy();
protected:
// Constructor used only for mocking and testing.
ExternalTextureBase(DeviceBase* device);
private:
enum class ExternalTextureState { Alive, Destroyed };
ExternalTextureBase(DeviceBase* device, const ExternalTextureDescriptor* descriptor);

View File

@ -469,14 +469,30 @@ namespace dawn_native {
if (internalUsageDesc != nullptr) {
mInternalUsage |= internalUsageDesc->internalUsage;
}
TrackInDevice();
}
static Format kUnusedFormat;
TextureBase::TextureBase(DeviceBase* device, TextureState state)
: ApiObjectBase(device, kLabelNotImplemented), mFormat(kUnusedFormat), mState(state) {
TrackInDevice();
}
TextureBase::TextureBase(DeviceBase* device, ObjectBase::ErrorTag tag)
: ApiObjectBase(device, tag), mFormat(kUnusedFormat) {
}
bool TextureBase::DestroyApiObject() {
// We need to run the destroy operations prior to setting the state to destroyed so that
// the state is both consistent, and implementations of the destroy that may check the
// state do not skip operations unintentionally. (Example in Vulkan backend, the destroy
// implementation will not be ran if we are already in the Destroyed state.)
bool wasDestroyed = ApiObjectBase::DestroyApiObject();
mState = TextureState::Destroyed;
return wasDestroyed;
}
// static
TextureBase* TextureBase::MakeError(DeviceBase* device) {
return new TextureBase(device, ObjectBase::kError);
@ -670,15 +686,7 @@ namespace dawn_native {
return;
}
ASSERT(!IsError());
DestroyInternal();
}
void TextureBase::DestroyImpl() {
}
void TextureBase::DestroyInternal() {
DestroyImpl();
mState = TextureState::Destroyed;
DestroyApiObject();
}
MaybeError TextureBase::ValidateDestroy() const {
@ -696,6 +704,14 @@ namespace dawn_native {
mRange({ConvertViewAspect(mFormat, descriptor->aspect),
{descriptor->baseArrayLayer, descriptor->arrayLayerCount},
{descriptor->baseMipLevel, descriptor->mipLevelCount}}) {
TrackInDevice();
}
TextureViewBase::TextureViewBase(TextureBase* texture)
: ApiObjectBase(texture->GetDevice(), kLabelNotImplemented),
mTexture(texture),
mFormat(kUnusedFormat) {
TrackInDevice();
}
TextureViewBase::TextureViewBase(DeviceBase* device, ObjectBase::ErrorTag tag)

View File

@ -51,6 +51,7 @@ namespace dawn_native {
static TextureBase* MakeError(DeviceBase* device);
bool DestroyApiObject() override;
ObjectType GetType() const override;
wgpu::TextureDimension GetDimension() const;
@ -96,11 +97,11 @@ namespace dawn_native {
void APIDestroy();
protected:
void DestroyInternal();
// Constructor used only for mocking and testing.
TextureBase(DeviceBase* device, TextureState state);
private:
TextureBase(DeviceBase* device, ObjectBase::ErrorTag tag);
virtual void DestroyImpl();
MaybeError ValidateDestroy() const;
wgpu::TextureDimension mDimension;
@ -136,6 +137,10 @@ namespace dawn_native {
uint32_t GetLayerCount() const;
const SubresourceRange& GetSubresourceRange() const;
protected:
// Constructor used only for mocking and testing.
TextureViewBase(TextureBase* texture);
private:
TextureViewBase(DeviceBase* device, ObjectBase::ErrorTag tag);

View File

@ -647,10 +647,9 @@ namespace dawn_native { namespace d3d12 {
}
Texture::~Texture() {
DestroyInternal();
}
void Texture::DestroyImpl() {
void Texture::DestroyApiObjectImpl() {
Device* device = ToBackend(GetDevice());
// In PIX's D3D12-only mode, there is no way to determine frame boundaries

View File

@ -105,7 +105,7 @@ namespace dawn_native { namespace d3d12 {
// Dawn API
void SetLabelImpl() override;
void DestroyImpl() override;
void DestroyApiObjectImpl() override;
MaybeError ClearTexture(CommandRecordingContext* commandContext,
const SubresourceRange& range,

View File

@ -66,7 +66,7 @@ namespace dawn_native { namespace metal {
void InitializeAsWrapping(const TextureDescriptor* descriptor,
NSPRef<id<MTLTexture>> wrapped);
void DestroyImpl() override;
void DestroyApiObjectImpl() override;
MaybeError ClearTexture(CommandRecordingContext* commandContext,
const SubresourceRange& range,

View File

@ -493,10 +493,9 @@ namespace dawn_native { namespace metal {
}
Texture::~Texture() {
DestroyInternal();
}
void Texture::DestroyImpl() {
void Texture::DestroyApiObjectImpl() {
mMtlTexture = nullptr;
}

View File

@ -188,10 +188,9 @@ namespace dawn_native { namespace opengl {
}
Texture::~Texture() {
DestroyInternal();
}
void Texture::DestroyImpl() {
void Texture::DestroyApiObjectImpl() {
if (GetTextureState() == TextureState::OwnedInternal) {
ToBackend(GetDevice())->gl.DeleteTextures(1, &mHandle);
mHandle = 0;
@ -559,6 +558,9 @@ namespace dawn_native { namespace opengl {
}
TextureView::~TextureView() {
}
void TextureView::DestroyApiObjectImpl() {
if (mOwnsHandle) {
ToBackend(GetDevice())->gl.DeleteTextures(1, &mHandle);
}

View File

@ -41,7 +41,7 @@ namespace dawn_native { namespace opengl {
private:
~Texture() override;
void DestroyImpl() override;
void DestroyApiObjectImpl() override;
MaybeError ClearTexture(const SubresourceRange& range, TextureBase::ClearValue clearValue);
GLuint mHandle;
@ -57,6 +57,7 @@ namespace dawn_native { namespace opengl {
private:
~TextureView() override;
void DestroyApiObjectImpl() override;
GLuint mHandle;
GLenum mTarget;

View File

@ -792,12 +792,11 @@ namespace dawn_native { namespace vulkan {
mSignalSemaphore = VK_NULL_HANDLE;
// Destroy the texture so it can't be used again
DestroyInternal();
DestroyApiObject();
return {};
}
Texture::~Texture() {
DestroyInternal();
}
void Texture::SetLabelHelper(const char* prefix) {
@ -809,7 +808,7 @@ namespace dawn_native { namespace vulkan {
SetLabelHelper("Dawn_InternalTexture");
}
void Texture::DestroyImpl() {
void Texture::DestroyApiObjectImpl() {
if (GetTextureState() == TextureState::OwnedInternal) {
Device* device = ToBackend(GetDevice());
@ -1300,6 +1299,9 @@ namespace dawn_native { namespace vulkan {
}
TextureView::~TextureView() {
}
void TextureView::DestroyApiObjectImpl() {
Device* device = ToBackend(GetTexture()->GetDevice());
if (mHandle != VK_NULL_HANDLE) {

View File

@ -107,7 +107,7 @@ namespace dawn_native { namespace vulkan {
external_memory::Service* externalMemoryService);
void InitializeForSwapChain(VkImage nativeImage);
void DestroyImpl() override;
void DestroyApiObjectImpl() override;
MaybeError ClearTexture(CommandRecordingContext* recordingContext,
const SubresourceRange& range,
TextureBase::ClearValue);
@ -176,6 +176,7 @@ namespace dawn_native { namespace vulkan {
private:
~TextureView() override;
void DestroyApiObjectImpl() override;
using TextureViewBase::TextureViewBase;
MaybeError Initialize(const TextureViewDescriptor* descriptor);

View File

@ -146,6 +146,7 @@ source_set("dawn_native_mocks_sources") {
"unittests/native/mocks/BindGroupMock.h",
"unittests/native/mocks/ComputePipelineMock.h",
"unittests/native/mocks/DeviceMock.h",
"unittests/native/mocks/ExternalTextureMock.h",
"unittests/native/mocks/PipelineLayoutMock.h",
"unittests/native/mocks/QuerySetMock.h",
"unittests/native/mocks/RenderPipelineMock.h",
@ -153,6 +154,7 @@ source_set("dawn_native_mocks_sources") {
"unittests/native/mocks/ShaderModuleMock.cpp",
"unittests/native/mocks/ShaderModuleMock.h",
"unittests/native/mocks/SwapChainMock.h",
"unittests/native/mocks/TextureMock.h",
]
}

View File

@ -20,12 +20,14 @@
#include "mocks/BufferMock.h"
#include "mocks/ComputePipelineMock.h"
#include "mocks/DeviceMock.h"
#include "mocks/ExternalTextureMock.h"
#include "mocks/PipelineLayoutMock.h"
#include "mocks/QuerySetMock.h"
#include "mocks/RenderPipelineMock.h"
#include "mocks/SamplerMock.h"
#include "mocks/ShaderModuleMock.h"
#include "mocks/SwapChainMock.h"
#include "mocks/TextureMock.h"
#include "tests/DawnNativeTest.h"
#include "utils/ComboRenderPipelineDescriptor.h"
@ -44,6 +46,16 @@ namespace dawn_native { namespace {
mDevice.SetToggle(Toggle::SkipValidation, true);
}
Ref<TextureMock> GetTexture() {
if (mTexture != nullptr) {
return mTexture;
}
mTexture =
AcquireRef(new TextureMock(&mDevice, TextureBase::TextureState::OwnedInternal));
EXPECT_CALL(*mTexture.Get(), DestroyApiObjectImpl).Times(1);
return mTexture;
}
Ref<PipelineLayoutMock> GetPipelineLayout() {
if (mPipelineLayout != nullptr) {
return mPipelineLayout;
@ -85,6 +97,7 @@ namespace dawn_native { namespace {
// The following lazy-initialized objects are used to facilitate creation of dependent
// objects under test.
Ref<TextureMock> mTexture;
Ref<PipelineLayoutMock> mPipelineLayout;
Ref<ShaderModuleMock> mVsModule;
Ref<ShaderModuleMock> mCsModule;
@ -237,6 +250,24 @@ namespace dawn_native { namespace {
}
}
TEST_F(DestroyObjectTests, ExternalTextureExplicit) {
ExternalTextureMock externalTextureMock(&mDevice);
EXPECT_CALL(externalTextureMock, DestroyApiObjectImpl).Times(1);
EXPECT_TRUE(externalTextureMock.IsAlive());
externalTextureMock.DestroyApiObject();
EXPECT_FALSE(externalTextureMock.IsAlive());
}
// We can use an actual ExternalTexture object to test the implicit case.
TEST_F(DestroyObjectTests, ExternalTextureImplicit) {
ExternalTextureDescriptor desc = {};
Ref<ExternalTextureBase> externalTexture;
DAWN_ASSERT_AND_ASSIGN(externalTexture, mDevice.CreateExternalTexture(&desc));
EXPECT_TRUE(externalTexture->IsAlive());
}
TEST_F(DestroyObjectTests, PipelineLayoutExplicit) {
PipelineLayoutMock pipelineLayoutMock(&mDevice);
EXPECT_CALL(pipelineLayoutMock, DestroyApiObjectImpl).Times(1);
@ -409,6 +440,84 @@ namespace dawn_native { namespace {
}
}
TEST_F(DestroyObjectTests, TextureExplicit) {
{
TextureMock textureMock(&mDevice, TextureBase::TextureState::OwnedInternal);
EXPECT_CALL(textureMock, DestroyApiObjectImpl).Times(1);
EXPECT_TRUE(textureMock.IsAlive());
textureMock.DestroyApiObject();
EXPECT_FALSE(textureMock.IsAlive());
}
{
TextureMock textureMock(&mDevice, TextureBase::TextureState::OwnedExternal);
EXPECT_CALL(textureMock, DestroyApiObjectImpl).Times(1);
EXPECT_TRUE(textureMock.IsAlive());
textureMock.DestroyApiObject();
EXPECT_FALSE(textureMock.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_F(DestroyObjectTests, TextureImplicit) {
{
TextureMock* textureMock =
new TextureMock(&mDevice, TextureBase::TextureState::OwnedInternal);
EXPECT_CALL(*textureMock, DestroyApiObjectImpl).Times(1);
{
TextureDescriptor desc = {};
Ref<TextureBase> texture;
EXPECT_CALL(mDevice, CreateTextureImpl)
.WillOnce(Return(ByMove(AcquireRef(textureMock))));
DAWN_ASSERT_AND_ASSIGN(texture, mDevice.CreateTexture(&desc));
EXPECT_TRUE(texture->IsAlive());
}
}
{
TextureMock* textureMock =
new TextureMock(&mDevice, TextureBase::TextureState::OwnedExternal);
EXPECT_CALL(*textureMock, DestroyApiObjectImpl).Times(1);
{
TextureDescriptor desc = {};
Ref<TextureBase> texture;
EXPECT_CALL(mDevice, CreateTextureImpl)
.WillOnce(Return(ByMove(AcquireRef(textureMock))));
DAWN_ASSERT_AND_ASSIGN(texture, mDevice.CreateTexture(&desc));
EXPECT_TRUE(texture->IsAlive());
}
}
}
TEST_F(DestroyObjectTests, TextureViewExplicit) {
TextureViewMock textureViewMock(GetTexture().Get());
EXPECT_CALL(textureViewMock, DestroyApiObjectImpl).Times(1);
EXPECT_TRUE(textureViewMock.IsAlive());
textureViewMock.DestroyApiObject();
EXPECT_FALSE(textureViewMock.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_F(DestroyObjectTests, TextureViewImplicit) {
TextureViewMock* textureViewMock = new TextureViewMock(GetTexture().Get());
EXPECT_CALL(*textureViewMock, DestroyApiObjectImpl).Times(1);
{
TextureViewDescriptor desc = {};
Ref<TextureViewBase> textureView;
EXPECT_CALL(mDevice, CreateTextureViewImpl)
.WillOnce(Return(ByMove(AcquireRef(textureViewMock))));
DAWN_ASSERT_AND_ASSIGN(textureView,
mDevice.CreateTextureView(GetTexture().Get(), &desc));
EXPECT_TRUE(textureView->IsAlive());
}
}
// Destroying the objects on the mDevice should result in all created objects being destroyed in
// order.
TEST_F(DestroyObjectTests, DestroyObjects) {
@ -422,6 +531,9 @@ namespace dawn_native { namespace {
SamplerMock* samplerMock = new SamplerMock(&mDevice);
ShaderModuleMock* shaderModuleMock = new ShaderModuleMock(&mDevice);
SwapChainMock* swapChainMock = new SwapChainMock(&mDevice);
TextureMock* textureMock =
new TextureMock(&mDevice, TextureBase::TextureState::OwnedInternal);
TextureViewMock* textureViewMock = new TextureViewMock(GetTexture().Get());
{
InSequence seq;
EXPECT_CALL(*renderPipelineMock, DestroyApiObjectImpl).Times(1);
@ -431,6 +543,8 @@ namespace dawn_native { namespace {
EXPECT_CALL(*bindGroupMock, DestroyApiObjectImpl).Times(1);
EXPECT_CALL(*bindGroupLayoutMock, DestroyApiObjectImpl).Times(1);
EXPECT_CALL(*shaderModuleMock, DestroyApiObjectImpl).Times(1);
EXPECT_CALL(*textureViewMock, DestroyApiObjectImpl).Times(1);
EXPECT_CALL(*textureMock, DestroyApiObjectImpl).Times(1);
EXPECT_CALL(*querySetMock, DestroyApiObjectImpl).Times(1);
EXPECT_CALL(*samplerMock, DestroyApiObjectImpl).Times(1);
EXPECT_CALL(*bufferMock, DestroyApiObjectImpl).Times(1);
@ -484,6 +598,13 @@ namespace dawn_native { namespace {
EXPECT_TRUE(computePipeline->IsCachedReference());
}
Ref<ExternalTextureBase> externalTexture;
{
ExternalTextureDescriptor desc = {};
DAWN_ASSERT_AND_ASSIGN(externalTexture, mDevice.CreateExternalTexture(&desc));
EXPECT_TRUE(externalTexture->IsAlive());
}
Ref<PipelineLayoutBase> pipelineLayout;
{
PipelineLayoutDescriptor desc = {};
@ -560,17 +681,39 @@ namespace dawn_native { namespace {
EXPECT_TRUE(swapChain->IsAlive());
}
Ref<TextureBase> texture;
{
TextureDescriptor desc = {};
EXPECT_CALL(mDevice, CreateTextureImpl)
.WillOnce(Return(ByMove(AcquireRef(textureMock))));
DAWN_ASSERT_AND_ASSIGN(texture, mDevice.CreateTexture(&desc));
EXPECT_TRUE(texture->IsAlive());
}
Ref<TextureViewBase> textureView;
{
TextureViewDescriptor desc = {};
EXPECT_CALL(mDevice, CreateTextureViewImpl)
.WillOnce(Return(ByMove(AcquireRef(textureViewMock))));
DAWN_ASSERT_AND_ASSIGN(textureView,
mDevice.CreateTextureView(GetTexture().Get(), &desc));
EXPECT_TRUE(textureView->IsAlive());
}
mDevice.DestroyObjects();
EXPECT_FALSE(bindGroup->IsAlive());
EXPECT_FALSE(bindGroupLayout->IsAlive());
EXPECT_FALSE(buffer->IsAlive());
EXPECT_FALSE(computePipeline->IsAlive());
EXPECT_FALSE(externalTexture->IsAlive());
EXPECT_FALSE(pipelineLayout->IsAlive());
EXPECT_FALSE(querySet->IsAlive());
EXPECT_FALSE(renderPipeline->IsAlive());
EXPECT_FALSE(sampler->IsAlive());
EXPECT_FALSE(shaderModule->IsAlive());
EXPECT_FALSE(swapChain->IsAlive());
EXPECT_FALSE(texture->IsAlive());
EXPECT_FALSE(textureView->IsAlive());
}
}} // namespace dawn_native::

View File

@ -0,0 +1,36 @@
// Copyright 2021 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef TESTS_UNITTESTS_NATIVE_MOCKS_EXTERNALTEXTURE_MOCK_H_
#define TESTS_UNITTESTS_NATIVE_MOCKS_EXTERNALTEXTURE_MOCK_H_
#include "dawn_native/Device.h"
#include "dawn_native/ExternalTexture.h"
#include <gmock/gmock.h>
namespace dawn_native {
class ExternalTextureMock : public ExternalTextureBase {
public:
ExternalTextureMock(DeviceBase* device) : ExternalTextureBase(device) {
}
~ExternalTextureMock() override = default;
MOCK_METHOD(void, DestroyApiObjectImpl, (), (override));
};
} // namespace dawn_native
#endif // TESTS_UNITTESTS_NATIVE_MOCKS_EXTERNALTEXTURE_MOCK_H_

View File

@ -0,0 +1,46 @@
// Copyright 2021 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef TESTS_UNITTESTS_NATIVE_MOCKS_TEXTURE_MOCK_H_
#define TESTS_UNITTESTS_NATIVE_MOCKS_TEXTURE_MOCK_H_
#include "dawn_native/Device.h"
#include "dawn_native/Texture.h"
#include <gmock/gmock.h>
namespace dawn_native {
class TextureMock : public TextureBase {
public:
TextureMock(DeviceBase* device, TextureBase::TextureState state)
: TextureBase(device, state) {
}
~TextureMock() override = default;
MOCK_METHOD(void, DestroyApiObjectImpl, (), (override));
};
class TextureViewMock : public TextureViewBase {
public:
TextureViewMock(TextureBase* texture) : TextureViewBase(texture) {
}
~TextureViewMock() override = default;
MOCK_METHOD(void, DestroyApiObjectImpl, (), (override));
};
} // namespace dawn_native
#endif // TESTS_UNITTESTS_NATIVE_MOCKS_TEXTURE_MOCK_H_