Add Metal context to Viewer.

Bug: skia:8737
Change-Id: I5c4c839bcf39f2cd3a9a32d005bf4bdb7c42e6a5
Reviewed-on: https://skia-review.googlesource.com/c/187925
Reviewed-by: Brian Osman <brianosman@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Jim Van Verth <jvanverth@google.com>
diff --git a/tools/sk_app/MetalWindowContext.h b/tools/sk_app/MetalWindowContext.h
new file mode 100644
index 0000000..fc712d7
--- /dev/null
+++ b/tools/sk_app/MetalWindowContext.h
@@ -0,0 +1,58 @@
+
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef MetalWindowContext_DEFINED
+#define MetalWindowContext_DEFINED
+
+#include "SkRefCnt.h"
+#include "SkSurface.h"
+
+#include "WindowContext.h"
+
+#import <Metal/Metal.h>
+#import <MetalKit/MetalKit.h>
+
+class GrContext;
+
+namespace sk_app {
+
+class MetalWindowContext : public WindowContext {
+public:
+    sk_sp<SkSurface> getBackbufferSurface() override;
+
+    bool isValid() override { return fValid; }
+
+    void resize(int w, int h) override;
+    void swapBuffers() override;
+
+    void setDisplayParams(const DisplayParams& params) override;
+
+protected:
+    MetalWindowContext(const DisplayParams&);
+    // This should be called by subclass constructor. It is also called when window/display
+    // parameters change. This will in turn call onInitializeContext().
+    void initializeContext();
+    virtual bool onInitializeContext() = 0;
+
+    // This should be called by subclass destructor. It is also called when window/display
+    // parameters change prior to initializing a new GL context. This will in turn call
+    // onDestroyContext().
+    void destroyContext();
+    virtual void onDestroyContext() = 0;
+
+
+    bool                        fValid;
+    id<MTLDevice>               fDevice;
+    id<MTLCommandQueue>         fQueue;
+    dispatch_semaphore_t        fInFlightSemaphore;
+    MTKView*                    fMTKView;
+    sk_sp<SkSurface>            fSurface;
+};
+
+}   // namespace sk_app
+
+#endif
diff --git a/tools/sk_app/MetalWindowContext.mm b/tools/sk_app/MetalWindowContext.mm
new file mode 100644
index 0000000..1ce4e26
--- /dev/null
+++ b/tools/sk_app/MetalWindowContext.mm
@@ -0,0 +1,115 @@
+
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "MetalWindowContext.h"
+#include "GrBackendSurface.h"
+#include "GrCaps.h"
+#include "GrContext.h"
+#include "GrContextPriv.h"
+#include "SkCanvas.h"
+#include "SkImage_Base.h"
+#include "SkMathPriv.h"
+#include "SkSurface.h"
+#include "mtl/GrMtlTypes.h"
+
+namespace sk_app {
+
+static int kMaxBuffersInFlight = 3;
+
+MetalWindowContext::MetalWindowContext(const DisplayParams& params)
+    : WindowContext(params)
+    , fValid(false)
+    , fSurface(nullptr) {
+    fDisplayParams.fMSAASampleCount = GrNextPow2(fDisplayParams.fMSAASampleCount);
+}
+
+void MetalWindowContext::initializeContext() {
+    SkASSERT(!fContext);
+
+    // The subclass uses these to initialize their view
+    fDevice = MTLCreateSystemDefaultDevice();
+    fQueue = [fDevice newCommandQueue];
+
+    fInFlightSemaphore = dispatch_semaphore_create(kMaxBuffersInFlight);
+
+    fValid = this->onInitializeContext();
+    fContext = GrContext::MakeMetal(fDevice, fQueue, fDisplayParams.fGrContextOptions);
+    if (!fContext && fDisplayParams.fMSAASampleCount > 1) {
+        fDisplayParams.fMSAASampleCount /= 2;
+        this->initializeContext();
+        return;
+    }
+}
+
+void MetalWindowContext::destroyContext() {
+    fSurface.reset(nullptr);
+
+    if (fContext) {
+        // in case we have outstanding refs to this guy (lua?)
+        fContext->abandonContext();
+        fContext.reset();
+    }
+
+    // TODO: Figure out who's releasing this
+    // [fQueue release];
+    [fDevice release];
+
+    this->onDestroyContext();
+}
+
+sk_sp<SkSurface> MetalWindowContext::getBackbufferSurface() {
+    sk_sp<SkSurface> surface;
+    if (fContext) {
+        GrMtlTextureInfo fbInfo;
+        fbInfo.fTexture = [[fMTKView currentDrawable] texture];
+
+        GrBackendRenderTarget backendRT(fWidth,
+                                        fHeight,
+                                        fSampleCount,
+                                        fbInfo);
+
+        surface = SkSurface::MakeFromBackendRenderTarget(fContext.get(), backendRT,
+                                                          kTopLeft_GrSurfaceOrigin,
+                                                          kBGRA_8888_SkColorType,
+                                                          fDisplayParams.fColorSpace,
+                                                          &fDisplayParams.fSurfaceProps);
+    }
+
+    return surface;
+}
+
+void MetalWindowContext::swapBuffers() {
+    // Block to ensure we don't try to render to a frame that hasn't finished presenting
+    dispatch_semaphore_wait(fInFlightSemaphore, DISPATCH_TIME_FOREVER);
+
+    id<MTLCommandBuffer> commandBuffer = [fQueue commandBuffer];
+    commandBuffer.label = @"Present";
+
+    __block dispatch_semaphore_t block_sema = fInFlightSemaphore;
+    [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer)
+     {
+         dispatch_semaphore_signal(block_sema);
+     }];
+
+    id<MTLDrawable> drawable = [fMTKView currentDrawable];
+    [commandBuffer presentDrawable:drawable];
+    [commandBuffer commit];
+}
+
+void MetalWindowContext::resize(int w, int h) {
+    this->destroyContext();
+    this->initializeContext();
+}
+
+void MetalWindowContext::setDisplayParams(const DisplayParams& params) {
+    this->destroyContext();
+    fDisplayParams = params;
+    this->initializeContext();
+}
+
+}   //namespace sk_app
diff --git a/tools/sk_app/Window.h b/tools/sk_app/Window.h
index 85eea60..648c10b 100644
--- a/tools/sk_app/Window.h
+++ b/tools/sk_app/Window.h
@@ -49,6 +49,9 @@
 #ifdef SK_VULKAN
         kVulkan_BackendType,
 #endif
+#if SK_METAL && defined(SK_BUILD_FOR_MAC)
+        kMetal_BackendType,
+#endif
         kRaster_BackendType,
 
         kLast_BackendType = kRaster_BackendType
diff --git a/tools/sk_app/mac/MetalWindowContext_mac.mm b/tools/sk_app/mac/MetalWindowContext_mac.mm
new file mode 100644
index 0000000..3f508c6
--- /dev/null
+++ b/tools/sk_app/mac/MetalWindowContext_mac.mm
@@ -0,0 +1,119 @@
+
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "../MetalWindowContext.h"
+#include "WindowContextFactory_mac.h"
+
+#import <MetalKit/MetalKit.h>
+
+#include <Cocoa/Cocoa.h>
+
+using sk_app::DisplayParams;
+using sk_app::window_context_factory::MacWindowInfo;
+using sk_app::MetalWindowContext;
+
+namespace {
+
+class MetalWindowContext_mac : public MetalWindowContext {
+public:
+    MetalWindowContext_mac(const MacWindowInfo&, const DisplayParams&);
+
+    ~MetalWindowContext_mac() override;
+
+    bool onInitializeContext() override;
+    void onDestroyContext() override;
+
+private:
+    NSView*              fMainView;
+
+    typedef MetalWindowContext INHERITED;
+};
+
+MetalWindowContext_mac::MetalWindowContext_mac(const MacWindowInfo& info, const DisplayParams& params)
+    : INHERITED(params)
+    , fMainView(info.fMainView) {
+
+    // any config code here (particularly for msaa)?
+
+    this->initializeContext();
+}
+
+MetalWindowContext_mac::~MetalWindowContext_mac() {
+    this->destroyContext();
+}
+
+bool MetalWindowContext_mac::onInitializeContext() {
+    SkASSERT(nil != fMainView);
+
+    // create mtkview
+    NSRect rect = fMainView.bounds;
+    fMTKView = [[MTKView alloc] initWithFrame:rect device:fDevice];
+    if (nil == fMTKView) {
+        return false;
+    }
+
+    fMTKView.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
+
+    if (fDisplayParams.fMSAASampleCount > 1) {
+        if (![fDevice supportsTextureSampleCount:fDisplayParams.fMSAASampleCount]) {
+            return false;
+        }
+    }
+    fMTKView.sampleCount = fDisplayParams.fMSAASampleCount;
+
+    // attach Metal view to main view
+    [fMTKView setTranslatesAutoresizingMaskIntoConstraints:NO];
+
+    [fMainView addSubview:fMTKView];
+    NSDictionary *views = NSDictionaryOfVariableBindings(fMTKView);
+
+    [fMainView addConstraints:
+     [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[fMTKView]|"
+                                             options:0
+                                             metrics:nil
+                                               views:views]];
+
+    [fMainView addConstraints:
+     [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[fMTKView]|"
+                                             options:0
+                                             metrics:nil
+                                               views:views]];
+
+    fSampleCount = [fMTKView sampleCount];
+    fStencilBits = 8;
+
+    CGSize backingSize = [fMTKView drawableSize];
+    fWidth = backingSize.width;
+    fHeight = backingSize.height;
+
+    return true;
+}
+
+void MetalWindowContext_mac::onDestroyContext() {
+    [fMTKView removeFromSuperview];
+    [fMTKView release];
+    fMTKView = nil;
+}
+
+}  // anonymous namespace
+
+
+namespace sk_app {
+namespace window_context_factory {
+
+WindowContext* NewMetalForMac(const MacWindowInfo& info, const DisplayParams& params) {
+    WindowContext* ctx = new MetalWindowContext_mac(info, params);
+    if (!ctx->isValid()) {
+        delete ctx;
+        return nullptr;
+    }
+    return ctx;
+}
+
+}  // namespace window_context_factory
+}  // namespace sk_app
diff --git a/tools/sk_app/mac/WindowContextFactory_mac.h b/tools/sk_app/mac/WindowContextFactory_mac.h
index 840c711..196663c 100644
--- a/tools/sk_app/mac/WindowContextFactory_mac.h
+++ b/tools/sk_app/mac/WindowContextFactory_mac.h
@@ -30,6 +30,9 @@
 WindowContext* NewGLForMac(const MacWindowInfo&, const DisplayParams&);
 
 WindowContext* NewRasterForMac(const MacWindowInfo&, const DisplayParams&);
+#ifdef SK_METAL
+WindowContext* NewMetalForMac(const MacWindowInfo&, const DisplayParams&);
+#endif
 
 }  // namespace window_context_factory
 
diff --git a/tools/sk_app/mac/Window_mac.mm b/tools/sk_app/mac/Window_mac.mm
index 0693cec..6353f21 100644
--- a/tools/sk_app/mac/Window_mac.mm
+++ b/tools/sk_app/mac/Window_mac.mm
@@ -105,7 +105,11 @@
         case kRaster_BackendType:
             fWindowContext = NewRasterForMac(info, fRequestedDisplayParams);
             break;
-
+#ifdef SK_METAL
+        case kMetal_BackendType:
+            fWindowContext = NewMetalForMac(info, fRequestedDisplayParams);
+            break;
+#endif
         case kNativeGL_BackendType:
         default:
             fWindowContext = NewGLForMac(info, fRequestedDisplayParams);
diff --git a/tools/viewer/Viewer.cpp b/tools/viewer/Viewer.cpp
index 75730ca..945b07f 100644
--- a/tools/viewer/Viewer.cpp
+++ b/tools/viewer/Viewer.cpp
@@ -65,6 +65,8 @@
 
 #ifdef SK_VULKAN
 #    define BACKENDS_STR "\"sw\", \"gl\", and \"vk\""
+#elif defined(SK_METAL) && defined(SK_BUILD_FOR_MAC)
+#    define BACKENDS_STR "\"sw\", \"gl\", and \"mtl\""
 #else
 #    define BACKENDS_STR "\"sw\" and \"gl\""
 #endif
@@ -87,6 +89,9 @@
 #ifdef SK_VULKAN
     "Vulkan",
 #endif
+#if defined(SK_METAL) && defined(SK_BUILD_FOR_MAC)
+    "Metal",
+#endif
     "Raster"
 };
 
@@ -101,6 +106,11 @@
         return sk_app::Window::kANGLE_BackendType;
     } else
 #endif
+#if defined(SK_METAL) && defined(SK_BUILD_FOR_MAC)
+        if (0 == strcmp(str, "mtl")) {
+            return sk_app::Window::kMetal_BackendType;
+        } else
+#endif
     if (0 == strcmp(str, "gl")) {
         return sk_app::Window::kNativeGL_BackendType;
     } else if (0 == strcmp(str, "sw")) {
@@ -1511,6 +1521,10 @@
                 ImGui::SameLine();
                 ImGui::RadioButton("Vulkan", &newBackend, sk_app::Window::kVulkan_BackendType);
 #endif
+#if defined(SK_METAL) && defined(SK_BUILD_FOR_MAC)
+                ImGui::SameLine();
+                ImGui::RadioButton("Metal", &newBackend, sk_app::Window::kMetal_BackendType);
+#endif
                 if (newBackend != fBackendType) {
                     fDeferredActions.push_back([=]() {
                         this->setBackend(static_cast<sk_app::Window::BackendType>(newBackend));