Blender  V3.3
GHOST_ContextCGL.mm
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later
2  * Copyright 2013 Blender Foundation. All rights reserved. */
3 
10 /* Don't generate OpenGL deprecation warning. This is a known thing, and is not something easily
11  * solvable in a short term. */
12 #ifdef __clang__
13 # pragma clang diagnostic ignored "-Wdeprecated-declarations"
14 #endif
15 
16 #include "GHOST_ContextCGL.h"
17 
18 #include <Cocoa/Cocoa.h>
19 #include <Metal/Metal.h>
20 #include <QuartzCore/QuartzCore.h>
21 
22 #include <cassert>
23 #include <vector>
24 
25 static void ghost_fatal_error_dialog(const char *msg)
26 {
27  /* clang-format off */
28  @autoreleasepool {
29  /* clang-format on */
30  NSString *message = [NSString stringWithFormat:@"Error opening window:\n%s", msg];
31 
32  NSAlert *alert = [[NSAlert alloc] init];
33  [alert addButtonWithTitle:@"Quit"];
34  [alert setMessageText:@"Blender"];
35  [alert setInformativeText:message];
36  [alert setAlertStyle:NSAlertStyleCritical];
37  [alert runModal];
38  }
39 
40  exit(1);
41 }
42 
43 NSOpenGLContext *GHOST_ContextCGL::s_sharedOpenGLContext = nil;
44 int GHOST_ContextCGL::s_sharedCount = 0;
45 
47  NSView *metalView,
48  CAMetalLayer *metalLayer,
49  NSOpenGLView *openGLView)
50  : GHOST_Context(stereoVisual),
51  m_metalView(metalView),
52  m_metalLayer(metalLayer),
53  m_metalCmdQueue(nil),
54  m_metalRenderPipeline(nil),
55  m_openGLView(openGLView),
56  m_openGLContext(nil),
57  m_defaultFramebuffer(0),
58  m_defaultFramebufferMetalTexture(nil),
59  m_debug(false)
60 {
61 #if defined(WITH_GL_PROFILE_CORE)
62  m_coreProfile = true;
63 #else
64  m_coreProfile = false;
65 #endif
66 
67  if (m_metalView) {
68  metalInit();
69  }
70 }
71 
73 {
74  metalFree();
75 
76  if (m_openGLContext != nil) {
77  if (m_openGLContext == [NSOpenGLContext currentContext]) {
78  [NSOpenGLContext clearCurrentContext];
79 
80  if (m_openGLView) {
81  [m_openGLView clearGLContext];
82  }
83  }
84 
85  if (m_openGLContext != s_sharedOpenGLContext || s_sharedCount == 1) {
86  assert(s_sharedCount > 0);
87 
88  s_sharedCount--;
89 
90  if (s_sharedCount == 0)
91  s_sharedOpenGLContext = nil;
92 
93  [m_openGLContext release];
94  }
95  }
96 }
97 
99 {
100  if (m_openGLContext != nil) {
101  if (m_metalView) {
102  metalSwapBuffers();
103  }
104  else if (m_openGLView) {
105  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
106  [m_openGLContext flushBuffer];
107  [pool drain];
108  }
109  return GHOST_kSuccess;
110  }
111  else {
112  return GHOST_kFailure;
113  }
114 }
115 
117 {
118  if (m_openGLContext != nil) {
119  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
120  [m_openGLContext setValues:&interval forParameter:NSOpenGLCPSwapInterval];
121  [pool drain];
122  return GHOST_kSuccess;
123  }
124  else {
125  return GHOST_kFailure;
126  }
127 }
128 
130 {
131  if (m_openGLContext != nil) {
132  GLint interval;
133 
134  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
135 
136  [m_openGLContext getValues:&interval forParameter:NSOpenGLCPSwapInterval];
137 
138  [pool drain];
139 
140  intervalOut = static_cast<int>(interval);
141 
142  return GHOST_kSuccess;
143  }
144  else {
145  return GHOST_kFailure;
146  }
147 }
148 
150 {
151  if (m_openGLContext != nil) {
152  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
153  [m_openGLContext makeCurrentContext];
154  [pool drain];
155  return GHOST_kSuccess;
156  }
157  else {
158  return GHOST_kFailure;
159  }
160 }
161 
163 {
164  if (m_openGLContext != nil) {
165  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
166  [NSOpenGLContext clearCurrentContext];
167  [pool drain];
168  return GHOST_kSuccess;
169  }
170  else {
171  return GHOST_kFailure;
172  }
173 }
174 
176 {
177  return m_defaultFramebuffer;
178 }
179 
181 {
182  if (m_openGLContext != nil) {
183  if (m_metalView) {
184  metalUpdateFramebuffer();
185  }
186  else if (m_openGLView) {
187  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
188  [m_openGLContext update];
189  [pool drain];
190  }
191 
192  return GHOST_kSuccess;
193  }
194  else {
195  return GHOST_kFailure;
196  }
197 }
198 
199 static void makeAttribList(std::vector<NSOpenGLPixelFormatAttribute> &attribs,
200  bool coreProfile,
201  bool stereoVisual,
202  bool needAlpha,
203  bool softwareGL,
204  bool increasedSamplerLimit)
205 {
206  attribs.clear();
207 
208  attribs.push_back(NSOpenGLPFAOpenGLProfile);
209  attribs.push_back(coreProfile ? NSOpenGLProfileVersion3_2Core : NSOpenGLProfileVersionLegacy);
210 
211  /* Pixel Format Attributes for the windowed NSOpenGLContext. */
212  attribs.push_back(NSOpenGLPFADoubleBuffer);
213 
214  if (softwareGL) {
215  attribs.push_back(NSOpenGLPFARendererID);
216  attribs.push_back(kCGLRendererGenericFloatID);
217  }
218  else {
219  attribs.push_back(NSOpenGLPFAAccelerated);
220  attribs.push_back(NSOpenGLPFANoRecovery);
221 
222  /* Attempt to initialise device with extended sampler limit.
223  * Resolves EEVEE purple rendering artifacts on macOS. */
224  if (increasedSamplerLimit) {
225  attribs.push_back((NSOpenGLPixelFormatAttribute)400);
226  }
227  }
228 
229  if (stereoVisual)
230  attribs.push_back(NSOpenGLPFAStereo);
231 
232  if (needAlpha) {
233  attribs.push_back(NSOpenGLPFAAlphaSize);
234  attribs.push_back((NSOpenGLPixelFormatAttribute)8);
235  }
236 
237  attribs.push_back((NSOpenGLPixelFormatAttribute)0);
238 }
239 
241 {
242  @autoreleasepool {
243 
244 #ifdef GHOST_OPENGL_ALPHA
245  static const bool needAlpha = true;
246 #else
247  static const bool needAlpha = false;
248 #endif
249 
250  /* Command-line argument would be better. */
251  static bool softwareGL = getenv("BLENDER_SOFTWAREGL");
252 
253  NSOpenGLPixelFormat *pixelFormat = nil;
254  std::vector<NSOpenGLPixelFormatAttribute> attribs;
255  bool increasedSamplerLimit = false;
256 
257  /* Attempt to initialize device with increased sampler limit.
258  * If this is unsupported and initialization fails, initialize GL Context as normal.
259  *
260  * NOTE: This is not available when using the SoftwareGL path, or for Intel-based
261  * platforms. */
262  if (!softwareGL) {
263  if (@available(macos 11.0, *)) {
264  increasedSamplerLimit = true;
265  }
266  }
267  const int max_ctx_attempts = increasedSamplerLimit ? 2 : 1;
268  for (int ctx_create_attempt = 0; ctx_create_attempt < max_ctx_attempts; ctx_create_attempt++) {
269 
270  attribs.clear();
271  attribs.reserve(40);
273  attribs, m_coreProfile, m_stereoVisual, needAlpha, softwareGL, increasedSamplerLimit);
274 
275  pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:&attribs[0]];
276  if (pixelFormat == nil) {
277  /* If pixel format creation fails when testing increased sampler limit,
278  * attempt intialisation again with feature disabled, otherwise, fail. */
279  if (increasedSamplerLimit) {
280  increasedSamplerLimit = false;
281  continue;
282  }
283  return GHOST_kFailure;
284  }
285 
286  /* Attempt to create context. */
287  m_openGLContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat
288  shareContext:s_sharedOpenGLContext];
289  [pixelFormat release];
290 
291  if (m_openGLContext == nil) {
292  /* If context creation fails when testing increased sampler limit,
293  * attempt re-creation with feature disabled. Otherwise, error. */
294  if (increasedSamplerLimit) {
295  increasedSamplerLimit = false;
296  continue;
297  }
298 
299  /* Default context creation attempt failed. */
300  return GHOST_kFailure;
301  }
302 
303  /* Created GL context successfully, activate. */
304  [m_openGLContext makeCurrentContext];
305 
306  /* When increasing sampler limit, verify created context is a supported configuration. */
307  if (increasedSamplerLimit) {
308  const char *vendor = (const char *)glGetString(GL_VENDOR);
309  const char *renderer = (const char *)glGetString(GL_RENDERER);
310 
311  /* If generated context type is unsupported, release existing context and
312  * fallback to creating a normal context below. */
313  if (strstr(vendor, "Intel") || strstr(renderer, "Software")) {
314  [m_openGLContext release];
315  m_openGLContext = nil;
316  increasedSamplerLimit = false;
317  continue;
318  }
319  }
320  }
321 
322  if (m_debug) {
323  GLint major = 0, minor = 0;
324  glGetIntegerv(GL_MAJOR_VERSION, &major);
325  glGetIntegerv(GL_MINOR_VERSION, &minor);
326  fprintf(stderr, "OpenGL version %d.%d%s\n", major, minor, softwareGL ? " (software)" : "");
327  fprintf(stderr, "Renderer: %s\n", glGetString(GL_RENDERER));
328  }
329 
330 #ifdef GHOST_WAIT_FOR_VSYNC
331  {
332  GLint swapInt = 1;
333  /* Wait for vertical-sync, to avoid tearing artifacts. */
334  [m_openGLContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
335  }
336 #endif
337 
338  initContextGLEW();
339 
340  if (m_metalView) {
341  if (m_defaultFramebuffer == 0) {
342  /* Create a virtual frame-buffer. */
343  [m_openGLContext makeCurrentContext];
344  metalInitFramebuffer();
345  initClearGL();
346  }
347  }
348  else if (m_openGLView) {
349  [m_openGLView setOpenGLContext:m_openGLContext];
350  [m_openGLContext setView:m_openGLView];
351  initClearGL();
352  }
353 
354  [m_openGLContext flushBuffer];
355 
356  if (s_sharedCount == 0)
357  s_sharedOpenGLContext = m_openGLContext;
358 
359  s_sharedCount++;
360  }
361  return GHOST_kSuccess;
362 }
363 
365 {
366  m_openGLContext = nil;
367  m_openGLView = nil;
368  m_metalView = nil;
369 
370  return GHOST_kSuccess;
371 }
372 
373 /* OpenGL on Metal
374  *
375  * Use Metal layer to avoid Viewport lagging on macOS, see T60043. */
376 
377 static const MTLPixelFormat METAL_FRAMEBUFFERPIXEL_FORMAT = MTLPixelFormatBGRA8Unorm;
378 static const OSType METAL_CORE_VIDEO_PIXEL_FORMAT = kCVPixelFormatType_32BGRA;
379 
380 void GHOST_ContextCGL::metalInit()
381 {
382  /* clang-format off */
383  @autoreleasepool {
384  /* clang-format on */
385  id<MTLDevice> device = m_metalLayer.device;
386 
387  /* Create a command queue for blit/present operation. */
388  m_metalCmdQueue = (MTLCommandQueue *)[device newCommandQueue];
389  [m_metalCmdQueue retain];
390 
391  /* Create shaders for blit operation. */
392  NSString *source = @R"msl(
393  using namespace metal;
394 
395  struct Vertex {
396  float4 position [[position]];
397  float2 texCoord [[attribute(0)]];
398  };
399 
400  vertex Vertex vertex_shader(uint v_id [[vertex_id]]) {
401  Vertex vtx;
402 
403  vtx.position.x = float(v_id & 1) * 4.0 - 1.0;
404  vtx.position.y = float(v_id >> 1) * 4.0 - 1.0;
405  vtx.position.z = 0.0;
406  vtx.position.w = 1.0;
407 
408  vtx.texCoord = vtx.position.xy * 0.5 + 0.5;
409 
410  return vtx;
411  }
412 
413  constexpr sampler s {};
414 
415  fragment float4 fragment_shader(Vertex v [[stage_in]],
416  texture2d<float> t [[texture(0)]]) {
417  return t.sample(s, v.texCoord);
418  }
419 
420  )msl";
421 
422  MTLCompileOptions *options = [[[MTLCompileOptions alloc] init] autorelease];
423  options.languageVersion = MTLLanguageVersion1_1;
424 
425  NSError *error = nil;
426  id<MTLLibrary> library = [device newLibraryWithSource:source options:options error:&error];
427  if (error) {
429  "GHOST_ContextCGL::metalInit: newLibraryWithSource:options:error: failed!");
430  }
431 
432  /* Create a render pipeline for blit operation. */
433  MTLRenderPipelineDescriptor *desc = [[[MTLRenderPipelineDescriptor alloc] init] autorelease];
434 
435  desc.fragmentFunction = [library newFunctionWithName:@"fragment_shader"];
436  desc.vertexFunction = [library newFunctionWithName:@"vertex_shader"];
437 
438  [desc.colorAttachments objectAtIndexedSubscript:0].pixelFormat = METAL_FRAMEBUFFERPIXEL_FORMAT;
439 
440  m_metalRenderPipeline = (MTLRenderPipelineState *)[device
441  newRenderPipelineStateWithDescriptor:desc
442  error:&error];
443  if (error) {
445  "GHOST_ContextCGL::metalInit: newRenderPipelineStateWithDescriptor:error: failed!");
446  }
447  }
448 }
449 
450 void GHOST_ContextCGL::metalFree()
451 {
452  if (m_metalCmdQueue) {
453  [m_metalCmdQueue release];
454  }
455  if (m_metalRenderPipeline) {
456  [m_metalRenderPipeline release];
457  }
458  if (m_defaultFramebufferMetalTexture) {
459  [m_defaultFramebufferMetalTexture release];
460  }
461 }
462 
463 void GHOST_ContextCGL::metalInitFramebuffer()
464 {
465  glGenFramebuffers(1, &m_defaultFramebuffer);
467  glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFramebuffer);
468 }
469 
470 void GHOST_ContextCGL::metalUpdateFramebuffer()
471 {
472  assert(m_defaultFramebuffer != 0);
473 
474  NSRect bounds = [m_metalView bounds];
475  NSSize backingSize = [m_metalView convertSizeToBacking:bounds.size];
476  size_t width = (size_t)backingSize.width;
477  size_t height = (size_t)backingSize.height;
478 
479  {
480  /* Test if there is anything to update */
481  id<MTLTexture> tex = (id<MTLTexture>)m_defaultFramebufferMetalTexture;
482  if (tex && tex.width == width && tex.height == height) {
483  return;
484  }
485  }
486 
488 
489  NSDictionary *cvPixelBufferProps = @{
490  (__bridge NSString *)kCVPixelBufferOpenGLCompatibilityKey : @YES,
491  (__bridge NSString *)kCVPixelBufferMetalCompatibilityKey : @YES,
492  };
493  CVPixelBufferRef cvPixelBuffer = nil;
494  CVReturn cvret = CVPixelBufferCreate(kCFAllocatorDefault,
495  width,
496  height,
498  (__bridge CFDictionaryRef)cvPixelBufferProps,
499  &cvPixelBuffer);
500  if (cvret != kCVReturnSuccess) {
502  "GHOST_ContextCGL::metalUpdateFramebuffer: CVPixelBufferCreate failed!");
503  }
504 
505  /* Create an OpenGL texture. */
506  CVOpenGLTextureCacheRef cvGLTexCache = nil;
507  cvret = CVOpenGLTextureCacheCreate(kCFAllocatorDefault,
508  nil,
509  m_openGLContext.CGLContextObj,
510  m_openGLContext.pixelFormat.CGLPixelFormatObj,
511  nil,
512  &cvGLTexCache);
513  if (cvret != kCVReturnSuccess) {
515  "GHOST_ContextCGL::metalUpdateFramebuffer: CVOpenGLTextureCacheCreate failed!");
516  }
517 
518  CVOpenGLTextureRef cvGLTex = nil;
519  cvret = CVOpenGLTextureCacheCreateTextureFromImage(
520  kCFAllocatorDefault, cvGLTexCache, cvPixelBuffer, nil, &cvGLTex);
521  if (cvret != kCVReturnSuccess) {
523  "GHOST_ContextCGL::metalUpdateFramebuffer: "
524  "CVOpenGLTextureCacheCreateTextureFromImage failed!");
525  }
526 
527  unsigned int glTex;
528  glTex = CVOpenGLTextureGetName(cvGLTex);
529 
530  /* Create a Metal texture. */
531  CVMetalTextureCacheRef cvMetalTexCache = nil;
532  cvret = CVMetalTextureCacheCreate(
533  kCFAllocatorDefault, nil, m_metalLayer.device, nil, &cvMetalTexCache);
534  if (cvret != kCVReturnSuccess) {
536  "GHOST_ContextCGL::metalUpdateFramebuffer: CVMetalTextureCacheCreate failed!");
537  }
538 
539  CVMetalTextureRef cvMetalTex = nil;
540  cvret = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
541  cvMetalTexCache,
542  cvPixelBuffer,
543  nil,
545  width,
546  height,
547  0,
548  &cvMetalTex);
549  if (cvret != kCVReturnSuccess) {
551  "GHOST_ContextCGL::metalUpdateFramebuffer: "
552  "CVMetalTextureCacheCreateTextureFromImage failed!");
553  }
554 
555  MTLTexture *tex = (MTLTexture *)CVMetalTextureGetTexture(cvMetalTex);
556 
557  if (!tex) {
559  "GHOST_ContextCGL::metalUpdateFramebuffer: CVMetalTextureGetTexture failed!");
560  }
561 
562  [m_defaultFramebufferMetalTexture release];
563  m_defaultFramebufferMetalTexture = [tex retain];
564 
565  glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFramebuffer);
566  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, glTex, 0);
567 
568  [m_metalLayer setDrawableSize:CGSizeMake((CGFloat)width, (CGFloat)height)];
569 
570  CVPixelBufferRelease(cvPixelBuffer);
571  CVOpenGLTextureCacheRelease(cvGLTexCache);
572  CVOpenGLTextureRelease(cvGLTex);
573  CFRelease(cvMetalTexCache);
574  CFRelease(cvMetalTex);
575 }
576 
577 void GHOST_ContextCGL::metalSwapBuffers()
578 {
579  /* clang-format off */
580  @autoreleasepool {
581  /* clang-format on */
583  glFlush();
584 
585  assert(m_defaultFramebufferMetalTexture != 0);
586 
587  id<CAMetalDrawable> drawable = [m_metalLayer nextDrawable];
588  if (!drawable) {
589  return;
590  }
591 
592  id<MTLCommandBuffer> cmdBuffer = [m_metalCmdQueue commandBuffer];
593 
594  MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
595  {
596  auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0];
597  attachment.texture = drawable.texture;
598  attachment.loadAction = MTLLoadActionDontCare;
599  attachment.storeAction = MTLStoreActionStore;
600  }
601 
602  id<MTLTexture> srcTexture = (id<MTLTexture>)m_defaultFramebufferMetalTexture;
603 
604  {
605  id<MTLRenderCommandEncoder> enc = [cmdBuffer
606  renderCommandEncoderWithDescriptor:passDescriptor];
607 
608  [enc setRenderPipelineState:(id<MTLRenderPipelineState>)m_metalRenderPipeline];
609  [enc setFragmentTexture:srcTexture atIndex:0];
610  [enc drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
611 
612  [enc endEncoding];
613  }
614 
615  [cmdBuffer presentDrawable:drawable];
616 
617  [cmdBuffer commit];
618  }
619 }
static void ghost_fatal_error_dialog(const char *msg)
static const OSType METAL_CORE_VIDEO_PIXEL_FORMAT
static void makeAttribList(std::vector< NSOpenGLPixelFormatAttribute > &attribs, bool coreProfile, bool stereoVisual, bool needAlpha, bool softwareGL, bool increasedSamplerLimit)
static const MTLPixelFormat METAL_FRAMEBUFFERPIXEL_FORMAT
GHOST_TSuccess
Definition: GHOST_Types.h:74
@ GHOST_kFailure
Definition: GHOST_Types.h:74
@ GHOST_kSuccess
Definition: GHOST_Types.h:74
_GL_VOID GLfloat value _GL_VOID_RET _GL_VOID const GLuint GLboolean *residences _GL_BOOL_RET _GL_VOID GLsizei height
_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
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 void error(const char *str)
Definition: meshlaplacian.c:51
static void update(bNodeTree *ntree)
static FT_Library library