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/BUILD.gn b/BUILD.gn
index a488e64..96fdf86 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -2162,6 +2162,14 @@
}
}
+ if (skia_use_metal) {
+ sources += [ "tools/sk_app/MetalWindowContext.mm" ]
+ if (is_mac) {
+ sources += [ "tools/sk_app/mac/MetalWindowContext_mac.mm" ]
+ }
+ libs += [ "MetalKit.framework" ]
+ }
+
deps = [
":gpu_tool_utils",
":skia",
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));