Destroy frontend and backend for Textures

Same idea as for buffers, Destroy can be used to free GPU memory
associated with resources without waiting for javascript garbage
collection to occur.

Bug: dawn:46
Change-Id: Ia796b06b5228cbec4cfe8d78a500f967181d8c1f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/5540
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Natasha Lee <natlee@microsoft.com>
This commit is contained in:
Natasha Lee 2019-03-27 22:04:10 +00:00 committed by Commit Bot service account
parent 889d743baa
commit cae68ff846
17 changed files with 235 additions and 52 deletions

View File

@ -636,7 +636,7 @@ test("dawn_end2end_tests") {
"src/tests/end2end/CopyTests.cpp",
"src/tests/end2end/DebugMarkerTests.cpp",
"src/tests/end2end/DepthStencilStateTests.cpp",
"src/tests/end2end/DestroyBufferTests.cpp",
"src/tests/end2end/DestroyTests.cpp",
"src/tests/end2end/DrawIndexedTests.cpp",
"src/tests/end2end/DrawTests.cpp",
"src/tests/end2end/FenceTests.cpp",

View File

@ -986,6 +986,9 @@
"args": [
{"name": "descriptor", "type": "texture view descriptor", "annotation": "const*"}
]
},
{
"name": "destroy"
}
]
},

View File

@ -197,6 +197,9 @@ namespace dawn_native {
if (descriptor->nextInChain != nullptr) {
return DAWN_VALIDATION_ERROR("nextInChain must be nullptr");
}
if (texture->GetTextureState() == TextureBase::TextureState::Destroyed) {
return DAWN_VALIDATION_ERROR("Destroyed texture used to create texture view");
}
DAWN_TRY(device->ValidateObject(texture));
DAWN_TRY(ValidateTextureViewDimension(descriptor->dimension));
@ -322,7 +325,9 @@ namespace dawn_native {
// TextureBase
TextureBase::TextureBase(DeviceBase* device, const TextureDescriptor* descriptor)
TextureBase::TextureBase(DeviceBase* device,
const TextureDescriptor* descriptor,
TextureState state)
: ObjectBase(device),
mDimension(descriptor->dimension),
mFormat(descriptor->format),
@ -330,7 +335,8 @@ namespace dawn_native {
mArrayLayerCount(descriptor->arrayLayerCount),
mMipLevelCount(descriptor->mipLevelCount),
mSampleCount(descriptor->sampleCount),
mUsage(descriptor->usage) {
mUsage(descriptor->usage),
mState(state) {
}
TextureBase::TextureBase(DeviceBase* device, ObjectBase::ErrorTag tag)
@ -371,8 +377,24 @@ namespace dawn_native {
return mUsage;
}
TextureBase::TextureState TextureBase::GetTextureState() const {
ASSERT(!IsError());
return mState;
}
MaybeError TextureBase::ValidateCanUseInSubmitNow() const {
ASSERT(!IsError());
if (mState == TextureState::Destroyed) {
return DAWN_VALIDATION_ERROR("Destroyed texture used in a submit");
}
return {};
}
MaybeError TextureBase::ValidateCanCreateTextureViewNow() const {
ASSERT(!IsError());
if (mState == TextureState::Destroyed) {
return DAWN_VALIDATION_ERROR("Destroyed texture used to create texture view");
}
return {};
}
@ -395,6 +417,16 @@ namespace dawn_native {
return GetDevice()->CreateTextureView(this, descriptor);
}
void TextureBase::Destroy() {
if (mState == TextureState::OwnedInternal) {
DestroyImpl();
}
mState = TextureState::Destroyed;
}
void TextureBase::DestroyImpl() {
}
// TextureViewBase
TextureViewBase::TextureViewBase(TextureBase* texture, const TextureViewDescriptor* descriptor)

View File

@ -46,7 +46,9 @@ namespace dawn_native {
class TextureBase : public ObjectBase {
public:
TextureBase(DeviceBase* device, const TextureDescriptor* descriptor);
enum class TextureState { OwnedInternal, OwnedExternal, Destroyed };
TextureBase(DeviceBase* device, const TextureDescriptor* descriptor, TextureState state);
static TextureBase* MakeError(DeviceBase* device);
@ -57,17 +59,21 @@ namespace dawn_native {
uint32_t GetNumMipLevels() const;
uint32_t GetSampleCount() const;
dawn::TextureUsageBit GetUsage() const;
TextureState GetTextureState() const;
MaybeError ValidateCanUseInSubmitNow() const;
MaybeError ValidateCanCreateTextureViewNow() const;
bool IsMultisampledTexture() const;
// Dawn API
TextureViewBase* CreateDefaultTextureView();
TextureViewBase* CreateTextureView(const TextureViewDescriptor* descriptor);
void Destroy();
private:
TextureBase(DeviceBase* device, ObjectBase::ErrorTag tag);
virtual void DestroyImpl();
dawn::TextureDimension mDimension;
dawn::TextureFormat mFormat;
@ -76,6 +82,7 @@ namespace dawn_native {
uint32_t mMipLevelCount;
uint32_t mSampleCount;
dawn::TextureUsageBit mUsage = dawn::TextureUsageBit::None;
TextureState mState;
};
class TextureViewBase : public ObjectBase {

View File

@ -108,7 +108,7 @@ namespace dawn_native { namespace d3d12 {
}
Texture::Texture(Device* device, const TextureDescriptor* descriptor)
: TextureBase(device, descriptor) {
: TextureBase(device, descriptor, TextureState::OwnedInternal) {
D3D12_RESOURCE_DESC resourceDescriptor;
resourceDescriptor.Dimension = D3D12TextureDimension(GetDimension());
resourceDescriptor.Alignment = 0;
@ -136,14 +136,18 @@ namespace dawn_native { namespace d3d12 {
Texture::Texture(Device* device,
const TextureDescriptor* descriptor,
ID3D12Resource* nativeTexture)
: TextureBase(device, descriptor), mResourcePtr(nativeTexture) {
: TextureBase(device, descriptor, TextureState::OwnedExternal),
mResourcePtr(nativeTexture) {
}
Texture::~Texture() {
if (mResource) {
// If we own the resource, release it.
ToBackend(GetDevice())->GetResourceAllocator()->Release(mResource);
}
Destroy();
}
void Texture::DestroyImpl() {
// If we own the resource, release it.
ToBackend(GetDevice())->GetResourceAllocator()->Release(mResource);
mResource = nullptr;
}
DXGI_FORMAT Texture::GetD3D12Format() const {

View File

@ -38,6 +38,9 @@ namespace dawn_native { namespace d3d12 {
dawn::TextureUsageBit usage);
private:
// Dawn API
void DestroyImpl() override;
UINT16 GetDepthOrArraySize();
ComPtr<ID3D12Resource> mResource = {};

View File

@ -42,6 +42,8 @@ namespace dawn_native { namespace metal {
id<MTLTexture> GetMTLTexture();
private:
void DestroyImpl() override;
id<MTLTexture> mMtlTexture = nil;
};

View File

@ -194,14 +194,14 @@ namespace dawn_native { namespace metal {
}
Texture::Texture(Device* device, const TextureDescriptor* descriptor)
: TextureBase(device, descriptor) {
: TextureBase(device, descriptor, TextureState::OwnedInternal) {
MTLTextureDescriptor* mtlDesc = CreateMetalTextureDescriptor(descriptor);
mMtlTexture = [device->GetMTLDevice() newTextureWithDescriptor:mtlDesc];
[mtlDesc release];
}
Texture::Texture(Device* device, const TextureDescriptor* descriptor, id<MTLTexture> mtlTexture)
: TextureBase(device, descriptor), mMtlTexture(mtlTexture) {
: TextureBase(device, descriptor, TextureState::OwnedInternal), mMtlTexture(mtlTexture) {
[mMtlTexture retain];
}
@ -209,7 +209,7 @@ namespace dawn_native { namespace metal {
const TextureDescriptor* descriptor,
IOSurfaceRef ioSurface,
uint32_t plane)
: TextureBase(device, descriptor) {
: TextureBase(device, descriptor, TextureState::OwnedInternal) {
MTLTextureDescriptor* mtlDesc = CreateMetalTextureDescriptor(descriptor);
mtlDesc.storageMode = MTLStorageModeManaged;
mMtlTexture = [device->GetMTLDevice() newTextureWithDescriptor:mtlDesc
@ -219,7 +219,12 @@ namespace dawn_native { namespace metal {
}
Texture::~Texture() {
Destroy();
}
void Texture::DestroyImpl() {
[mMtlTexture release];
mMtlTexture = nil;
}
id<MTLTexture> Texture::GetMTLTexture() {

View File

@ -110,7 +110,7 @@ namespace dawn_native { namespace null {
return new SwapChain(this, descriptor);
}
ResultOrError<TextureBase*> Device::CreateTextureImpl(const TextureDescriptor* descriptor) {
return new Texture(this, descriptor);
return new Texture(this, descriptor, TextureBase::TextureState::OwnedInternal);
}
ResultOrError<TextureViewBase*> Device::CreateTextureViewImpl(
TextureBase* texture,

View File

@ -40,7 +40,8 @@ namespace dawn_native { namespace opengl {
return nullptr;
}
GLuint nativeTexture = next.texture.u32;
return new Texture(ToBackend(GetDevice()), descriptor, nativeTexture);
return new Texture(ToBackend(GetDevice()), descriptor, nativeTexture,
TextureBase::TextureState::OwnedExternal);
}
void SwapChain::OnBeforePresent(TextureBase*) {

View File

@ -119,7 +119,7 @@ namespace dawn_native { namespace opengl {
// Texture
Texture::Texture(Device* device, const TextureDescriptor* descriptor)
: Texture(device, descriptor, GenTexture()) {
: Texture(device, descriptor, GenTexture(), TextureState::OwnedInternal) {
uint32_t width = GetSize().width;
uint32_t height = GetSize().height;
uint32_t levels = GetNumMipLevels();
@ -150,14 +150,21 @@ namespace dawn_native { namespace opengl {
glTexParameteri(mTarget, GL_TEXTURE_MAX_LEVEL, levels - 1);
}
Texture::Texture(Device* device, const TextureDescriptor* descriptor, GLuint handle)
: TextureBase(device, descriptor), mHandle(handle) {
Texture::Texture(Device* device,
const TextureDescriptor* descriptor,
GLuint handle,
TextureState state)
: TextureBase(device, descriptor, state), mHandle(handle) {
mTarget = TargetForDimensionAndArrayLayers(GetDimension(), GetArrayLayers());
}
Texture::~Texture() {
// TODO(kainino@chromium.org): delete texture (but only when not using the native texture
// constructor?)
Destroy();
}
void Texture::DestroyImpl() {
glDeleteTextures(1, &mHandle);
mHandle = 0;
}
GLuint Texture::GetHandle() const {

View File

@ -32,7 +32,10 @@ namespace dawn_native { namespace opengl {
class Texture : public TextureBase {
public:
Texture(Device* device, const TextureDescriptor* descriptor);
Texture(Device* device, const TextureDescriptor* descriptor, GLuint handle);
Texture(Device* device,
const TextureDescriptor* descriptor,
GLuint handle,
TextureState state);
~Texture();
GLuint GetHandle() const;
@ -40,6 +43,8 @@ namespace dawn_native { namespace opengl {
TextureFormatInfo GetGLFormat() const;
private:
void DestroyImpl() override;
GLuint mHandle;
GLenum mTarget;
};

View File

@ -246,7 +246,7 @@ namespace dawn_native { namespace vulkan {
}
Texture::Texture(Device* device, const TextureDescriptor* descriptor)
: TextureBase(device, descriptor) {
: TextureBase(device, descriptor, TextureState::OwnedInternal) {
// Create the Vulkan image "container". We don't need to check that the format supports the
// combination of sample, usage etc. because validation should have been done in the Dawn
// frontend already based on the minimum supported formats in the Vulkan spec
@ -291,17 +291,23 @@ namespace dawn_native { namespace vulkan {
}
}
// With this constructor, the lifetime of the resource is externally managed.
Texture::Texture(Device* device, const TextureDescriptor* descriptor, VkImage nativeImage)
: TextureBase(device, descriptor), mHandle(nativeImage) {
: TextureBase(device, descriptor, TextureState::OwnedExternal), mHandle(nativeImage) {
}
Texture::~Texture() {
Destroy();
}
void Texture::DestroyImpl() {
Device* device = ToBackend(GetDevice());
// If we own the resource, release it.
if (mMemoryAllocation.GetMemory() != VK_NULL_HANDLE) {
// We need to free both the memory allocation and the container. Memory should be freed
// after the VkImage is destroyed and this is taken care of by the FencedDeleter.
// We need to free both the memory allocation and the container. Memory should be
// freed after the VkImage is destroyed and this is taken care of by the
// FencedDeleter.
device->GetMemoryAllocator()->Free(&mMemoryAllocation);
if (mHandle != VK_NULL_HANDLE) {

View File

@ -40,6 +40,8 @@ namespace dawn_native { namespace vulkan {
void TransitionUsageNow(VkCommandBuffer commands, dawn::TextureUsageBit usage);
private:
void DestroyImpl() override;
VkImage mHandle = VK_NULL_HANDLE;
DeviceMemoryAllocation mMemoryAllocation;

View File

@ -19,7 +19,7 @@
constexpr uint32_t kRTSize = 4;
class DestroyBufferTest : public DawnTest {
class DestroyTest : public DawnTest {
protected:
void SetUp() override {
DawnTest::SetUp();
@ -96,7 +96,7 @@ class DestroyBufferTest : public DawnTest {
};
// Destroy before submit will result in error, and nothing drawn
TEST_P(DestroyBufferTest, DestroyBeforeSubmit) {
TEST_P(DestroyTest, BufferDestroyBeforeSubmit) {
RGBA8 notFilled(0, 0, 0, 0);
dawn::CommandBuffer commands = CreateTriangleCommandBuffer();
@ -107,7 +107,7 @@ TEST_P(DestroyBufferTest, DestroyBeforeSubmit) {
}
// Destroy after submit will draw successfully
TEST_P(DestroyBufferTest, DestroyAfterSubmit) {
TEST_P(DestroyTest, BufferDestroyAfterSubmit) {
RGBA8 filled(0, 255, 0, 255);
dawn::CommandBuffer commands = CreateTriangleCommandBuffer();
@ -119,7 +119,7 @@ TEST_P(DestroyBufferTest, DestroyAfterSubmit) {
// First submit succeeds, draws triangle, second submit fails
// after destroy is called on the buffer, pixel does not change
TEST_P(DestroyBufferTest, SubmitDestroySubmit) {
TEST_P(DestroyTest, BufferSubmitDestroySubmit) {
RGBA8 filled(0, 255, 0, 255);
dawn::CommandBuffer commands = CreateTriangleCommandBuffer();
@ -135,4 +135,37 @@ TEST_P(DestroyBufferTest, SubmitDestroySubmit) {
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, 1, 3);
}
DAWN_INSTANTIATE_TEST(DestroyBufferTest, D3D12Backend, MetalBackend, OpenGLBackend, VulkanBackend);
// Destroy texture before submit should fail submit
TEST_P(DestroyTest, TextureDestroyBeforeSubmit) {
dawn::CommandBuffer commands = CreateTriangleCommandBuffer();
renderPass.color.Destroy();
ASSERT_DEVICE_ERROR(queue.Submit(1, &commands));
}
// Destroy after submit will draw successfully
TEST_P(DestroyTest, TextureDestroyAfterSubmit) {
RGBA8 filled(0, 255, 0, 255);
dawn::CommandBuffer commands = CreateTriangleCommandBuffer();
queue.Submit(1, &commands);
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, 1, 3);
renderPass.color.Destroy();
}
// First submit succeeds, draws triangle, second submit fails
// after destroy is called on the texture
TEST_P(DestroyTest, TextureSubmitDestroySubmit) {
RGBA8 filled(0, 255, 0, 255);
dawn::CommandBuffer commands = CreateTriangleCommandBuffer();
queue.Submit(1, &commands);
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, 1, 3);
renderPass.color.Destroy();
// Submit fails because texture was destroyed
ASSERT_DEVICE_ERROR(queue.Submit(1, &commands));
}
DAWN_INSTANTIATE_TEST(DestroyTest, D3D12Backend, MetalBackend, OpenGLBackend, VulkanBackend);

View File

@ -15,35 +15,39 @@
#include "tests/unittests/validation/ValidationTest.h"
#include "common/Constants.h"
#include "utils/ComboRenderPipelineDescriptor.h"
#include "utils/DawnHelpers.h"
namespace {
class TextureValidationTest : public ValidationTest {
protected:
dawn::TextureDescriptor CreateDefaultTextureDescriptor() {
dawn::TextureDescriptor descriptor;
descriptor.size.width = kWidth;
descriptor.size.height = kHeight;
descriptor.size.depth = 1;
descriptor.arrayLayerCount = kDefaultArraySize;
descriptor.mipLevelCount = kDefaultMipLevels;
descriptor.sampleCount = kDefaultSampleCount;
descriptor.dimension = dawn::TextureDimension::e2D;
descriptor.format = kDefaultTextureFormat;
descriptor.usage = dawn::TextureUsageBit::OutputAttachment | dawn::TextureUsageBit::Sampled;
return descriptor;
}
dawn::Queue queue = device.CreateQueue();
private:
static constexpr uint32_t kWidth = 32;
static constexpr uint32_t kHeight = 32;
static constexpr uint32_t kDefaultArraySize = 1;
static constexpr uint32_t kDefaultMipLevels = 1;
static constexpr uint32_t kDefaultSampleCount = 1;
static constexpr dawn::TextureFormat kDefaultTextureFormat = dawn::TextureFormat::R8G8B8A8Unorm;
};
constexpr uint32_t kWidth = 32;
constexpr uint32_t kHeight = 32;
constexpr uint32_t kDefaultArraySize = 1;
constexpr uint32_t kDefaultMipLevels = 1;
constexpr uint32_t kDefaultSampleCount = 1;
constexpr dawn::TextureFormat kDefaultTextureFormat = dawn::TextureFormat::R8G8B8A8Unorm;
dawn::TextureDescriptor CreateDefaultTextureDescriptor() {
dawn::TextureDescriptor descriptor;
descriptor.size.width = kWidth;
descriptor.size.height = kHeight;
descriptor.size.depth = 1;
descriptor.arrayLayerCount = kDefaultArraySize;
descriptor.mipLevelCount = kDefaultMipLevels;
descriptor.sampleCount = kDefaultSampleCount;
descriptor.dimension = dawn::TextureDimension::e2D;
descriptor.format = kDefaultTextureFormat;
descriptor.usage = dawn::TextureUsageBit::OutputAttachment | dawn::TextureUsageBit::Sampled;
return descriptor;
}
// Test the validation of sample count
TEST_F(TextureValidationTest, SampleCount) {
dawn::TextureDescriptor defaultDescriptor = CreateDefaultTextureDescriptor();
@ -81,4 +85,65 @@ TEST_F(TextureValidationTest, SampleCount) {
ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
}
}
// Test that it is valid to destroy a texture
TEST_F(TextureValidationTest, DestroyTexture) {
dawn::TextureDescriptor descriptor = CreateDefaultTextureDescriptor();
dawn::Texture texture = device.CreateTexture(&descriptor);
texture.Destroy();
}
// Test that it's valid to destroy a destroyed texture
TEST_F(TextureValidationTest, DestroyDestroyedTexture) {
dawn::TextureDescriptor descriptor = CreateDefaultTextureDescriptor();
dawn::Texture texture = device.CreateTexture(&descriptor);
texture.Destroy();
texture.Destroy();
}
// Test that it's invalid to submit a destroyed texture in a queue
// in the case of destroy, encode, submit
TEST_F(TextureValidationTest, DestroyEncodeSubmit) {
dawn::TextureDescriptor descriptor = CreateDefaultTextureDescriptor();
dawn::Texture texture = device.CreateTexture(&descriptor);
dawn::TextureView textureView = texture.CreateDefaultTextureView();
utils::ComboRenderPassDescriptor renderPass({textureView});
// Destroy the texture
texture.Destroy();
dawn::CommandEncoder encoder_post_destroy = device.CreateCommandEncoder();
{
dawn::RenderPassEncoder pass = encoder_post_destroy.BeginRenderPass(&renderPass);
pass.EndPass();
}
dawn::CommandBuffer commands = encoder_post_destroy.Finish();
// Submit should fail due to destroyed texture
ASSERT_DEVICE_ERROR(queue.Submit(1, &commands));
}
// Test that it's invalid to submit a destroyed texture in a queue
// in the case of encode, destroy, submit
TEST_F(TextureValidationTest, EncodeDestroySubmit) {
dawn::TextureDescriptor descriptor = CreateDefaultTextureDescriptor();
dawn::Texture texture = device.CreateTexture(&descriptor);
dawn::TextureView textureView = texture.CreateDefaultTextureView();
utils::ComboRenderPassDescriptor renderPass({textureView});
dawn::CommandEncoder encoder = device.CreateCommandEncoder();
{
dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
pass.EndPass();
}
dawn::CommandBuffer commands = encoder.Finish();
// Destroy the texture
texture.Destroy();
// Submit should fail due to destroyed texture
ASSERT_DEVICE_ERROR(queue.Submit(1, &commands));
}
} // namespace

View File

@ -242,4 +242,12 @@ TEST_F(TextureViewValidationTest, TextureViewFormatCompatibility) {
}
}
// Test that it's invalid to create a texture view from a destroyed texture
TEST_F(TextureViewValidationTest, DestroyCreateTextureView) {
dawn::Texture texture = Create2DArrayTexture(device, 1);
dawn::TextureViewDescriptor descriptor =
CreateDefaultTextureViewDescriptor(dawn::TextureViewDimension::e2D);
texture.Destroy();
ASSERT_DEVICE_ERROR(texture.CreateTextureView(&descriptor));
}
}