macOS: fix viewport lagging, by using CAMetalLayer instead of NSOpenGLView
authorTomoaki Kawada <i@yvt.jp>
Fri, 24 May 2019 16:38:20 +0000 (18:38 +0200)
committerBrecht Van Lommel <brechtvanlommel@gmail.com>
Sun, 2 Jun 2019 10:54:46 +0000 (12:54 +0200)
On GPUs that support it, we now present OpenGL contents via CAMetalLayer. This
fixes frame skipping issues found in T60043. If the system does not have a Metal
capable GPU, NSOpenGLView will continue to be used.

Patch by Tomoaki Kawada, with some changes by Brecht Van Lommel.

Differential Revision: https://developer.blender.org/D4619

build_files/cmake/platform/platform_apple.cmake
intern/ghost/intern/GHOST_ContextCGL.h
intern/ghost/intern/GHOST_ContextCGL.mm
intern/ghost/intern/GHOST_SystemCocoa.mm
intern/ghost/intern/GHOST_WindowCocoa.h
intern/ghost/intern/GHOST_WindowCocoa.mm
intern/ghost/intern/GHOST_WindowViewCocoa.h

index 3366d49..d7c4370 100644 (file)
@@ -181,7 +181,7 @@ endif()
 
 set(PLATFORM_CFLAGS "-pipe -funsigned-char")
 set(PLATFORM_LINKFLAGS
-  "-fexceptions -framework CoreServices -framework Foundation -framework IOKit -framework AppKit -framework Cocoa -framework Carbon -framework AudioUnit -framework AudioToolbox -framework CoreAudio"
+  "-fexceptions -framework CoreServices -framework Foundation -framework IOKit -framework AppKit -framework Cocoa -framework Carbon -framework AudioUnit -framework AudioToolbox -framework CoreAudio -framework Metal -framework QuartzCore"
 )
 
 list(APPEND PLATFORM_LINKLIBS c++)
index b808eff..37c1ac3 100644 (file)
 
 #include "GHOST_Context.h"
 
-@class NSOpenGLView;
+@class CAMetalLayer;
+@class MTLCommandQueue;
+@class MTLRenderPipelineState;
+@class MTLTexture;
 @class NSOpenGLContext;
+@class NSOpenGLView;
+@class NSView;
 
 class GHOST_ContextCGL : public GHOST_Context {
  public:
   /**
    * Constructor.
    */
-  GHOST_ContextCGL(bool stereoVisual, NSOpenGLView *openGLView);
+  GHOST_ContextCGL(bool stereoVisual,
+                   NSView *metalView,
+                   CAMetalLayer *metalLayer,
+                   NSOpenGLView *openglView);
 
   /**
    * Destructor.
@@ -59,6 +67,8 @@ class GHOST_ContextCGL : public GHOST_Context {
    */
   GHOST_TSuccess releaseDrawingContext();
 
+  unsigned int getDefaultFramebuffer();
+
   /**
    * Call immediately after new to initialize.  If this fails then immediately delete the object.
    * \return Indication as to whether initialization has succeeded.
@@ -94,12 +104,24 @@ class GHOST_ContextCGL : public GHOST_Context {
   GHOST_TSuccess updateDrawingContext();
 
  private:
-  /** The openGL view */
+  /** Metal state */
+  NSView *m_metalView;
+  CAMetalLayer *m_metalLayer;
+  MTLCommandQueue *m_metalCmdQueue;
+  MTLRenderPipelineState *m_metalRenderPipeline;
+
+  /** OpenGL state, for GPUs that don't support Metal */
   NSOpenGLView *m_openGLView;
 
   /** The OpenGL drawing context */
   NSOpenGLContext *m_openGLContext;
 
+  /** The virtualized default framebuffer */
+  unsigned int m_defaultFramebuffer;
+
+  /** The virtualized default framebuffer's texture */
+  MTLTexture *m_defaultFramebufferMetalTexture;
+
   bool m_coreProfile;
 
   const bool m_debug;
@@ -107,6 +129,13 @@ class GHOST_ContextCGL : public GHOST_Context {
   /** The first created OpenGL context (for sharing display lists) */
   static NSOpenGLContext *s_sharedOpenGLContext;
   static int s_sharedCount;
+
+  /* Metal functions */
+  void metalInit();
+  void metalFree();
+  void metalInitFramebuffer();
+  void metalUpdateFramebuffer();
+  void metalSwapBuffers();
 };
 
 #endif  // __GHOST_CONTEXTCGL_H__
index 5fe40cb..12c340f 100644 (file)
 #include "GHOST_ContextCGL.h"
 
 #include <Cocoa/Cocoa.h>
+#include <QuartzCore/QuartzCore.h>
+#include <Metal/Metal.h>
 
 #include <vector>
 #include <cassert>
 
+static void ghost_fatal_error_dialog(const char *msg)
+{
+  @autoreleasepool {
+    NSString *message = [NSString stringWithFormat:@"Error opening window:\n%s", msg];
+
+    NSAlert *alert = [[NSAlert alloc] init];
+    [alert addButtonWithTitle:@"Quit"];
+    [alert setMessageText:@"Blender"];
+    [alert setInformativeText:message];
+    [alert setAlertStyle:NSAlertStyleCritical];
+    [alert runModal];
+  }
+
+  exit(1);
+}
+
 NSOpenGLContext *GHOST_ContextCGL::s_sharedOpenGLContext = nil;
 int GHOST_ContextCGL::s_sharedCount = 0;
 
-GHOST_ContextCGL::GHOST_ContextCGL(bool stereoVisual, NSOpenGLView *openGLView)
-    : GHOST_Context(stereoVisual), m_openGLView(openGLView), m_openGLContext(nil), m_debug(false)
+GHOST_ContextCGL::GHOST_ContextCGL(bool stereoVisual,
+                                   NSView *metalView,
+                                   CAMetalLayer *metalLayer,
+                                   NSOpenGLView *openGLView)
+    : GHOST_Context(stereoVisual),
+      m_metalView(metalView),
+      m_metalLayer(metalLayer),
+      m_metalCmdQueue(nil),
+      m_metalRenderPipeline(nil),
+      m_openGLView(openGLView),
+      m_openGLContext(nil),
+      m_defaultFramebuffer(0),
+      m_defaultFramebufferMetalTexture(nil),
+      m_debug(false)
 {
 #if defined(WITH_GL_PROFILE_CORE)
   m_coreProfile = true;
 #else
   m_coreProfile = false;
 #endif
+
+  if (m_metalView) {
+    metalInit();
+  }
 }
 
 GHOST_ContextCGL::~GHOST_ContextCGL()
 {
+  metalFree();
+
   if (m_openGLContext != nil) {
     if (m_openGLContext == [NSOpenGLContext currentContext]) {
       [NSOpenGLContext clearCurrentContext];
@@ -70,9 +106,14 @@ GHOST_ContextCGL::~GHOST_ContextCGL()
 GHOST_TSuccess GHOST_ContextCGL::swapBuffers()
 {
   if (m_openGLContext != nil) {
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-    [m_openGLContext flushBuffer];
-    [pool drain];
+    if (m_metalView) {
+      metalSwapBuffers();
+    }
+    else if (m_openGLView) {
+      NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+      [m_openGLContext flushBuffer];
+      [pool drain];
+    }
     return GHOST_kSuccess;
   }
   else {
@@ -139,12 +180,23 @@ GHOST_TSuccess GHOST_ContextCGL::releaseDrawingContext()
   }
 }
 
+unsigned int GHOST_ContextCGL::getDefaultFramebuffer()
+{
+  return m_defaultFramebuffer;
+}
+
 GHOST_TSuccess GHOST_ContextCGL::updateDrawingContext()
 {
   if (m_openGLContext != nil) {
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-    [m_openGLContext update];
-    [pool drain];
+    if (m_metalView) {
+      metalUpdateFramebuffer();
+    }
+    else if (m_openGLView) {
+      NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+      [m_openGLContext update];
+      [pool drain];
+    }
+
     return GHOST_kSuccess;
   }
   else {
@@ -233,7 +285,15 @@ GHOST_TSuccess GHOST_ContextCGL::initializeDrawingContext()
 
   initContextGLEW();
 
-  if (m_openGLView) {
+  if (m_metalView) {
+    if (m_defaultFramebuffer == 0) {
+      // Create a virtual framebuffer
+      [m_openGLContext makeCurrentContext];
+      metalInitFramebuffer();
+      initClearGL();
+    }
+  }
+  else if (m_openGLView) {
     [m_openGLView setOpenGLContext:m_openGLContext];
     [m_openGLContext setView:m_openGLView];
     initClearGL();
@@ -261,8 +321,253 @@ error:
 
 GHOST_TSuccess GHOST_ContextCGL::releaseNativeHandles()
 {
-  m_openGLContext = NULL;
-  m_openGLView = NULL;
+  m_openGLContext = nil;
+  m_openGLView = nil;
+  m_metalView = nil;
 
   return GHOST_kSuccess;
 }
+
+/* OpenGL on Metal
+ *
+ * Use Metal layer to avoid Viewport lagging on macOS, see T60043. */
+
+static const MTLPixelFormat METAL_FRAMEBUFFERPIXEL_FORMAT = MTLPixelFormatBGRA8Unorm;
+static const OSType METAL_CORE_VIDEO_PIXEL_FORMAT = kCVPixelFormatType_32BGRA;
+
+void GHOST_ContextCGL::metalInit()
+{
+  @autoreleasepool {
+    id<MTLDevice> device = m_metalLayer.device;
+
+    // Create a command queue for blit/present operation
+    m_metalCmdQueue = (MTLCommandQueue *)[device newCommandQueue];
+    [m_metalCmdQueue retain];
+
+    // Create shaders for blit operation
+    NSString *source = @R"msl(
+      using namespace metal;
+
+      struct Vertex {
+        float4 position [[position]];
+        float2 texCoord [[attribute(0)]];
+      };
+
+      vertex Vertex vertex_shader(uint v_id [[vertex_id]]) {
+        Vertex vtx;
+
+        vtx.position.x = float(v_id & 1) * 4.0 - 1.0;
+        vtx.position.y = float(v_id >> 1) * 4.0 - 1.0;
+        vtx.position.z = 0.0;
+        vtx.position.w = 1.0;
+
+        vtx.texCoord = vtx.position.xy * 0.5 + 0.5;
+
+        return vtx;
+      }
+
+      constexpr sampler s {};
+
+      fragment float4 fragment_shader(Vertex v [[stage_in]],
+                      texture2d<float> t [[texture(0)]]) {
+        return t.sample(s, v.texCoord);
+      }
+
+      )msl";
+
+    MTLCompileOptions *options = [[[MTLCompileOptions alloc] init] autorelease];
+    options.languageVersion = MTLLanguageVersion1_1;
+
+    NSError *error = nil;
+    id<MTLLibrary> library = [device newLibraryWithSource:source options:options error:&error];
+    if (error) {
+      ghost_fatal_error_dialog(
+          "GHOST_ContextCGL::metalInit: newLibraryWithSource:options:error: failed!");
+    }
+
+    // Create a render pipeline for blit operation
+    MTLRenderPipelineDescriptor *desc = [[[MTLRenderPipelineDescriptor alloc] init] autorelease];
+
+    desc.fragmentFunction = [library newFunctionWithName:@"fragment_shader"];
+    desc.vertexFunction = [library newFunctionWithName:@"vertex_shader"];
+
+    [desc.colorAttachments objectAtIndexedSubscript:0].pixelFormat = METAL_FRAMEBUFFERPIXEL_FORMAT;
+
+    m_metalRenderPipeline = (MTLRenderPipelineState *)[device
+        newRenderPipelineStateWithDescriptor:desc
+                                       error:&error];
+    if (error) {
+      ghost_fatal_error_dialog(
+          "GHOST_ContextCGL::metalInit: newRenderPipelineStateWithDescriptor:error: failed!");
+    }
+  }
+}
+
+void GHOST_ContextCGL::metalFree()
+{
+  if (m_metalCmdQueue) {
+    [m_metalCmdQueue release];
+  }
+  if (m_metalRenderPipeline) {
+    [m_metalRenderPipeline release];
+  }
+  if (m_defaultFramebufferMetalTexture) {
+    [m_defaultFramebufferMetalTexture release];
+  }
+}
+
+void GHOST_ContextCGL::metalInitFramebuffer()
+{
+  glGenFramebuffers(1, &m_defaultFramebuffer);
+  updateDrawingContext();
+  glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFramebuffer);
+}
+
+void GHOST_ContextCGL::metalUpdateFramebuffer()
+{
+  assert(m_defaultFramebuffer != 0);
+
+  NSRect bounds = [m_metalView bounds];
+  NSSize backingSize = [m_metalView convertSizeToBacking:bounds.size];
+  size_t width = (size_t)backingSize.width;
+  size_t height = (size_t)backingSize.height;
+
+  {
+    /* Test if there is anything to update */
+    id<MTLTexture> tex = (id<MTLTexture>)m_defaultFramebufferMetalTexture;
+    if (tex && tex.width == width && tex.height == height) {
+      return;
+    }
+  }
+
+  activateDrawingContext();
+
+  NSDictionary *cvPixelBufferProps = @{
+    (__bridge NSString *)kCVPixelBufferOpenGLCompatibilityKey : @YES,
+    (__bridge NSString *)kCVPixelBufferMetalCompatibilityKey : @YES,
+  };
+  CVPixelBufferRef cvPixelBuffer = nil;
+  CVReturn cvret = CVPixelBufferCreate(kCFAllocatorDefault,
+                                       width,
+                                       height,
+                                       METAL_CORE_VIDEO_PIXEL_FORMAT,
+                                       (__bridge CFDictionaryRef)cvPixelBufferProps,
+                                       &cvPixelBuffer);
+  if (cvret != kCVReturnSuccess) {
+    ghost_fatal_error_dialog(
+        "GHOST_ContextCGL::metalUpdateFramebuffer: CVPixelBufferCreate failed!");
+  }
+
+  // Create an OpenGL texture
+  CVOpenGLTextureCacheRef cvGLTexCache = nil;
+  cvret = CVOpenGLTextureCacheCreate(kCFAllocatorDefault,
+                                     nil,
+                                     m_openGLContext.CGLContextObj,
+                                     m_openGLContext.pixelFormat.CGLPixelFormatObj,
+                                     nil,
+                                     &cvGLTexCache);
+  if (cvret != kCVReturnSuccess) {
+    ghost_fatal_error_dialog(
+        "GHOST_ContextCGL::metalUpdateFramebuffer: CVOpenGLTextureCacheCreate failed!");
+  }
+
+  CVOpenGLTextureRef cvGLTex = nil;
+  cvret = CVOpenGLTextureCacheCreateTextureFromImage(
+      kCFAllocatorDefault, cvGLTexCache, cvPixelBuffer, nil, &cvGLTex);
+  if (cvret != kCVReturnSuccess) {
+    ghost_fatal_error_dialog(
+        "GHOST_ContextCGL::metalUpdateFramebuffer: "
+        "CVOpenGLTextureCacheCreateTextureFromImage failed!");
+  }
+
+  unsigned int glTex;
+  glTex = CVOpenGLTextureGetName(cvGLTex);
+
+  // Create a Metal texture
+  CVMetalTextureCacheRef cvMetalTexCache = nil;
+  cvret = CVMetalTextureCacheCreate(
+      kCFAllocatorDefault, nil, m_metalLayer.device, nil, &cvMetalTexCache);
+  if (cvret != kCVReturnSuccess) {
+    ghost_fatal_error_dialog(
+        "GHOST_ContextCGL::metalUpdateFramebuffer: CVMetalTextureCacheCreate failed!");
+  }
+
+  CVMetalTextureRef cvMetalTex = nil;
+  cvret = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
+                                                    cvMetalTexCache,
+                                                    cvPixelBuffer,
+                                                    nil,
+                                                    METAL_FRAMEBUFFERPIXEL_FORMAT,
+                                                    width,
+                                                    height,
+                                                    0,
+                                                    &cvMetalTex);
+  if (cvret != kCVReturnSuccess) {
+    ghost_fatal_error_dialog(
+        "GHOST_ContextCGL::metalUpdateFramebuffer: "
+        "CVMetalTextureCacheCreateTextureFromImage failed!");
+  }
+
+  MTLTexture *tex = (MTLTexture *)CVMetalTextureGetTexture(cvMetalTex);
+
+  if (!tex) {
+    ghost_fatal_error_dialog(
+        "GHOST_ContextCGL::metalUpdateFramebuffer: CVMetalTextureGetTexture failed!");
+  }
+
+  [m_defaultFramebufferMetalTexture release];
+  m_defaultFramebufferMetalTexture = [tex retain];
+
+  glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFramebuffer);
+  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, glTex, 0);
+
+  [m_metalLayer setDrawableSize:CGSizeMake((CGFloat)width, (CGFloat)height)];
+
+  CVPixelBufferRelease(cvPixelBuffer);
+  CVOpenGLTextureCacheRelease(cvGLTexCache);
+  CVOpenGLTextureRelease(cvGLTex);
+  CFRelease(cvMetalTexCache);
+  CFRelease(cvMetalTex);
+}
+
+void GHOST_ContextCGL::metalSwapBuffers()
+{
+  @autoreleasepool {
+    updateDrawingContext();
+    glFlush();
+
+    assert(m_defaultFramebufferMetalTexture != 0);
+
+    id<CAMetalDrawable> drawable = [m_metalLayer nextDrawable];
+    if (!drawable) {
+      return;
+    }
+
+    id<MTLCommandBuffer> cmdBuffer = [m_metalCmdQueue commandBuffer];
+
+    MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
+    {
+      auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0];
+      attachment.texture = drawable.texture;
+      attachment.loadAction = MTLLoadActionDontCare;
+      attachment.storeAction = MTLStoreActionStore;
+    }
+
+    id<MTLTexture> srcTexture = (id<MTLTexture>)m_defaultFramebufferMetalTexture;
+
+    {
+      id<MTLRenderCommandEncoder> enc = [cmdBuffer
+          renderCommandEncoderWithDescriptor:passDescriptor];
+
+      [enc setRenderPipelineState:(id<MTLRenderPipelineState>)m_metalRenderPipeline];
+      [enc setFragmentTexture:srcTexture atIndex:0];
+      [enc drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
+
+      [enc endEncoding];
+    }
+
+    [cmdBuffer presentDrawable:drawable];
+
+    [cmdBuffer commit];
+  }
+}
index c80424b..376ebfa 100644 (file)
@@ -762,7 +762,7 @@ GHOST_IWindow *GHOST_SystemCocoa::createWindow(const STR_String &title,
  */
 GHOST_IContext *GHOST_SystemCocoa::createOffscreenContext()
 {
-  GHOST_Context *context = new GHOST_ContextCGL(false, NULL);
+  GHOST_Context *context = new GHOST_ContextCGL(false, NULL, NULL, NULL);
   if (context->initializeDrawingContext())
     return context;
   else
index 41e7191..5300b07 100644 (file)
 #include "GHOST_Window.h"
 #include "STR_String.h"
 
-@class CocoaWindow;
+@class CAMetalLayer;
+@class CocoaMetalView;
 @class CocoaOpenGLView;
+@class CocoaWindow;
 @class NSCursor;
 @class NSScreen;
 
@@ -313,11 +315,13 @@ class GHOST_WindowCocoa : public GHOST_Window {
                                             int hotX,
                                             int hotY);
 
-  /** The window containing the OpenGL view */
+  /** The window containing the view */
   CocoaWindow *m_window;
 
-  /** The openGL view */
+  /** The view, either Metal or OpenGL */
   CocoaOpenGLView *m_openGLView;
+  CocoaMetalView *m_metalView;
+  CAMetalLayer *m_metalLayer;
 
   /** The mother SystemCocoa class to send events */
   GHOST_SystemCocoa *m_systemCocoa;
index 33d9921..45ea3c8 100644 (file)
@@ -29,6 +29,8 @@
 #endif
 
 #include <Cocoa/Cocoa.h>
+#include <QuartzCore/QuartzCore.h>
+#include <Metal/Metal.h>
 
 #include <sys/sysctl.h>
 
 @end
 
 /* NSView for handling input and drawing. */
+#define COCOA_VIEW_CLASS CocoaOpenGLView
+#define COCOA_VIEW_BASE_CLASS NSOpenGLView
 #include "GHOST_WindowViewCocoa.h"
+#undef COCOA_VIEW_CLASS
+#undef COCOA_VIEW_BASE_CLASS
+
+#define COCOA_VIEW_CLASS CocoaMetalView
+#define COCOA_VIEW_BASE_CLASS NSView
+#include "GHOST_WindowViewCocoa.h"
+#undef COCOA_VIEW_CLASS
+#undef COCOA_VIEW_BASE_CLASS
 
 #pragma mark initialization / finalization
 
@@ -281,6 +293,8 @@ GHOST_WindowCocoa::GHOST_WindowCocoa(GHOST_SystemCocoa *systemCocoa,
                                      bool is_debug)
     : GHOST_Window(width, height, state, stereoVisual, false),
       m_openGLView(nil),
+      m_metalView(nil),
+      m_metalLayer(nil),
       m_systemCocoa(systemCocoa),
       m_customCursor(0),
       m_immediateDraw(false),
@@ -318,21 +332,44 @@ GHOST_WindowCocoa::GHOST_WindowCocoa(GHOST_SystemCocoa *systemCocoa,
   minSize.height = 240;
   [m_window setContentMinSize:minSize];
 
-  // Creates the OpenGL View inside the window
-  m_openGLView = [[CocoaOpenGLView alloc] initWithFrame:rect];
+  // Create NSView inside the window
+  id<MTLDevice> metalDevice = MTLCreateSystemDefaultDevice();
+  NSView *view;
+
+  if (metalDevice) {
+    // Create metal layer and view if supported
+    m_metalLayer = [[CAMetalLayer alloc] init];
+    [m_metalLayer setEdgeAntialiasingMask:0];
+    [m_metalLayer setMasksToBounds:NO];
+    [m_metalLayer setOpaque:YES];
+    [m_metalLayer setFramebufferOnly:YES];
+    [m_metalLayer setPresentsWithTransaction:NO];
+    [m_metalLayer removeAllAnimations];
+    [m_metalLayer setDevice:metalDevice];
+
+    m_metalView = [[CocoaMetalView alloc] initWithFrame:rect];
+    [m_metalView setWantsLayer:YES];
+    [m_metalView setLayer:m_metalLayer];
+    [m_metalView setSystemAndWindowCocoa:systemCocoa windowCocoa:this];
+    view = m_metalView;
+  }
+  else {
+    // Fallback to OpenGL view if there is no Metal support
+    m_openGLView = [[CocoaOpenGLView alloc] initWithFrame:rect];
+    [m_openGLView setSystemAndWindowCocoa:systemCocoa windowCocoa:this];
+    view = m_openGLView;
+  }
 
   if (m_systemCocoa->m_nativePixel) {
     // Needs to happen early when building with the 10.14 SDK, otherwise
     // has no effect until resizeing the window.
-    if ([m_openGLView respondsToSelector:@selector(setWantsBestResolutionOpenGLSurface:)]) {
-      [m_openGLView setWantsBestResolutionOpenGLSurface:YES];
+    if ([view respondsToSelector:@selector(setWantsBestResolutionOpenGLSurface:)]) {
+      [view setWantsBestResolutionOpenGLSurface:YES];
     }
   }
 
-  [m_openGLView setSystemAndWindowCocoa:systemCocoa windowCocoa:this];
-
-  [m_window setContentView:m_openGLView];
-  [m_window setInitialFirstResponder:m_openGLView];
+  [m_window setContentView:view];
+  [m_window setInitialFirstResponder:view];
 
   [m_window makeKeyAndOrderFront:nil];
 
@@ -381,7 +418,18 @@ GHOST_WindowCocoa::~GHOST_WindowCocoa()
 
   releaseNativeHandles();
 
-  [m_openGLView release];
+  if (m_openGLView) {
+    [m_openGLView release];
+    m_openGLView = nil;
+  }
+  if (m_metalView) {
+    [m_metalView release];
+    m_metalView = nil;
+  }
+  if (m_metalLayer) {
+    [m_metalLayer release];
+    m_metalLayer = nil;
+  }
 
   if (m_window) {
     [m_window close];
@@ -405,7 +453,8 @@ GHOST_WindowCocoa::~GHOST_WindowCocoa()
 
 bool GHOST_WindowCocoa::getValid() const
 {
-  return GHOST_Window::getValid() && m_window != NULL && m_openGLView != NULL;
+  NSView *view = (m_openGLView) ? m_openGLView : m_metalView;
+  return GHOST_Window::getValid() && m_window != NULL && view != NULL;
 }
 
 void *GHOST_WindowCocoa::getOSWindow() const
@@ -669,7 +718,8 @@ NSScreen *GHOST_WindowCocoa::getScreen()
 /* called for event, when window leaves monitor to another */
 void GHOST_WindowCocoa::setNativePixelSize(void)
 {
-  NSRect backingBounds = [m_openGLView convertRectToBacking:[m_openGLView bounds]];
+  NSView *view = (m_openGLView) ? m_openGLView : m_metalView;
+  NSRect backingBounds = [view convertRectToBacking:[view bounds]];
 
   GHOST_Rect rect;
   getClientBounds(rect);
@@ -763,7 +813,8 @@ GHOST_Context *GHOST_WindowCocoa::newDrawingContext(GHOST_TDrawingContextType ty
 {
   if (type == GHOST_kDrawingContextTypeOpenGL) {
 
-    GHOST_Context *context = new GHOST_ContextCGL(m_wantStereoVisual, m_openGLView);
+    GHOST_Context *context = new GHOST_ContextCGL(
+        m_wantStereoVisual, m_metalView, m_metalLayer, m_openGLView);
 
     if (context->initializeDrawingContext())
       return context;
@@ -780,7 +831,8 @@ GHOST_TSuccess GHOST_WindowCocoa::invalidate()
 {
   GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::invalidate(): window invalid");
   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-  [m_openGLView setNeedsDisplay:YES];
+  NSView *view = (m_openGLView) ? m_openGLView : m_metalView;
+  [view setNeedsDisplay:YES];
   [pool drain];
   return GHOST_kSuccess;
 }
index 601c5fb..9ed339c 100644 (file)
  * All rights reserved.
  */
 
-/* NSView subclass for drawing and handling input. */
+/* NSView subclass for drawing and handling input.
+ *
+ * COCOA_VIEW_BASE_CLASS will be either NSView or NSOpenGLView depending if
+ * we use a Metal or OpenGL layer for drawing in the view. We use macros
+ * to defined classes for each case, so we don't have to duplicate code as
+ * Objective-C does not have multiple inheritance. */
 
 // We need to subclass it in order to give Cocoa the feeling key events are trapped
-@interface CocoaOpenGLView : NSOpenGLView <NSTextInput>
+@interface COCOA_VIEW_CLASS : COCOA_VIEW_BASE_CLASS <NSTextInput>
 {
   GHOST_SystemCocoa *systemCocoa;
   GHOST_WindowCocoa *associatedWindow;
@@ -34,7 +39,7 @@
                     windowCocoa:(GHOST_WindowCocoa *)winCocoa;
 @end
 
-@implementation CocoaOpenGLView
+@implementation COCOA_VIEW_CLASS
 
 - (void)setSystemAndWindowCocoa:(GHOST_SystemCocoa *)sysCocoa
                     windowCocoa:(GHOST_WindowCocoa *)winCocoa