Blender  V2.93
GHOST_ContextCGL.mm
Go to the documentation of this file.
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2013 Blender Foundation.
17  * All rights reserved.
18  */
19 
26 #include "GHOST_ContextCGL.h"
27 
28 #include <Cocoa/Cocoa.h>
29 #include <Metal/Metal.h>
30 #include <QuartzCore/QuartzCore.h>
31 
32 #include <cassert>
33 #include <vector>
34 
35 static void ghost_fatal_error_dialog(const char *msg)
36 {
37  /* clang-format off */
38  @autoreleasepool {
39  /* clang-format on */
40  NSString *message = [NSString stringWithFormat:@"Error opening window:\n%s", msg];
41 
42  NSAlert *alert = [[NSAlert alloc] init];
43  [alert addButtonWithTitle:@"Quit"];
44  [alert setMessageText:@"Blender"];
45  [alert setInformativeText:message];
46  [alert setAlertStyle:NSAlertStyleCritical];
47  [alert runModal];
48  }
49 
50  exit(1);
51 }
52 
53 NSOpenGLContext *GHOST_ContextCGL::s_sharedOpenGLContext = nil;
54 int GHOST_ContextCGL::s_sharedCount = 0;
55 
57  NSView *metalView,
58  CAMetalLayer *metalLayer,
59  NSOpenGLView *openGLView)
60  : GHOST_Context(stereoVisual),
61  m_metalView(metalView),
62  m_metalLayer(metalLayer),
63  m_metalCmdQueue(nil),
64  m_metalRenderPipeline(nil),
65  m_openGLView(openGLView),
66  m_openGLContext(nil),
67  m_defaultFramebuffer(0),
68  m_defaultFramebufferMetalTexture(nil),
69  m_debug(false)
70 {
71 #if defined(WITH_GL_PROFILE_CORE)
72  m_coreProfile = true;
73 #else
74  m_coreProfile = false;
75 #endif
76 
77  if (m_metalView) {
78  metalInit();
79  }
80 }
81 
83 {
84  metalFree();
85 
86  if (m_openGLContext != nil) {
87  if (m_openGLContext == [NSOpenGLContext currentContext]) {
88  [NSOpenGLContext clearCurrentContext];
89 
90  if (m_openGLView) {
91  [m_openGLView clearGLContext];
92  }
93  }
94 
95  if (m_openGLContext != s_sharedOpenGLContext || s_sharedCount == 1) {
96  assert(s_sharedCount > 0);
97 
98  s_sharedCount--;
99 
100  if (s_sharedCount == 0)
101  s_sharedOpenGLContext = nil;
102 
103  [m_openGLContext release];
104  }
105  }
106 }
107 
109 {
110  if (m_openGLContext != nil) {
111  if (m_metalView) {
112  metalSwapBuffers();
113  }
114  else if (m_openGLView) {
115  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
116  [m_openGLContext flushBuffer];
117  [pool drain];
118  }
119  return GHOST_kSuccess;
120  }
121  else {
122  return GHOST_kFailure;
123  }
124 }
125 
127 {
128  if (m_openGLContext != nil) {
129  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
130  [m_openGLContext setValues:&interval forParameter:NSOpenGLCPSwapInterval];
131  [pool drain];
132  return GHOST_kSuccess;
133  }
134  else {
135  return GHOST_kFailure;
136  }
137 }
138 
140 {
141  if (m_openGLContext != nil) {
142  GLint interval;
143 
144  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
145 
146  [m_openGLContext getValues:&interval forParameter:NSOpenGLCPSwapInterval];
147 
148  [pool drain];
149 
150  intervalOut = static_cast<int>(interval);
151 
152  return GHOST_kSuccess;
153  }
154  else {
155  return GHOST_kFailure;
156  }
157 }
158 
160 {
161  if (m_openGLContext != nil) {
162  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
163  [m_openGLContext makeCurrentContext];
164  [pool drain];
165  return GHOST_kSuccess;
166  }
167  else {
168  return GHOST_kFailure;
169  }
170 }
171 
173 {
174  if (m_openGLContext != nil) {
175  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
176  [NSOpenGLContext clearCurrentContext];
177  [pool drain];
178  return GHOST_kSuccess;
179  }
180  else {
181  return GHOST_kFailure;
182  }
183 }
184 
186 {
187  return m_defaultFramebuffer;
188 }
189 
191 {
192  if (m_openGLContext != nil) {
193  if (m_metalView) {
194  metalUpdateFramebuffer();
195  }
196  else if (m_openGLView) {
197  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
198  [m_openGLContext update];
199  [pool drain];
200  }
201 
202  return GHOST_kSuccess;
203  }
204  else {
205  return GHOST_kFailure;
206  }
207 }
208 
209 static void makeAttribList(std::vector<NSOpenGLPixelFormatAttribute> &attribs,
210  bool coreProfile,
211  bool stereoVisual,
212  bool needAlpha,
213  bool softwareGL)
214 {
215  attribs.clear();
216 
217  attribs.push_back(NSOpenGLPFAOpenGLProfile);
218  attribs.push_back(coreProfile ? NSOpenGLProfileVersion3_2Core : NSOpenGLProfileVersionLegacy);
219 
220  // Pixel Format Attributes for the windowed NSOpenGLContext
221  attribs.push_back(NSOpenGLPFADoubleBuffer);
222 
223  if (softwareGL) {
224  attribs.push_back(NSOpenGLPFARendererID);
225  attribs.push_back(kCGLRendererGenericFloatID);
226  }
227  else {
228  attribs.push_back(NSOpenGLPFAAccelerated);
229  attribs.push_back(NSOpenGLPFANoRecovery);
230  }
231 
232  if (stereoVisual)
233  attribs.push_back(NSOpenGLPFAStereo);
234 
235  if (needAlpha) {
236  attribs.push_back(NSOpenGLPFAAlphaSize);
237  attribs.push_back((NSOpenGLPixelFormatAttribute)8);
238  }
239 
240  attribs.push_back((NSOpenGLPixelFormatAttribute)0);
241 }
242 
244 {
245  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
246 
247 #ifdef GHOST_OPENGL_ALPHA
248  static const bool needAlpha = true;
249 #else
250  static const bool needAlpha = false;
251 #endif
252 
253  static bool softwareGL = getenv("BLENDER_SOFTWAREGL"); // command-line argument would be better
254 
255  std::vector<NSOpenGLPixelFormatAttribute> attribs;
256  attribs.reserve(40);
257  makeAttribList(attribs, m_coreProfile, m_stereoVisual, needAlpha, softwareGL);
258 
259  NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:&attribs[0]];
260  if (pixelFormat == nil) {
261  goto error;
262  }
263 
264  m_openGLContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat
265  shareContext:s_sharedOpenGLContext];
266  [pixelFormat release];
267 
268  [m_openGLContext makeCurrentContext];
269 
270  if (m_debug) {
271  GLint major = 0, minor = 0;
272  glGetIntegerv(GL_MAJOR_VERSION, &major);
273  glGetIntegerv(GL_MINOR_VERSION, &minor);
274  fprintf(stderr, "OpenGL version %d.%d%s\n", major, minor, softwareGL ? " (software)" : "");
275  fprintf(stderr, "Renderer: %s\n", glGetString(GL_RENDERER));
276  }
277 
278 #ifdef GHOST_WAIT_FOR_VSYNC
279  {
280  GLint swapInt = 1;
281  /* Wait for vertical-sync, to avoid tearing artifacts. */
282  [m_openGLContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
283  }
284 #endif
285 
286  initContextGLEW();
287 
288  if (m_metalView) {
289  if (m_defaultFramebuffer == 0) {
290  // Create a virtual framebuffer
291  [m_openGLContext makeCurrentContext];
292  metalInitFramebuffer();
293  initClearGL();
294  }
295  }
296  else if (m_openGLView) {
297  [m_openGLView setOpenGLContext:m_openGLContext];
298  [m_openGLContext setView:m_openGLView];
299  initClearGL();
300  }
301 
302  [m_openGLContext flushBuffer];
303 
304  if (s_sharedCount == 0)
305  s_sharedOpenGLContext = m_openGLContext;
306 
307  s_sharedCount++;
308 
309  [pool drain];
310 
311  return GHOST_kSuccess;
312 
313 error:
314 
315  [pixelFormat release];
316 
317  [pool drain];
318 
319  return GHOST_kFailure;
320 }
321 
323 {
324  m_openGLContext = nil;
325  m_openGLView = nil;
326  m_metalView = nil;
327 
328  return GHOST_kSuccess;
329 }
330 
331 /* OpenGL on Metal
332  *
333  * Use Metal layer to avoid Viewport lagging on macOS, see T60043. */
334 
335 static const MTLPixelFormat METAL_FRAMEBUFFERPIXEL_FORMAT = MTLPixelFormatBGRA8Unorm;
336 static const OSType METAL_CORE_VIDEO_PIXEL_FORMAT = kCVPixelFormatType_32BGRA;
337 
338 void GHOST_ContextCGL::metalInit()
339 {
340  /* clang-format off */
341  @autoreleasepool {
342  /* clang-format on */
343  id<MTLDevice> device = m_metalLayer.device;
344 
345  // Create a command queue for blit/present operation
346  m_metalCmdQueue = (MTLCommandQueue *)[device newCommandQueue];
347  [m_metalCmdQueue retain];
348 
349  // Create shaders for blit operation
350  NSString *source = @R"msl(
351  using namespace metal;
352 
353  struct Vertex {
354  float4 position [[position]];
355  float2 texCoord [[attribute(0)]];
356  };
357 
358  vertex Vertex vertex_shader(uint v_id [[vertex_id]]) {
359  Vertex vtx;
360 
361  vtx.position.x = float(v_id & 1) * 4.0 - 1.0;
362  vtx.position.y = float(v_id >> 1) * 4.0 - 1.0;
363  vtx.position.z = 0.0;
364  vtx.position.w = 1.0;
365 
366  vtx.texCoord = vtx.position.xy * 0.5 + 0.5;
367 
368  return vtx;
369  }
370 
371  constexpr sampler s {};
372 
373  fragment float4 fragment_shader(Vertex v [[stage_in]],
374  texture2d<float> t [[texture(0)]]) {
375  return t.sample(s, v.texCoord);
376  }
377 
378  )msl";
379 
380  MTLCompileOptions *options = [[[MTLCompileOptions alloc] init] autorelease];
381  options.languageVersion = MTLLanguageVersion1_1;
382 
383  NSError *error = nil;
384  id<MTLLibrary> library = [device newLibraryWithSource:source options:options error:&error];
385  if (error) {
387  "GHOST_ContextCGL::metalInit: newLibraryWithSource:options:error: failed!");
388  }
389 
390  // Create a render pipeline for blit operation
391  MTLRenderPipelineDescriptor *desc = [[[MTLRenderPipelineDescriptor alloc] init] autorelease];
392 
393  desc.fragmentFunction = [library newFunctionWithName:@"fragment_shader"];
394  desc.vertexFunction = [library newFunctionWithName:@"vertex_shader"];
395 
396  [desc.colorAttachments objectAtIndexedSubscript:0].pixelFormat = METAL_FRAMEBUFFERPIXEL_FORMAT;
397 
398  m_metalRenderPipeline = (MTLRenderPipelineState *)[device
399  newRenderPipelineStateWithDescriptor:desc
400  error:&error];
401  if (error) {
403  "GHOST_ContextCGL::metalInit: newRenderPipelineStateWithDescriptor:error: failed!");
404  }
405  }
406 }
407 
408 void GHOST_ContextCGL::metalFree()
409 {
410  if (m_metalCmdQueue) {
411  [m_metalCmdQueue release];
412  }
413  if (m_metalRenderPipeline) {
414  [m_metalRenderPipeline release];
415  }
416  if (m_defaultFramebufferMetalTexture) {
417  [m_defaultFramebufferMetalTexture release];
418  }
419 }
420 
421 void GHOST_ContextCGL::metalInitFramebuffer()
422 {
423  glGenFramebuffers(1, &m_defaultFramebuffer);
425  glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFramebuffer);
426 }
427 
428 void GHOST_ContextCGL::metalUpdateFramebuffer()
429 {
430  assert(m_defaultFramebuffer != 0);
431 
432  NSRect bounds = [m_metalView bounds];
433  NSSize backingSize = [m_metalView convertSizeToBacking:bounds.size];
434  size_t width = (size_t)backingSize.width;
435  size_t height = (size_t)backingSize.height;
436 
437  {
438  /* Test if there is anything to update */
439  id<MTLTexture> tex = (id<MTLTexture>)m_defaultFramebufferMetalTexture;
440  if (tex && tex.width == width && tex.height == height) {
441  return;
442  }
443  }
444 
446 
447  NSDictionary *cvPixelBufferProps = @{
448  (__bridge NSString *)kCVPixelBufferOpenGLCompatibilityKey : @YES,
449  (__bridge NSString *)kCVPixelBufferMetalCompatibilityKey : @YES,
450  };
451  CVPixelBufferRef cvPixelBuffer = nil;
452  CVReturn cvret = CVPixelBufferCreate(kCFAllocatorDefault,
453  width,
454  height,
456  (__bridge CFDictionaryRef)cvPixelBufferProps,
457  &cvPixelBuffer);
458  if (cvret != kCVReturnSuccess) {
460  "GHOST_ContextCGL::metalUpdateFramebuffer: CVPixelBufferCreate failed!");
461  }
462 
463  // Create an OpenGL texture
464  CVOpenGLTextureCacheRef cvGLTexCache = nil;
465  cvret = CVOpenGLTextureCacheCreate(kCFAllocatorDefault,
466  nil,
467  m_openGLContext.CGLContextObj,
468  m_openGLContext.pixelFormat.CGLPixelFormatObj,
469  nil,
470  &cvGLTexCache);
471  if (cvret != kCVReturnSuccess) {
473  "GHOST_ContextCGL::metalUpdateFramebuffer: CVOpenGLTextureCacheCreate failed!");
474  }
475 
476  CVOpenGLTextureRef cvGLTex = nil;
477  cvret = CVOpenGLTextureCacheCreateTextureFromImage(
478  kCFAllocatorDefault, cvGLTexCache, cvPixelBuffer, nil, &cvGLTex);
479  if (cvret != kCVReturnSuccess) {
481  "GHOST_ContextCGL::metalUpdateFramebuffer: "
482  "CVOpenGLTextureCacheCreateTextureFromImage failed!");
483  }
484 
485  unsigned int glTex;
486  glTex = CVOpenGLTextureGetName(cvGLTex);
487 
488  // Create a Metal texture
489  CVMetalTextureCacheRef cvMetalTexCache = nil;
490  cvret = CVMetalTextureCacheCreate(
491  kCFAllocatorDefault, nil, m_metalLayer.device, nil, &cvMetalTexCache);
492  if (cvret != kCVReturnSuccess) {
494  "GHOST_ContextCGL::metalUpdateFramebuffer: CVMetalTextureCacheCreate failed!");
495  }
496 
497  CVMetalTextureRef cvMetalTex = nil;
498  cvret = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
499  cvMetalTexCache,
500  cvPixelBuffer,
501  nil,
503  width,
504  height,
505  0,
506  &cvMetalTex);
507  if (cvret != kCVReturnSuccess) {
509  "GHOST_ContextCGL::metalUpdateFramebuffer: "
510  "CVMetalTextureCacheCreateTextureFromImage failed!");
511  }
512 
513  MTLTexture *tex = (MTLTexture *)CVMetalTextureGetTexture(cvMetalTex);
514 
515  if (!tex) {
517  "GHOST_ContextCGL::metalUpdateFramebuffer: CVMetalTextureGetTexture failed!");
518  }
519 
520  [m_defaultFramebufferMetalTexture release];
521  m_defaultFramebufferMetalTexture = [tex retain];
522 
523  glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFramebuffer);
524  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, glTex, 0);
525 
526  [m_metalLayer setDrawableSize:CGSizeMake((CGFloat)width, (CGFloat)height)];
527 
528  CVPixelBufferRelease(cvPixelBuffer);
529  CVOpenGLTextureCacheRelease(cvGLTexCache);
530  CVOpenGLTextureRelease(cvGLTex);
531  CFRelease(cvMetalTexCache);
532  CFRelease(cvMetalTex);
533 }
534 
535 void GHOST_ContextCGL::metalSwapBuffers()
536 {
537  /* clang-format off */
538  @autoreleasepool {
539  /* clang-format on */
541  glFlush();
542 
543  assert(m_defaultFramebufferMetalTexture != 0);
544 
545  id<CAMetalDrawable> drawable = [m_metalLayer nextDrawable];
546  if (!drawable) {
547  return;
548  }
549 
550  id<MTLCommandBuffer> cmdBuffer = [m_metalCmdQueue commandBuffer];
551 
552  MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
553  {
554  auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0];
555  attachment.texture = drawable.texture;
556  attachment.loadAction = MTLLoadActionDontCare;
557  attachment.storeAction = MTLStoreActionStore;
558  }
559 
560  id<MTLTexture> srcTexture = (id<MTLTexture>)m_defaultFramebufferMetalTexture;
561 
562  {
563  id<MTLRenderCommandEncoder> enc = [cmdBuffer
564  renderCommandEncoderWithDescriptor:passDescriptor];
565 
566  [enc setRenderPipelineState:(id<MTLRenderPipelineState>)m_metalRenderPipeline];
567  [enc setFragmentTexture:srcTexture atIndex:0];
568  [enc drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
569 
570  [enc endEncoding];
571  }
572 
573  [cmdBuffer presentDrawable:drawable];
574 
575  [cmdBuffer commit];
576  }
577 }
static void makeAttribList(std::vector< NSOpenGLPixelFormatAttribute > &attribs, bool coreProfile, bool stereoVisual, bool needAlpha, bool softwareGL)
static void ghost_fatal_error_dialog(const char *msg)
static const OSType METAL_CORE_VIDEO_PIXEL_FORMAT
static const MTLPixelFormat METAL_FRAMEBUFFERPIXEL_FORMAT
GHOST_TSuccess
Definition: GHOST_Types.h:91
@ GHOST_kFailure
Definition: GHOST_Types.h:91
@ GHOST_kSuccess
Definition: GHOST_Types.h:91
_GL_VOID GLfloat value _GL_VOID_RET _GL_VOID const GLuint GLboolean *residences _GL_BOOL_RET _GL_VOID GLsizei GLfloat GLfloat GLfloat GLfloat const GLubyte *bitmap _GL_VOID_RET _GL_VOID GLenum const void *lists _GL_VOID_RET _GL_VOID const GLdouble *equation _GL_VOID_RET _GL_VOID GLdouble GLdouble blue _GL_VOID_RET _GL_VOID GLfloat GLfloat blue _GL_VOID_RET _GL_VOID GLint GLint blue _GL_VOID_RET _GL_VOID GLshort GLshort blue _GL_VOID_RET _GL_VOID GLubyte GLubyte blue _GL_VOID_RET _GL_VOID GLuint GLuint blue _GL_VOID_RET _GL_VOID GLushort GLushort blue _GL_VOID_RET _GL_VOID GLbyte GLbyte GLbyte alpha _GL_VOID_RET _GL_VOID GLdouble GLdouble GLdouble alpha _GL_VOID_RET _GL_VOID GLfloat GLfloat GLfloat alpha _GL_VOID_RET _GL_VOID GLint GLint GLint alpha _GL_VOID_RET _GL_VOID GLshort GLshort GLshort alpha _GL_VOID_RET _GL_VOID GLubyte GLubyte GLubyte alpha _GL_VOID_RET _GL_VOID GLuint GLuint GLuint alpha _GL_VOID_RET _GL_VOID GLushort GLushort GLushort alpha _GL_VOID_RET _GL_VOID GLenum mode _GL_VOID_RET _GL_VOID GLint GLsizei width
_GL_VOID GLfloat value _GL_VOID_RET _GL_VOID const GLuint GLboolean *residences _GL_BOOL_RET _GL_VOID GLsizei height
static btDbvtVolume bounds(btDbvtNode **leaves, int count)
Definition: btDbvt.cpp:299
GHOST_TSuccess setSwapInterval(int interval)
unsigned int getDefaultFramebuffer()
GHOST_TSuccess swapBuffers()
GHOST_TSuccess initializeDrawingContext()
GHOST_ContextCGL(bool stereoVisual, NSView *metalView, CAMetalLayer *metalLayer, NSOpenGLView *openglView)
GHOST_TSuccess releaseDrawingContext()
GHOST_TSuccess activateDrawingContext()
GHOST_TSuccess releaseNativeHandles()
GHOST_TSuccess getSwapInterval(int &)
GHOST_TSuccess updateDrawingContext()
void initContextGLEW()
static void initClearGL()
CCL_NAMESPACE_BEGIN struct Options options
static FT_Library library
Definition: freetypefont.c:51
static void error(const char *str)
Definition: meshlaplacian.c:65
static void update(bNodeTree *ntree)