[graphite] Add Context create/deleteBackendTexture calls.

Bug: skia:12633
Change-Id: Ida78c4145423376dc0267096a1d758b74144fd0c
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/477139
Reviewed-by: Jim Van Verth <jvanverth@google.com>
Commit-Queue: Greg Daniel <egdaniel@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index f378cad..aa0df7f 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -2038,6 +2038,9 @@
     }
     if (skia_enable_graphite) {
       sources += graphite_tests_sources
+      if (skia_use_metal) {
+        sources += graphite_metal_tests_sources
+      }
     }
     if (!skia_enable_skgpu_v1) {
       sources -= skgpu_v1_tests_sources
diff --git a/experimental/graphite/include/BackendTexture.h b/experimental/graphite/include/BackendTexture.h
index 9d70806..192d8e3 100644
--- a/experimental/graphite/include/BackendTexture.h
+++ b/experimental/graphite/include/BackendTexture.h
@@ -20,10 +20,22 @@
 
 class BackendTexture {
 public:
+    BackendTexture() {}
 #ifdef SK_METAL
-    BackendTexture(SkISize dimensions, sk_cfp<mtl::Handle> mtlTexture);
+    // The BackendTexture will not call retain or release on the passed in mtl::Handle. Thus the
+    // client must keep the mtl::Handle valid until they are no longer using the BackendTexture.
+    BackendTexture(SkISize dimensions, mtl::Handle mtlTexture);
 #endif
 
+    BackendTexture(const BackendTexture&);
+
+    ~BackendTexture();
+
+    BackendTexture& operator=(const BackendTexture&);
+
+    bool operator==(const BackendTexture&) const;
+    bool operator!=(const BackendTexture& that) const { return !(*this == that); }
+
     bool isValid() const { return fInfo.isValid(); }
     BackendApi backend() const { return fInfo.backend(); }
 
@@ -32,7 +44,7 @@
     const TextureInfo& info() const { return fInfo; }
 
 #ifdef SK_METAL
-    sk_cfp<mtl::Handle> getMtlTexture() const;
+    mtl::Handle getMtlTexture() const;
 #endif
 
 private:
@@ -41,7 +53,7 @@
 
     union {
 #ifdef SK_METAL
-        sk_cfp<mtl::Handle> fMtlTexture;
+        mtl::Handle fMtlTexture;
 #endif
     };
 };
diff --git a/experimental/graphite/include/Context.h b/experimental/graphite/include/Context.h
index cc9ec30..33c88a9 100644
--- a/experimental/graphite/include/Context.h
+++ b/experimental/graphite/include/Context.h
@@ -18,10 +18,12 @@
 
 namespace skgpu {
 
+class BackendTexture;
 class ContextPriv;
 class Gpu;
 class Recorder;
 class Recording;
+class TextureInfo;
 namespace mtl { struct BackendContext; }
 
 struct ShaderCombo {
@@ -57,6 +59,8 @@
     static sk_sp<Context> MakeMetal(const skgpu::mtl::BackendContext&);
 #endif
 
+    BackendApi backend() const { return fBackend; }
+
     sk_sp<Recorder> createRecorder();
 
     void insertRecording(std::unique_ptr<Recording>);
@@ -64,18 +68,40 @@
 
     void preCompile(const PaintCombo&);
 
+    /**
+     * Creates a new backend gpu texture matching the dimensinos and TextureInfo. If an invalid
+     * TextureInfo or a TextureInfo Skia can't support is passed in, this will return an invalid
+     * BackendTexture. Thus the client should check isValid on the returned BackendTexture to know
+     * if it succeeded or not.
+     *
+     * If this does return a valid BackendTexture, the caller is required to use
+     * Context::deleteBackendTexture to delete that texture.
+     */
+    BackendTexture createBackendTexture(SkISize dimensions, const TextureInfo&);
+
+    /**
+     * Called to delete the passed in BackendTexture. This should only be called if the
+     * BackendTexture was created by calling Context::createBackendTexture. If the BackendTexture is
+     * not valid or does not match the BackendApi of the Context then nothing happens.
+     *
+     * Otherwise this will delete/release the backend object that is wrapped in the BackendTexture.
+     * The BackendTexture will be reset to an invalid state and should not be used again.
+     */
+    void deleteBackendTexture(BackendTexture&);
+
     // Provides access to functions that aren't part of the public API.
     ContextPriv priv();
     const ContextPriv priv() const;  // NOLINT(readability-const-return-type)
 
 protected:
-    Context(sk_sp<Gpu>);
+    Context(sk_sp<Gpu>, BackendApi);
 
 private:
     friend class ContextPriv;
 
     std::vector<std::unique_ptr<Recording>> fRecordings;
     sk_sp<Gpu> fGpu;
+    BackendApi fBackend;
 };
 
 } // namespace skgpu
diff --git a/experimental/graphite/include/TextureInfo.h b/experimental/graphite/include/TextureInfo.h
index eb5cf3c..850e1d7 100644
--- a/experimental/graphite/include/TextureInfo.h
+++ b/experimental/graphite/include/TextureInfo.h
@@ -39,9 +39,10 @@
 
     ~TextureInfo() {}
     TextureInfo(const TextureInfo&) = default;
-    TextureInfo& operator=(const TextureInfo&) = delete;
+    TextureInfo& operator=(const TextureInfo&);
 
     bool operator==(const TextureInfo&) const;
+    bool operator!=(const TextureInfo& that) const { return !(*this == that); }
 
     bool isValid() const { return fValid; }
     BackendApi backend() const { return fBackend; }
diff --git a/experimental/graphite/src/BackendTexture.cpp b/experimental/graphite/src/BackendTexture.cpp
index 4556c0a..9dd2cc5 100644
--- a/experimental/graphite/src/BackendTexture.cpp
+++ b/experimental/graphite/src/BackendTexture.cpp
@@ -9,13 +9,65 @@
 
 namespace skgpu {
 
-#ifdef SK_METAL
-BackendTexture::BackendTexture(SkISize dimensions, sk_cfp<mtl::Handle> mtlTexture)
-        : fDimensions(dimensions)
-        , fInfo(mtl::TextureInfo(mtlTexture.get()))
-        , fMtlTexture(std::move(mtlTexture)) {}
+BackendTexture::~BackendTexture() {}
 
-sk_cfp<mtl::Handle> BackendTexture::getMtlTexture() const {
+BackendTexture::BackendTexture(const BackendTexture& that) {
+    *this = that;
+}
+
+BackendTexture& BackendTexture::operator=(const BackendTexture& that) {
+    bool valid = this->isValid();
+    if (!that.isValid()) {
+        fInfo = {};
+        return *this;
+    } else if (valid && this->backend() != that.backend()) {
+        valid = false;
+    }
+    fDimensions = that.fDimensions;
+    fInfo = that.fInfo;
+
+    switch (that.backend()) {
+#ifdef SK_METAL
+        case BackendApi::kMetal:
+            fMtlTexture = that.fMtlTexture;
+            break;
+#endif
+        default:
+            SK_ABORT("Unsupport Backend");
+    }
+    return *this;
+}
+
+bool BackendTexture::operator==(const BackendTexture& that) const {
+    if (!this->isValid() || !that.isValid()) {
+        return false;
+    }
+
+    if (fDimensions != that.fDimensions || fInfo != that.fInfo) {
+        return false;
+    }
+
+    switch (that.backend()) {
+#ifdef SK_METAL
+        case BackendApi::kMetal:
+            if (fMtlTexture != that.fMtlTexture) {
+                return false;
+            }
+            break;
+#endif
+        default:
+            SK_ABORT("Unsupport Backend");
+    }
+    return true;
+}
+
+#ifdef SK_METAL
+BackendTexture::BackendTexture(SkISize dimensions, mtl::Handle mtlTexture)
+        : fDimensions(dimensions)
+        , fInfo(mtl::TextureInfo(mtlTexture))
+        , fMtlTexture(mtlTexture) {}
+
+mtl::Handle BackendTexture::getMtlTexture() const {
     if (this->isValid() && this->backend() == BackendApi::kMetal) {
         return fMtlTexture;
     }
diff --git a/experimental/graphite/src/Caps.h b/experimental/graphite/src/Caps.h
index b5029c2..73dad79 100644
--- a/experimental/graphite/src/Caps.h
+++ b/experimental/graphite/src/Caps.h
@@ -44,9 +44,13 @@
     virtual bool isTexturable(const TextureInfo&) const = 0;
     virtual bool isRenderable(const TextureInfo&) const = 0;
 
+    int maxTextureSize() const { return fMaxTextureSize; }
+
 protected:
     Caps();
 
+    int fMaxTextureSize = 0;
+
     std::unique_ptr<SkSL::ShaderCaps> fShaderCaps;
 
 private:
diff --git a/experimental/graphite/src/Context.cpp b/experimental/graphite/src/Context.cpp
index e42690e..294f63d 100644
--- a/experimental/graphite/src/Context.cpp
+++ b/experimental/graphite/src/Context.cpp
@@ -7,6 +7,8 @@
 
 #include "experimental/graphite/include/Context.h"
 
+#include "experimental/graphite/include/BackendTexture.h"
+#include "experimental/graphite/include/TextureInfo.h"
 #include "experimental/graphite/src/Caps.h"
 #include "experimental/graphite/src/CommandBuffer.h"
 #include "experimental/graphite/src/ContextUtils.h"
@@ -22,7 +24,7 @@
 
 namespace skgpu {
 
-Context::Context(sk_sp<Gpu> gpu) : fGpu(std::move(gpu)) {}
+Context::Context(sk_sp<Gpu> gpu, BackendApi backend) : fGpu(std::move(gpu)), fBackend(backend) {}
 Context::~Context() {}
 
 #ifdef SK_METAL
@@ -32,7 +34,7 @@
         return nullptr;
     }
 
-    return sk_sp<Context>(new Context(std::move(gpu)));
+    return sk_sp<Context>(new Context(std::move(gpu), BackendApi::kMetal));
 }
 #endif
 
@@ -79,4 +81,18 @@
     }
 }
 
+BackendTexture Context::createBackendTexture(SkISize dimensions, const TextureInfo& info) {
+    if (!info.isValid() || info.backend() != this->backend()) {
+        return {};
+    }
+    return fGpu->createBackendTexture(dimensions, info);
+}
+
+void Context::deleteBackendTexture(BackendTexture& texture) {
+    if (!texture.isValid() || texture.backend() != this->backend()) {
+        return;
+    }
+    fGpu->deleteBackendTexture(texture);
+}
+
 } // namespace skgpu
diff --git a/experimental/graphite/src/Gpu.cpp b/experimental/graphite/src/Gpu.cpp
index 17b5706..9e2803f 100644
--- a/experimental/graphite/src/Gpu.cpp
+++ b/experimental/graphite/src/Gpu.cpp
@@ -7,6 +7,8 @@
 
 #include "experimental/graphite/src/Gpu.h"
 
+#include "experimental/graphite/include/BackendTexture.h"
+#include "experimental/graphite/include/TextureInfo.h"
 #include "experimental/graphite/src/Caps.h"
 #include "experimental/graphite/src/CommandBuffer.h"
 #include "experimental/graphite/src/GpuWorkSubmission.h"
@@ -83,4 +85,20 @@
     SkASSERT(sync == SyncToCpu::kNo || fOutstandingSubmissions.empty());
 }
 
+BackendTexture Gpu::createBackendTexture(SkISize dimensions, const TextureInfo& info) {
+    if (dimensions.isEmpty() || dimensions.width()  > this->caps()->maxTextureSize() ||
+                                dimensions.height() > this->caps()->maxTextureSize()) {
+        return {};
+    }
+
+    return this->onCreateBackendTexture(dimensions, info);
+}
+
+void Gpu::deleteBackendTexture(BackendTexture& texture) {
+    this->onDeleteBackendTexture(texture);
+    // Invalidate the texture;
+    texture = BackendTexture();
+}
+
+
 } // namespace skgpu
diff --git a/experimental/graphite/src/Gpu.h b/experimental/graphite/src/Gpu.h
index 1cbdcd4..9750bb3 100644
--- a/experimental/graphite/src/Gpu.h
+++ b/experimental/graphite/src/Gpu.h
@@ -9,6 +9,7 @@
 #define skgpu_Gpu_DEFINED
 
 #include "include/core/SkRefCnt.h"
+#include "include/core/SkSize.h"
 #include "include/private/SkDeque.h"
 
 #include "experimental/graphite/include/GraphiteTypes.h"
@@ -19,10 +20,12 @@
 
 namespace skgpu {
 
+class BackendTexture;
 class Caps;
-class ResourceProvider;
 class CommandBuffer;
 class GpuWorkSubmission;
+class ResourceProvider;
+class TextureInfo;
 
 class Gpu : public SkRefCnt {
 public:
@@ -41,6 +44,9 @@
     bool submit(sk_sp<CommandBuffer>);
     void checkForFinishedWork(SyncToCpu);
 
+    BackendTexture createBackendTexture(SkISize dimensions, const TextureInfo&);
+    void deleteBackendTexture(BackendTexture&);
+
 #if GRAPHITE_TEST_UTILS
     virtual void testingOnly_startCapture() {}
     virtual void testingOnly_endCapture() {}
@@ -60,6 +66,9 @@
 private:
     virtual bool onSubmit(sk_sp<CommandBuffer>) = 0;
 
+    virtual BackendTexture onCreateBackendTexture(SkISize dimensions, const TextureInfo&) = 0;
+    virtual void onDeleteBackendTexture(BackendTexture&) = 0;
+
     sk_sp<const Caps> fCaps;
     // Compiler used for compiling SkSL into backend shader code. We only want to create the
     // compiler once, as there is significant overhead to the first compile.
diff --git a/experimental/graphite/src/TextureInfo.cpp b/experimental/graphite/src/TextureInfo.cpp
index 1c631c6..5db2eab 100644
--- a/experimental/graphite/src/TextureInfo.cpp
+++ b/experimental/graphite/src/TextureInfo.cpp
@@ -9,6 +9,30 @@
 
 namespace skgpu {
 
+TextureInfo& TextureInfo::operator=(const TextureInfo& that) {
+    if (!that.isValid()) {
+        fValid = false;
+        return *this;
+    }
+    fBackend = that.fBackend;
+    fSampleCount = that.fSampleCount;
+    fLevelCount = that.fLevelCount;
+    fProtected = that.fProtected;
+
+    switch (that.backend()) {
+#ifdef SK_METAL
+        case BackendApi::kMetal:
+            fMtlSpec = that.fMtlSpec;
+            break;
+#endif
+        default:
+            SK_ABORT("Unsupport Backend");
+    }
+
+    fValid = true;
+    return *this;
+}
+
 bool TextureInfo::operator==(const TextureInfo& that) const {
     if (!this->isValid() || !that.isValid()) {
         return false;
diff --git a/experimental/graphite/src/mtl/MtlCaps.mm b/experimental/graphite/src/mtl/MtlCaps.mm
index 6620ba9..1a7b604 100644
--- a/experimental/graphite/src/mtl/MtlCaps.mm
+++ b/experimental/graphite/src/mtl/MtlCaps.mm
@@ -212,7 +212,11 @@
 }
 
 void Caps::initCaps(const id<MTLDevice> device) {
-    // TODO
+    if (this->isMac() || fFamilyGroup >= 3) {
+        fMaxTextureSize = 16384;
+    } else {
+        fMaxTextureSize = 8192;
+    }
 }
 
 void Caps::initShaderCaps() {
diff --git a/experimental/graphite/src/mtl/MtlGpu.h b/experimental/graphite/src/mtl/MtlGpu.h
index b63efdb..19cd403 100644
--- a/experimental/graphite/src/mtl/MtlGpu.h
+++ b/experimental/graphite/src/mtl/MtlGpu.h
@@ -34,6 +34,9 @@
 
     bool onSubmit(sk_sp<skgpu::CommandBuffer>) override;
 
+    BackendTexture onCreateBackendTexture(SkISize dimensions, const skgpu::TextureInfo&) override;
+    void onDeleteBackendTexture(BackendTexture&) override;
+
 #if GRAPHITE_TEST_UTILS
     void testingOnly_startCapture() override;
     void testingOnly_endCapture() override;
diff --git a/experimental/graphite/src/mtl/MtlGpu.mm b/experimental/graphite/src/mtl/MtlGpu.mm
index 336d6ad..bf708ae 100644
--- a/experimental/graphite/src/mtl/MtlGpu.mm
+++ b/experimental/graphite/src/mtl/MtlGpu.mm
@@ -7,9 +7,12 @@
 
 #include "experimental/graphite/src/mtl/MtlGpu.h"
 
+#include "experimental/graphite/include/BackendTexture.h"
+#include "experimental/graphite/include/TextureInfo.h"
 #include "experimental/graphite/src/Caps.h"
 #include "experimental/graphite/src/mtl/MtlCommandBuffer.h"
 #include "experimental/graphite/src/mtl/MtlResourceProvider.h"
+#include "experimental/graphite/src/mtl/MtlTexture.h"
 
 namespace skgpu::mtl {
 
@@ -76,6 +79,20 @@
     return true;
 }
 
+BackendTexture Gpu::onCreateBackendTexture(SkISize dimensions, const skgpu::TextureInfo& info) {
+    sk_cfp<id<MTLTexture>> texture = Texture::MakeMtlTexture(this, dimensions, info);
+    if (!texture) {
+        return {};
+    }
+    return BackendTexture(dimensions, (Handle)texture.release());
+}
+
+void Gpu::onDeleteBackendTexture(BackendTexture& texture) {
+    SkASSERT(texture.backend() == BackendApi::kMetal);
+    Handle texHandle = texture.getMtlTexture();
+    SkCFSafeRelease(texHandle);
+}
+
 #if GRAPHITE_TEST_UTILS
 void Gpu::testingOnly_startCapture() {
     if (@available(macOS 10.13, iOS 11.0, *)) {
diff --git a/experimental/graphite/src/mtl/MtlResourceProvider.mm b/experimental/graphite/src/mtl/MtlResourceProvider.mm
index cd177a2..1501bd5 100644
--- a/experimental/graphite/src/mtl/MtlResourceProvider.mm
+++ b/experimental/graphite/src/mtl/MtlResourceProvider.mm
@@ -42,11 +42,11 @@
 }
 
 sk_sp<skgpu::Texture> ResourceProvider::createWrappedTexture(const BackendTexture& texture) {
-    sk_cfp<mtl::Handle> mtlHandleTexture = texture.getMtlTexture();
+    mtl::Handle mtlHandleTexture = texture.getMtlTexture();
     if (!mtlHandleTexture) {
         return nullptr;
     }
-    sk_cfp<id<MTLTexture>> mtlTexture((id<MTLTexture>)mtlHandleTexture.release());
+    sk_cfp<id<MTLTexture>> mtlTexture = sk_ret_cfp((id<MTLTexture>)mtlHandleTexture);
     return Texture::MakeWrapped(texture.dimensions(), texture.info(), std::move(mtlTexture));
 }
 
diff --git a/experimental/graphite/src/mtl/MtlTexture.h b/experimental/graphite/src/mtl/MtlTexture.h
index a0a7ceb..6488039 100644
--- a/experimental/graphite/src/mtl/MtlTexture.h
+++ b/experimental/graphite/src/mtl/MtlTexture.h
@@ -18,6 +18,10 @@
 
 class Texture : public skgpu::Texture {
 public:
+    static sk_cfp<id<MTLTexture>> MakeMtlTexture(const Gpu*,
+                                                 SkISize dimensions,
+                                                 const skgpu::TextureInfo&);
+
     static sk_sp<Texture> Make(const Gpu* gpu,
                                SkISize dimensions,
                                const skgpu::TextureInfo&);
diff --git a/experimental/graphite/src/mtl/MtlTexture.mm b/experimental/graphite/src/mtl/MtlTexture.mm
index 5789909..457f9e1 100644
--- a/experimental/graphite/src/mtl/MtlTexture.mm
+++ b/experimental/graphite/src/mtl/MtlTexture.mm
@@ -9,29 +9,32 @@
 
 #include "experimental/graphite/include/mtl/MtlTypes.h"
 #include "experimental/graphite/include/private/MtlTypesPriv.h"
+#include "experimental/graphite/src/mtl/MtlCaps.h"
 #include "experimental/graphite/src/mtl/MtlGpu.h"
 #include "experimental/graphite/src/mtl/MtlUtils.h"
 
 namespace skgpu::mtl {
 
-Texture::Texture(SkISize dimensions,
-                 const skgpu::TextureInfo& info,
-                 sk_cfp<id<MTLTexture>> texture,
-                 Ownership ownership)
-        : skgpu::Texture(dimensions, info, ownership)
-        , fTexture(std::move(texture)) {}
-
-sk_sp<Texture> Texture::Make(const Gpu* gpu,
-                             SkISize dimensions,
-                             const skgpu::TextureInfo& info) {
-    // TODO: get this from Caps
-    if (dimensions.width() > 16384 || dimensions.height() > 16384) {
+sk_cfp<id<MTLTexture>> Texture::MakeMtlTexture(const Gpu* gpu,
+                                               SkISize dimensions,
+                                               const skgpu::TextureInfo& info) {
+    const skgpu::Caps* caps = gpu->caps();
+    if (dimensions.width() > caps->maxTextureSize() ||
+        dimensions.height() > caps->maxTextureSize()) {
         return nullptr;
     }
 
     const TextureSpec& mtlSpec = info.mtlTextureSpec();
     SkASSERT(!mtlSpec.fFramebufferOnly);
 
+    if (mtlSpec.fUsage & MTLTextureUsageShaderRead && !caps->isTexturable(info)) {
+        return nullptr;
+    }
+
+    if (mtlSpec.fUsage & MTLTextureUsageRenderTarget && !caps->isRenderable(info)) {
+        return nullptr;
+    }
+
     sk_cfp<MTLTextureDescriptor*> desc([[MTLTextureDescriptor alloc] init]);
     (*desc).textureType = (info.numSamples() > 1) ? MTLTextureType2DMultisample : MTLTextureType2D;
     (*desc).pixelFormat = (MTLPixelFormat)mtlSpec.fFormat;
@@ -70,6 +73,23 @@
     }
 #endif
 
+    return texture;
+}
+
+Texture::Texture(SkISize dimensions,
+                 const skgpu::TextureInfo& info,
+                 sk_cfp<id<MTLTexture>> texture,
+                 Ownership ownership)
+        : skgpu::Texture(dimensions, info, ownership)
+        , fTexture(std::move(texture)) {}
+
+sk_sp<Texture> Texture::Make(const Gpu* gpu,
+                             SkISize dimensions,
+                             const skgpu::TextureInfo& info) {
+    sk_cfp<id<MTLTexture>> texture = MakeMtlTexture(gpu, dimensions, info);
+    if (!texture) {
+        return nullptr;
+    }
     return sk_sp<Texture>(new Texture(dimensions, info, std::move(texture), Ownership::kOwned));
 }
 
diff --git a/gn/tests.gni b/gn/tests.gni
index c51d9da..1e7d0ff 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -330,6 +330,7 @@
 ]
 
 graphite_tests_sources = [
+  "$_tests/graphite/BackendTextureTest.cpp",
   "$_tests/graphite/CommandBufferTest.cpp",
   "$_tests/graphite/IntersectionTreeTest.cpp",
   "$_tests/graphite/MaskTest.cpp",
@@ -340,6 +341,8 @@
   "$_tests/graphite/UniformTest.cpp",
 ]
 
+graphite_metal_tests_sources = [ "$_tests/graphite/MtlBackendTextureTest.mm" ]
+
 pathops_tests_sources = [
   "$_tests/PathOpsAngleIdeas.cpp",
   "$_tests/PathOpsAngleTest.cpp",
diff --git a/tests/graphite/BackendTextureTest.cpp b/tests/graphite/BackendTextureTest.cpp
new file mode 100644
index 0000000..6aea980
--- /dev/null
+++ b/tests/graphite/BackendTextureTest.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "tests/Test.h"
+
+#include "experimental/graphite/include/BackendTexture.h"
+#include "experimental/graphite/include/Context.h"
+#include "experimental/graphite/src/Caps.h"
+#include "experimental/graphite/src/ContextPriv.h"
+#include "experimental/graphite/src/Gpu.h"
+#include "experimental/graphite/src/ResourceTypes.h"
+
+using namespace skgpu;
+
+namespace {
+    const SkISize kSize = {16, 16};
+}
+
+DEF_GRAPHITE_TEST_FOR_CONTEXTS(BackendTextureTest, reporter, context) {
+    auto caps = context->priv().gpu()->caps();
+
+    TextureInfo info = caps->getDefaultSampledTextureInfo(kRGBA_8888_SkColorType,
+                                                          /*levelCount=*/1,
+                                                          Protected::kNo,
+                                                          Renderable::kNo);
+    REPORTER_ASSERT(reporter, info.isValid());
+
+    auto texture1 = context->createBackendTexture(kSize, info);
+    REPORTER_ASSERT(reporter, texture1.isValid());
+
+    // We make a copy to do the remaining tests so we still have texture1 to safely delete the
+    // backend object.
+    auto texture1Copy = texture1;
+    REPORTER_ASSERT(reporter, texture1Copy.isValid());
+    REPORTER_ASSERT(reporter, texture1 == texture1Copy);
+
+    auto texture2 = context->createBackendTexture(kSize, info);
+    REPORTER_ASSERT(reporter, texture2.isValid());
+
+    REPORTER_ASSERT(reporter, texture1Copy != texture2);
+
+    // Test state after assignment
+    texture1Copy = texture2;
+    REPORTER_ASSERT(reporter, texture1Copy.isValid());
+    REPORTER_ASSERT(reporter, texture1Copy == texture2);
+
+    BackendTexture invalidTexture;
+    REPORTER_ASSERT(reporter, !invalidTexture.isValid());
+
+    texture1Copy = invalidTexture;
+    REPORTER_ASSERT(reporter, !texture1Copy.isValid());
+
+    texture1Copy = texture1;
+    REPORTER_ASSERT(reporter, texture1Copy.isValid());
+    REPORTER_ASSERT(reporter, texture1 == texture1Copy);
+
+    context->deleteBackendTexture(texture1);
+    context->deleteBackendTexture(texture2);
+}
diff --git a/tests/graphite/MtlBackendTextureTest.mm b/tests/graphite/MtlBackendTextureTest.mm
new file mode 100644
index 0000000..aa8c6ba
--- /dev/null
+++ b/tests/graphite/MtlBackendTextureTest.mm
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "tests/Test.h"
+
+#include "experimental/graphite/include/BackendTexture.h"
+#include "experimental/graphite/include/Context.h"
+#include "experimental/graphite/include/mtl/MtlTypes.h"
+
+#import <Metal/Metal.h>
+
+using namespace skgpu;
+
+namespace {
+    const SkISize kSize = {16, 16};
+}
+
+DEF_GRAPHITE_TEST_FOR_CONTEXTS(MtlBackendTextureTest, reporter, context) {
+    mtl::TextureInfo textureInfo;
+    textureInfo.fSampleCount = 1;
+    textureInfo.fLevelCount = 1;
+    textureInfo.fFormat = MTLPixelFormatRGBA8Unorm;
+    textureInfo.fStorageMode = MTLStorageModePrivate;
+    textureInfo.fUsage = MTLTextureUsageShaderRead;
+
+    // TODO: For now we are just testing the basic case of RGBA single sample because that is what
+    // we've added to the backend. However, once we expand the backend support to handle all the
+    // formats this test should iterate over a large set of combinations. See the Ganesh
+    // MtlBackendAllocationTest for example of doing this.
+
+    auto beTexture = context->createBackendTexture(kSize, textureInfo);
+    REPORTER_ASSERT(reporter, beTexture.isValid());
+    context->deleteBackendTexture(beTexture);
+
+    // It should also pass if we set the usage to be a render target
+    textureInfo.fUsage |= MTLTextureUsageRenderTarget;
+    beTexture = context->createBackendTexture(kSize, textureInfo);
+    REPORTER_ASSERT(reporter, beTexture.isValid());
+    context->deleteBackendTexture(beTexture);
+
+    // It should fail with a format that isn't rgba8
+    textureInfo.fFormat = MTLPixelFormatR8Unorm;
+    beTexture = context->createBackendTexture(kSize, textureInfo);
+    REPORTER_ASSERT(reporter, !beTexture.isValid());
+    context->deleteBackendTexture(beTexture);
+
+    // It should fail with a sample count greater than 1
+    textureInfo.fFormat = MTLPixelFormatRGBA8Unorm;
+    textureInfo.fSampleCount = 4;
+    beTexture = context->createBackendTexture(kSize, textureInfo);
+    REPORTER_ASSERT(reporter, !beTexture.isValid());
+    context->deleteBackendTexture(beTexture);
+}