[Bf-blender-cvs] [99de160340c] master: macOS: fix viewport lagging, by using CAMetalLayer instead of NSOpenGLView

Tomoaki Kawada noreply at git.blender.org
Sun Jun 2 13:07:13 CEST 2019


Commit: 99de160340c7ae08ac5abed12a541017e4846d87
Author: Tomoaki Kawada
Date:   Fri May 24 18:38:20 2019 +0200
Branches: master
https://developer.blender.org/rB99de160340c7ae08ac5abed12a541017e4846d87

macOS: fix viewport lagging, by using CAMetalLayer instead of NSOpenGLView

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

===================================================================

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

===================================================================

diff --git a/build_files/cmake/platform/platform_apple.cmake b/build_files/cmake/platform/platform_apple.cmake
index 3366d49868d..d7c4370cde7 100644
--- a/build_files/cmake/platform/platform_apple.cmake
+++ b/build_files/cmake/platform/platform_apple.cmake
@@ -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++)
diff --git a/intern/ghost/intern/GHOST_ContextCGL.h b/intern/ghost/intern/GHOST_ContextCGL.h
index b808effc795..37c1ac34299 100644
--- a/intern/ghost/intern/GHOST_ContextCGL.h
+++ b/intern/ghost/intern/GHOST_ContextCGL.h
@@ -26,15 +26,23 @@
 
 #include "GHOST_Context.h"
 
- at class NSOpenGLView;
+ at class CAMetalLayer;
+ at class MTLCommandQueue;
+ at class MTLRenderPipelineState;
+ at class MTLTexture;
 @class NSOpenGLContext;
+ at class NSOpenGLView;
+ at 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__
diff --git a/intern/ghost/intern/GHOST_ContextCGL.mm b/intern/ghost/intern/GHOST_ContextCGL.mm
index 5fe40cb04a0..12c340ffe97 100644
--- a/intern/ghost/intern/GHOST_ContextCGL.mm
+++ b/intern/ghost/intern/GHOST_ContextCGL.mm
@@ -26,25 +26,61 @@
 #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 convertSizeT

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list