Switch to using GrMtlBackendContext for GrDirectContext creation.

Makes the Metal backend more consistent with the other backends,
and allows new init parameters to be added without significantly
changing API.

Added updated sk_cf_obj because I needed some of its functionality.

Bug: skia:10804
Change-Id: I6f1dd1c03ddc4c4b702ea75eff14bc0f98ab5ad2
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/334426
Commit-Queue: Jim Van Verth <jvanverth@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt
index 848f86c..639605d 100644
--- a/RELEASE_NOTES.txt
+++ b/RELEASE_NOTES.txt
@@ -4,6 +4,16 @@
 
 * * *
 
+Milestone 89
+------------
+
+  * Added a new interface for GrDirectContext creation in Metal, using
+    a new struct called GrMtlBackendContext. The previous interface taking
+    a MTLDevice and MTLCommandQueue is deprecated.
+    https://review.skia.org/334426
+
+* * *
+
 Milestone 88
 ------------
 
diff --git a/gn/gpu.gni b/gn/gpu.gni
index b7f91a2..99d7af1 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -845,6 +845,7 @@
 ]
 
 skia_metal_sources = [
+  "$_include/gpu/mtl/GrMtlBackendContext.h",
   "$_include/gpu/mtl/GrMtlTypes.h",
   "$_src/gpu/mtl/GrMtlAttachment.h",
   "$_src/gpu/mtl/GrMtlAttachment.mm",
diff --git a/include/gpu/GrDirectContext.h b/include/gpu/GrDirectContext.h
index e05fb78..57f35f7 100644
--- a/include/gpu/GrDirectContext.h
+++ b/include/gpu/GrDirectContext.h
@@ -24,6 +24,7 @@
 class GrFragmentProcessor;
 class GrGpu;
 struct GrGLInterface;
+struct GrMtlBackendContext;
 struct GrMockOptions;
 class GrPath;
 class GrResourceCache;
@@ -70,9 +71,20 @@
 
 #ifdef SK_METAL
     /**
+     * Makes a GrDirectContext which uses Metal as the backend. The GrMtlBackendContext contains a
+     * MTLDevice and MTLCommandQueue which should be used by the backend. These objects must
+     * have their own ref which will be released when the GrMtlBackendContext is destroyed.
+     * Ganesh will take its own ref on the objects which will be released when the GrDirectContext
+     * is destroyed.
+     */
+    static sk_sp<GrDirectContext> MakeMetal(const GrMtlBackendContext&, const GrContextOptions&);
+    static sk_sp<GrDirectContext> MakeMetal(const GrMtlBackendContext&);
+    /**
+     * Deprecated.
+     *
      * Makes a GrDirectContext which uses Metal as the backend. The device parameter is an
      * MTLDevice and queue is an MTLCommandQueue which should be used by the backend. These objects
-     * must have a ref on them which can be transferred to Ganesh which will release the ref
+     * must have a ref on them that can be transferred to Ganesh, which will release the ref
      * when the GrDirectContext is destroyed.
      */
     static sk_sp<GrDirectContext> MakeMetal(void* device, void* queue, const GrContextOptions&);
diff --git a/include/gpu/mtl/GrMtlBackendContext.h b/include/gpu/mtl/GrMtlBackendContext.h
new file mode 100644
index 0000000..9991c1a
--- /dev/null
+++ b/include/gpu/mtl/GrMtlBackendContext.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrMtlBackendContext_DEFINED
+#define GrMtlBackendContext_DEFINED
+
+#include "include/gpu/mtl/GrMtlTypes.h"
+
+// The BackendContext contains all of the base Metal objects needed by the GrMtlGpu. The assumption
+// is that the client will set these up and pass them to the GrMtlGpu constructor.
+struct SK_API GrMtlBackendContext {
+    sk_cf_obj<GrMTLHandle> fDevice;
+    sk_cf_obj<GrMTLHandle> fQueue;
+};
+
+#endif
diff --git a/include/ports/SkCFObject.h b/include/ports/SkCFObject.h
index c151703..ac156da 100644
--- a/include/ports/SkCFObject.h
+++ b/include/ports/SkCFObject.h
@@ -34,7 +34,8 @@
 public:
     using element_type = T;
 
-    constexpr sk_cf_obj() : fObject(nullptr) {}
+    constexpr sk_cf_obj() {}
+    constexpr sk_cf_obj(std::nullptr_t) {}
 
     /**
      *  Shares the underlying object by calling CFRetain(), so that both the argument and the newly
@@ -62,9 +63,11 @@
      */
     ~sk_cf_obj() {
         SkCFSafeRelease(fObject);
-        SkDEBUGCODE(fObject = nullptr);
+        SkDEBUGCODE(fObject = nil);
     }
 
+    sk_cf_obj<T>& operator=(std::nullptr_t) { this->reset(); return *this; }
+
     /**
      *  Shares the underlying object referenced by the argument by calling CFRetain() on it. If this
      *  sk_cf_obj previously had a reference to an object (i.e. not null) it will call CFRelease()
@@ -87,13 +90,22 @@
         return *this;
     }
 
+    explicit operator bool() const { return this->get() != nil; }
+
     T get() const { return fObject; }
+    T operator*() const {
+        SkASSERT(fObject);
+        return fObject;
+    }
 
     /**
      *  Adopt the new object, and call CFRelease() on any previously held object (if not null).
      *  No call to CFRetain() will be made.
      */
-    void reset(T object = nullptr) {
+    void reset(T object = nil) {
+        // Need to unref after assigning, see
+        // http://wg21.cmeerw.net/lwg/issue998
+        // http://wg21.cmeerw.net/lwg/issue2262
         T oldObject = fObject;
         fObject = object;
         SkCFSafeRelease(oldObject);
@@ -104,7 +116,7 @@
      *  reference to an object (i.e. not null) it will call CFRelease() on that object.
      */
     void retain(T object) {
-        if (this->fObject != object) {
+        if (fObject != object) {
             this->reset(SkCFSafeRetain(object));
         }
     }
@@ -116,23 +128,49 @@
      */
     T SK_WARN_UNUSED_RESULT release() {
         T obj = fObject;
-        fObject = nullptr;
+        fObject = nil;
         return obj;
     }
 
 private:
-    T fObject;
+    T fObject = nil;
 };
 
 template <typename T> inline bool operator==(const sk_cf_obj<T>& a,
                                              const sk_cf_obj<T>& b) {
     return a.get() == b.get();
 }
+template <typename T> inline bool operator==(const sk_cf_obj<T>& a,
+                                             std::nullptr_t) {
+    return !a;
+}
+template <typename T> inline bool operator==(std::nullptr_t,
+                                             const sk_cf_obj<T>& b) {
+    return !b;
+}
 
 template <typename T> inline bool operator!=(const sk_cf_obj<T>& a,
                                              const sk_cf_obj<T>& b) {
     return a.get() != b.get();
 }
+template <typename T> inline bool operator!=(const sk_cf_obj<T>& a,
+                                             std::nullptr_t) {
+    return static_cast<bool>(a);
+}
+template <typename T> inline bool operator!=(std::nullptr_t,
+                                             const sk_cf_obj<T>& b) {
+    return static_cast<bool>(b);
+}
+
+/*
+ *  Returns a sk_cf_obj wrapping the provided object AND calls retain on it (if not null).
+ *
+ *  This is different than the semantics of the constructor for sk_cf_obj, which just wraps the
+ *  object, effectively "adopting" it.
+ */
+template <typename T> sk_cf_obj<T> sk_ret_cf_obj(T obj) {
+    return sk_cf_obj<T>(SkCFSafeRetain(obj));
+}
 
 #endif  // SK_BUILD_FOR_MAC || SK_BUILD_FOR_IOS
-#endif  // SkCFOBject_DEFINED
+#endif  // SkCFObject_DEFINED
diff --git a/src/gpu/GrDirectContext.cpp b/src/gpu/GrDirectContext.cpp
index 8982015..6ababe8 100644
--- a/src/gpu/GrDirectContext.cpp
+++ b/src/gpu/GrDirectContext.cpp
@@ -28,6 +28,7 @@
 #include "src/gpu/text/GrAtlasManager.h"
 #include "src/gpu/text/GrStrikeCache.h"
 #ifdef SK_METAL
+#include "include/gpu/mtl/GrMtlBackendContext.h"
 #include "src/gpu/mtl/GrMtlTrampoline.h"
 #endif
 #ifdef SK_VULKAN
@@ -958,22 +959,40 @@
 
 #ifdef SK_METAL
 /*************************************************************************************************/
-sk_sp<GrDirectContext> GrDirectContext::MakeMetal(void* device, void* queue) {
+sk_sp<GrDirectContext> GrDirectContext::MakeMetal(const GrMtlBackendContext& backendContext) {
     GrContextOptions defaultOptions;
-    return MakeMetal(device, queue, defaultOptions);
+    return MakeMetal(backendContext, defaultOptions);
 }
 
-sk_sp<GrDirectContext> GrDirectContext::MakeMetal(void* device, void* queue,
-                                                  const GrContextOptions& options) {
+sk_sp<GrDirectContext> GrDirectContext::MakeMetal(const GrMtlBackendContext& backendContext,
+                                                     const GrContextOptions& options) {
     sk_sp<GrDirectContext> direct(new GrDirectContext(GrBackendApi::kMetal, options));
 
-    direct->fGpu = GrMtlTrampoline::MakeGpu(direct.get(), options, device, queue);
+    direct->fGpu = GrMtlTrampoline::MakeGpu(backendContext, options, direct.get());
     if (!direct->init()) {
         return nullptr;
     }
 
     return direct;
 }
+
+// deprecated
+sk_sp<GrDirectContext> GrDirectContext::MakeMetal(void* device, void* queue) {
+    GrContextOptions defaultOptions;
+    return MakeMetal(device, queue, defaultOptions);
+}
+
+// deprecated
+// remove include/gpu/mtl/GrMtlBackendContext.h, above, when removed
+sk_sp<GrDirectContext> GrDirectContext::MakeMetal(void* device, void* queue,
+                                                  const GrContextOptions& options) {
+    sk_sp<GrDirectContext> direct(new GrDirectContext(GrBackendApi::kMetal, options));
+    GrMtlBackendContext backendContext = {};
+    backendContext.fDevice.reset(device);
+    backendContext.fQueue.reset(queue);
+
+    return GrDirectContext::MakeMetal(backendContext, options);
+}
 #endif
 
 #ifdef SK_DIRECT3D
diff --git a/src/gpu/mtl/GrMtlGpu.h b/src/gpu/mtl/GrMtlGpu.h
index a52baee..1a7f609 100644
--- a/src/gpu/mtl/GrMtlGpu.h
+++ b/src/gpu/mtl/GrMtlGpu.h
@@ -8,7 +8,9 @@
 #ifndef GrMtlGpu_DEFINED
 #define GrMtlGpu_DEFINED
 
+#include "include/gpu/mtl/GrMtlBackendContext.h"
 #include "include/private/SkDeque.h"
+
 #include "src/gpu/GrFinishCallbacks.h"
 #include "src/gpu/GrGpu.h"
 #include "src/gpu/GrRenderTarget.h"
@@ -27,7 +29,6 @@
 class GrMtlOpsRenderPass;
 class GrMtlTexture;
 class GrSemaphore;
-struct GrMtlBackendContext;
 class GrMtlCommandBuffer;
 
 namespace SkSL {
@@ -36,8 +37,7 @@
 
 class GrMtlGpu : public GrGpu {
 public:
-    static sk_sp<GrGpu> Make(GrDirectContext*, const GrContextOptions&,
-                             id<MTLDevice>, id<MTLCommandQueue>);
+    static sk_sp<GrGpu> Make(const GrMtlBackendContext&, const GrContextOptions&, GrDirectContext*);
     ~GrMtlGpu() override;
 
     void disconnect(DisconnectType) override;
diff --git a/src/gpu/mtl/GrMtlGpu.mm b/src/gpu/mtl/GrMtlGpu.mm
index 3bed568..0b0b441 100644
--- a/src/gpu/mtl/GrMtlGpu.mm
+++ b/src/gpu/mtl/GrMtlGpu.mm
@@ -100,9 +100,9 @@
     return false;
 }
 
-sk_sp<GrGpu> GrMtlGpu::Make(GrDirectContext* direct, const GrContextOptions& options,
-                            id<MTLDevice> device, id<MTLCommandQueue> queue) {
-    if (!device || !queue) {
+sk_sp<GrGpu> GrMtlGpu::Make(const GrMtlBackendContext& context, const GrContextOptions& options,
+                            GrDirectContext* direct) {
+    if (!context.fDevice || !context.fQueue) {
         return nullptr;
     }
     if (@available(macOS 10.14, iOS 9.0, *)) {
@@ -117,6 +117,8 @@
 #endif
     }
 
+    id<MTLDevice> device = (__bridge id<MTLDevice>)(context.fDevice.get());
+    id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)(context.fQueue.get());
     MTLFeatureSet featureSet;
     if (!get_feature_set(device, &featureSet)) {
         return nullptr;
diff --git a/src/gpu/mtl/GrMtlTrampoline.h b/src/gpu/mtl/GrMtlTrampoline.h
index 7ddb105..e6dffa6 100644
--- a/src/gpu/mtl/GrMtlTrampoline.h
+++ b/src/gpu/mtl/GrMtlTrampoline.h
@@ -14,6 +14,7 @@
 class GrDirectContext;
 class GrGpu;
 struct GrContextOptions;
+struct GrMtlBackendContext;
 
 /*
  * This class is used to hold functions which trampoline from the Ganesh cpp code to the GrMtl
@@ -21,8 +22,8 @@
  */
 class GrMtlTrampoline {
 public:
-    static sk_sp<GrGpu> MakeGpu(GrDirectContext*, const GrContextOptions&,
-                                void* device, void* queue);
+    static sk_sp<GrGpu> MakeGpu(const GrMtlBackendContext&, const GrContextOptions&,
+                                GrDirectContext*);
 };
 
 #endif
diff --git a/src/gpu/mtl/GrMtlTrampoline.mm b/src/gpu/mtl/GrMtlTrampoline.mm
index 46f64ae..590c7a3 100644
--- a/src/gpu/mtl/GrMtlTrampoline.mm
+++ b/src/gpu/mtl/GrMtlTrampoline.mm
@@ -13,13 +13,8 @@
 #error This file must be compiled with Arc. Use -fobjc-arc flag
 #endif
 
-sk_sp<GrGpu> GrMtlTrampoline::MakeGpu(GrDirectContext* direct,
+sk_sp<GrGpu> GrMtlTrampoline::MakeGpu(const GrMtlBackendContext& backendContext,
                                       const GrContextOptions& options,
-                                      void* device,
-                                      void* queue) {
-    return GrMtlGpu::Make(direct,
-                          options,
-                          (__bridge id<MTLDevice>)device,
-                          (__bridge id<MTLCommandQueue>)queue);
+                                      GrDirectContext* direct) {
+    return GrMtlGpu::Make(backendContext, options, direct);
 }
-
diff --git a/tools/gpu/mtl/MtlTestContext.h b/tools/gpu/mtl/MtlTestContext.h
index 20310ec..5c74378 100644
--- a/tools/gpu/mtl/MtlTestContext.h
+++ b/tools/gpu/mtl/MtlTestContext.h
@@ -12,13 +12,22 @@
 
 #ifdef SK_METAL
 
+#include "include/gpu/mtl/GrMtlBackendContext.h"
+
 namespace sk_gpu_test {
 class MtlTestContext : public TestContext {
 public:
     GrBackendApi backend() override { return GrBackendApi::kMetal; }
 
+    const GrMtlBackendContext& getMtlBackendContext() const {
+        return fMtl;
+    }
+
 protected:
-    MtlTestContext() {}
+    MtlTestContext(const GrMtlBackendContext& mtl)
+            : fMtl(mtl) {}
+
+    GrMtlBackendContext fMtl;
 
 private:
     using INHERITED = TestContext;
diff --git a/tools/gpu/mtl/MtlTestContext.mm b/tools/gpu/mtl/MtlTestContext.mm
index f436ab5..9580fd3 100644
--- a/tools/gpu/mtl/MtlTestContext.mm
+++ b/tools/gpu/mtl/MtlTestContext.mm
@@ -20,13 +20,12 @@
 class MtlTestContextImpl : public sk_gpu_test::MtlTestContext {
 public:
     static MtlTestContext* Create(MtlTestContext* sharedContext) {
-        id<MTLDevice> device;
-        id<MTLCommandQueue> queue;
+        GrMtlBackendContext backendContext = {};
         if (sharedContext) {
             MtlTestContextImpl* sharedContextImpl = (MtlTestContextImpl*) sharedContext;
-            device = sharedContextImpl->device();
-            queue = sharedContextImpl->queue();
+            backendContext = sharedContextImpl->getMtlBackendContext();
         } else {
+            id<MTLDevice> device;
 #ifdef SK_BUILD_FOR_MAC
             NSArray<id <MTLDevice>>* availableDevices = MTLCopyAllDevices();
             // Choose the non-integrated CPU if available
@@ -46,10 +45,12 @@
 #else
             device = MTLCreateSystemDefaultDevice();
 #endif
-            queue = [device newCommandQueue];
+            backendContext.fDevice.retain((__bridge GrMTLHandle)device);
+            id<MTLCommandQueue> queue = [device newCommandQueue];
+            backendContext.fQueue.retain((__bridge GrMTLHandle)queue);
         }
 
-        return new MtlTestContextImpl(device, queue);
+        return new MtlTestContextImpl(backendContext);
     }
 
     ~MtlTestContextImpl() override { this->teardown(); }
@@ -59,17 +60,12 @@
     void finish() override {}
 
     sk_sp<GrDirectContext> makeContext(const GrContextOptions& options) override {
-        return GrDirectContext::MakeMetal((__bridge void*)fDevice,
-                                          (__bridge void*)fQueue,
-                                          options);
+        return GrDirectContext::MakeMetal(fMtl, options);
     }
 
-    id<MTLDevice> device() { return fDevice; }
-    id<MTLCommandQueue> queue() { return fQueue; }
-
 private:
-    MtlTestContextImpl(id<MTLDevice> device, id<MTLCommandQueue> queue)
-            : INHERITED(), fDevice(device), fQueue(queue) {
+    MtlTestContextImpl(const GrMtlBackendContext& mtl)
+            : INHERITED(mtl) {
         fFenceSupport = true;
     }
 
@@ -77,9 +73,6 @@
     void onPlatformMakeCurrent() const override {}
     std::function<void()> onPlatformGetAutoContextRestore() const override { return nullptr; }
 
-    id<MTLDevice>        fDevice;
-    id<MTLCommandQueue>  fQueue;
-
     using INHERITED = sk_gpu_test::MtlTestContext;
 };
 
diff --git a/tools/sk_app/MetalWindowContext.mm b/tools/sk_app/MetalWindowContext.mm
index 4893a42..de9d88b 100644
--- a/tools/sk_app/MetalWindowContext.mm
+++ b/tools/sk_app/MetalWindowContext.mm
@@ -9,6 +9,7 @@
 #include "include/core/SkSurface.h"
 #include "include/gpu/GrBackendSurface.h"
 #include "include/gpu/GrDirectContext.h"
+#include "include/gpu/mtl/GrMtlBackendContext.h"
 #include "include/gpu/mtl/GrMtlTypes.h"
 #include "src/core/SkMathPriv.h"
 #include "src/gpu/GrCaps.h"
@@ -48,8 +49,10 @@
 
     fValid = this->onInitializeContext();
 
-    fContext = GrDirectContext::MakeMetal((__bridge void*)fDevice, (__bridge void*)fQueue,
-                                          fDisplayParams.fGrContextOptions);
+    GrMtlBackendContext backendContext = {};
+    backendContext.fDevice.retain((__bridge GrMTLHandle)fDevice);
+    backendContext.fQueue.retain((__bridge GrMTLHandle)fQueue);
+    fContext = GrDirectContext::MakeMetal(backendContext, fDisplayParams.fGrContextOptions);
     if (!fContext && fDisplayParams.fMSAASampleCount > 1) {
         fDisplayParams.fMSAASampleCount /= 2;
         this->initializeContext();