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>
diff --git a/BUILD.gn b/BUILD.gn
index 7d80221..f58ef64 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -636,7 +636,7 @@
     "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",
diff --git a/dawn.json b/dawn.json
index 0b132a1..d4fec2f 100644
--- a/dawn.json
+++ b/dawn.json
@@ -986,6 +986,9 @@
                 "args": [
                     {"name": "descriptor", "type": "texture view descriptor", "annotation": "const*"}
                 ]
+            },
+            {
+                "name": "destroy"
             }
         ]
     },
diff --git a/src/dawn_native/Texture.cpp b/src/dawn_native/Texture.cpp
index 990c324..c97cdfd 100644
--- a/src/dawn_native/Texture.cpp
+++ b/src/dawn_native/Texture.cpp
@@ -197,6 +197,9 @@
         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 @@
 
     // 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 @@
           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 @@
         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 @@
         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)
diff --git a/src/dawn_native/Texture.h b/src/dawn_native/Texture.h
index 10f9849..db613cf 100644
--- a/src/dawn_native/Texture.h
+++ b/src/dawn_native/Texture.h
@@ -46,7 +46,9 @@
 
     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 @@
         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 @@
         uint32_t mMipLevelCount;
         uint32_t mSampleCount;
         dawn::TextureUsageBit mUsage = dawn::TextureUsageBit::None;
+        TextureState mState;
     };
 
     class TextureViewBase : public ObjectBase {
diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp
index 3cfff18..ecce6f2 100644
--- a/src/dawn_native/d3d12/TextureD3D12.cpp
+++ b/src/dawn_native/d3d12/TextureD3D12.cpp
@@ -108,7 +108,7 @@
     }
 
     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 @@
     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 {
diff --git a/src/dawn_native/d3d12/TextureD3D12.h b/src/dawn_native/d3d12/TextureD3D12.h
index 5ca9cf3..5167612 100644
--- a/src/dawn_native/d3d12/TextureD3D12.h
+++ b/src/dawn_native/d3d12/TextureD3D12.h
@@ -38,6 +38,9 @@
                                 dawn::TextureUsageBit usage);
 
       private:
+        // Dawn API
+        void DestroyImpl() override;
+
         UINT16 GetDepthOrArraySize();
 
         ComPtr<ID3D12Resource> mResource = {};
diff --git a/src/dawn_native/metal/TextureMTL.h b/src/dawn_native/metal/TextureMTL.h
index 51951f6..d4feb5b 100644
--- a/src/dawn_native/metal/TextureMTL.h
+++ b/src/dawn_native/metal/TextureMTL.h
@@ -42,6 +42,8 @@
         id<MTLTexture> GetMTLTexture();
 
       private:
+        void DestroyImpl() override;
+
         id<MTLTexture> mMtlTexture = nil;
     };
 
diff --git a/src/dawn_native/metal/TextureMTL.mm b/src/dawn_native/metal/TextureMTL.mm
index 75d6b69..b364d4a 100644
--- a/src/dawn_native/metal/TextureMTL.mm
+++ b/src/dawn_native/metal/TextureMTL.mm
@@ -194,14 +194,14 @@
     }
 
     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 @@
                      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 @@
     }
 
     Texture::~Texture() {
+        Destroy();
+    }
+
+    void Texture::DestroyImpl() {
         [mMtlTexture release];
+        mMtlTexture = nil;
     }
 
     id<MTLTexture> Texture::GetMTLTexture() {
diff --git a/src/dawn_native/null/DeviceNull.cpp b/src/dawn_native/null/DeviceNull.cpp
index 4a88b0b..0482f2e 100644
--- a/src/dawn_native/null/DeviceNull.cpp
+++ b/src/dawn_native/null/DeviceNull.cpp
@@ -110,7 +110,7 @@
         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,
diff --git a/src/dawn_native/opengl/SwapChainGL.cpp b/src/dawn_native/opengl/SwapChainGL.cpp
index 49cb152..2a9fe29 100644
--- a/src/dawn_native/opengl/SwapChainGL.cpp
+++ b/src/dawn_native/opengl/SwapChainGL.cpp
@@ -40,7 +40,8 @@
             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*) {
diff --git a/src/dawn_native/opengl/TextureGL.cpp b/src/dawn_native/opengl/TextureGL.cpp
index 8f70cf3..04f2942 100644
--- a/src/dawn_native/opengl/TextureGL.cpp
+++ b/src/dawn_native/opengl/TextureGL.cpp
@@ -119,7 +119,7 @@
     // 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 @@
         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 {
diff --git a/src/dawn_native/opengl/TextureGL.h b/src/dawn_native/opengl/TextureGL.h
index 05d5c9e..1dacd17 100644
--- a/src/dawn_native/opengl/TextureGL.h
+++ b/src/dawn_native/opengl/TextureGL.h
@@ -32,7 +32,10 @@
     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 @@
         TextureFormatInfo GetGLFormat() const;
 
       private:
+        void DestroyImpl() override;
+
         GLuint mHandle;
         GLenum mTarget;
     };
diff --git a/src/dawn_native/vulkan/TextureVk.cpp b/src/dawn_native/vulkan/TextureVk.cpp
index f57d359..872393d 100644
--- a/src/dawn_native/vulkan/TextureVk.cpp
+++ b/src/dawn_native/vulkan/TextureVk.cpp
@@ -246,7 +246,7 @@
     }
 
     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 @@
         }
     }
 
+    // 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) {
diff --git a/src/dawn_native/vulkan/TextureVk.h b/src/dawn_native/vulkan/TextureVk.h
index e17ebb2..a9cf6a0 100644
--- a/src/dawn_native/vulkan/TextureVk.h
+++ b/src/dawn_native/vulkan/TextureVk.h
@@ -40,6 +40,8 @@
         void TransitionUsageNow(VkCommandBuffer commands, dawn::TextureUsageBit usage);
 
       private:
+        void DestroyImpl() override;
+
         VkImage mHandle = VK_NULL_HANDLE;
         DeviceMemoryAllocation mMemoryAllocation;
 
diff --git a/src/tests/end2end/DestroyBufferTests.cpp b/src/tests/end2end/DestroyTests.cpp
similarity index 77%
rename from src/tests/end2end/DestroyBufferTests.cpp
rename to src/tests/end2end/DestroyTests.cpp
index 6012536..7ac3244 100644
--- a/src/tests/end2end/DestroyBufferTests.cpp
+++ b/src/tests/end2end/DestroyTests.cpp
@@ -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 @@
 };

 

 // 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 @@
 }

 

 // 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 @@
 

 // 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 @@
     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);

diff --git a/src/tests/unittests/validation/TextureValidationTests.cpp b/src/tests/unittests/validation/TextureValidationTests.cpp
index dce5af1..df755c5 100644
--- a/src/tests/unittests/validation/TextureValidationTests.cpp
+++ b/src/tests/unittests/validation/TextureValidationTests.cpp
@@ -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 @@
         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
diff --git a/src/tests/unittests/validation/TextureViewValidationTests.cpp b/src/tests/unittests/validation/TextureViewValidationTests.cpp
index b330600..589812c 100644
--- a/src/tests/unittests/validation/TextureViewValidationTests.cpp
+++ b/src/tests/unittests/validation/TextureViewValidationTests.cpp
@@ -242,4 +242,12 @@
     }
 }
 
+// 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));
+}
 }
\ No newline at end of file