Vuo  2.0.2
VuoGlContext.cc
Go to the documentation of this file.
1 
10 #include <vector>
11 #include <algorithm>
12 #include <bitset>
13 #include <map>
14 using namespace std;
15 
16 #include "module.h"
17 #include "VuoGlContext.h"
18 #include "VuoCglPixelFormat.h"
19 #include "VuoScreenCommon.h"
20 
21 #include <OpenGL/CGLMacro.h>
22 #include <CoreFoundation/CoreFoundation.h>
23 #include <CoreGraphics/CoreGraphics.h>
24 
25 #include <dispatch/dispatch.h>
26 #include <dlfcn.h>
27 #include <mach-o/dyld.h>
28 #include <objc/objc-runtime.h>
29 #include <pthread.h>
30 
31 
32 static CGLContextObj VuoGlContext_create(CGLContextObj rootContext);
33 dispatch_semaphore_t VuoGlContext_poolSemaphore;
34 static CGLContextObj VuoGlContext_root = NULL;
35 vector<CGLContextObj> VuoGlContext_allSharedContexts;
36 vector<CGLContextObj> VuoGlContext_avaialbleSharedContexts;
37 
38 static dispatch_once_t VuoGlContextPoolCreated = 0;
39 static pthread_key_t VuoGlContextPerformKey;
40 
44 static void VuoGlContext_renderers(void)
45 {
46  VuoList_VuoScreen screensList = VuoScreen_getList();
47  unsigned long screenCount = VuoListGetCount_VuoScreen(screensList);
48  VuoScreen *screens = VuoListGetData_VuoScreen(screensList);
49  VuoLocal(screensList);
50 
51  // https://developer.apple.com/library/mac/qa/qa1168/_index.html says:
52  // "If you are looking for the VRAM sizes of all the renderers on your system […]
53  // you may specify a -1/0xFFFFFFFF display mask in the CGLQueryRendererInfo() function.
54  CGLRendererInfoObj ri;
55  GLint rendererCount = 0;
56  CGLQueryRendererInfo(-1, &ri, &rendererCount);
57  for (int i = 0; i < rendererCount; ++i)
58  {
59  GLint rendererID;
60  CGLDescribeRenderer(ri, i, kCGLRPRendererID, &rendererID);
61  VUserLog("Renderer %d: %s", i, VuoCglRenderer_getText(rendererID));
62 
63  GLint online;
64  if (CGLDescribeRenderer(ri, i, kCGLRPOnline, &online) == kCGLNoError)
65  VUserLog(" Online : %s", online ? "yes" : "no");
66 
67  GLint accelerated;
68  if (CGLDescribeRenderer(ri, i, kCGLRPAccelerated, &accelerated) == kCGLNoError)
69  VUserLog(" Accelerated : %s", accelerated ? "yes" : "no");
70 
71  GLint videoMegabytes = 0;
72  if (CGLDescribeRenderer(ri, i, kCGLRPVideoMemoryMegabytes, &videoMegabytes) == kCGLNoError
73  && videoMegabytes)
74  VUserLog(" Video memory : %d MB", videoMegabytes);
75 
76  GLint textureMegabytes = 0;
77  if (CGLDescribeRenderer(ri, i, kCGLRPTextureMemoryMegabytes, &textureMegabytes) == kCGLNoError
78  && textureMegabytes)
79  VUserLog(" Texture memory : %d MB", textureMegabytes);
80 
81  GLint displayMask;
82  if (CGLDescribeRenderer(ri, i, kCGLRPDisplayMask, &displayMask) == kCGLNoError)
83  {
84  VUserLog(" Display mask : %s (0x%x)%s",
85  std::bitset<32>(displayMask).to_string().c_str(),
86  displayMask, (displayMask & 0xff) == 0xff ? " (any)" : "");
87  if ((displayMask & 0xff) != 0xff)
88  for (unsigned long i = 0; i < screenCount; ++i)
89  if (displayMask & screens[i].displayMask)
90  VUserLog(" %s %s",
91  std::bitset<32>(screens[i].displayMask).to_string().c_str(),
92  screens[i].name);
93  }
94 
95  GLint glVersion = 0;
96  if (CGLDescribeRenderer(ri, i, /*kCGLRPMajorGLVersion*/ (CGLRendererProperty)133, &glVersion) == kCGLNoError)
97  VUserLog(" OpenGL version : %d", glVersion);
98 
99  CGLPixelFormatObj pf = (CGLPixelFormatObj)VuoGlContext_makePlatformPixelFormat(false, false, displayMask);
100  if (pf != (CGLPixelFormatObj)-1)
101  {
102  CGLContextObj cgl_ctx;
103  CGLError error = CGLCreateContext(pf, NULL, &cgl_ctx);
104  if (error != kCGLNoError)
105  VUserLog(" Error: %s", CGLErrorString(error));
106  else
107  {
108  GLint maxTextureSize;
109  glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
110  VUserLog(" OpenGL 2 : %s (%s) maxTextureSize=%d", glGetString(GL_RENDERER), glGetString(GL_VERSION), maxTextureSize);
111  CGLDestroyContext(cgl_ctx);
112  }
113  }
114  else
115  VUserLog(" (Can't create an OpenGL 2 context on this renderer.)");
116 
117  pf = (CGLPixelFormatObj)VuoGlContext_makePlatformPixelFormat(false, true, displayMask);
118  if (pf != (CGLPixelFormatObj)-1)
119  {
120  CGLContextObj cgl_ctx;
121  CGLError error = CGLCreateContext(pf, NULL, &cgl_ctx);
122  if (error != kCGLNoError)
123  VUserLog(" Error: %s", CGLErrorString(error));
124  else
125  {
126  GLint maxTextureSize;
127  glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
128  VUserLog(" OpenGL Core Profile: %s (%s) maxTextureSize=%d", glGetString(GL_RENDERER), glGetString(GL_VERSION), maxTextureSize);
129  CGLDestroyContext(cgl_ctx);
130  }
131  }
132  else
133  VUserLog(" (Can't create an OpenGL Core Profile context on this renderer.)");
134 
135  GLint cl = 0;
136  if (CGLDescribeRenderer(ri, i, kCGLRPAcceleratedCompute, &cl) == kCGLNoError)
137  VUserLog(" OpenCL supported : %s", cl ? "yes" : "no");
138  }
139  CGLDestroyRendererInfo(ri);
140 
141 
142  const char *gldriver = "GLDriver";
143  size_t gldriverLen = strlen(gldriver);
144  uint32_t imageCount = _dyld_image_count();
145  for (uint32_t i = 0; i < imageCount; ++i)
146  {
147  const char *dylibPath = _dyld_get_image_name(i);
148  size_t len = strlen(dylibPath);
149  // If the image name ends with "GLDriver"…
150  if (strcmp(dylibPath + len - gldriverLen, gldriver) == 0)
151  {
152  // Trim off the path and the common "GLDriver" suffix.
153  char *z = strdup(strrchr(dylibPath, '/')+1);
154  z[strlen(z)-gldriverLen] = 0;
155  VUserLog("Driver: %s", z);
156  free(z);
157  }
158  }
159 
160  typedef void *(*mtlCopyAllDevicesType)(void);
161  mtlCopyAllDevicesType mtlCopyAllDevices = (mtlCopyAllDevicesType)dlsym(RTLD_DEFAULT, "MTLCopyAllDevices");
162  if (mtlCopyAllDevices)
163  {
164  CFArrayRef mtlDevices = (CFArrayRef)mtlCopyAllDevices();
165  int mtlDeviceCount = CFArrayGetCount(mtlDevices);
166  if (mtlDeviceCount)
167  VUserLog("Metal devices:");
168  for (int i = 0; i < mtlDeviceCount; ++i)
169  {
170  id dev = (id)CFArrayGetValueAtIndex(mtlDevices, i);
171  VUserLog(" %s:", (char *)objc_msgSend(objc_msgSend(dev, sel_getUid("name")), sel_getUid("UTF8String")));
172  if (class_respondsToSelector(object_getClass(dev), sel_getUid("registryID")))
173  VUserLog(" ID : %p", objc_msgSend(dev, sel_getUid("registryID")));
174  if (class_respondsToSelector(object_getClass(dev), sel_getUid("recommendedMaxWorkingSetSize")))
175  VUserLog(" Recommended max working-set size : %lld MiB", (int64_t)objc_msgSend(dev, sel_getUid("recommendedMaxWorkingSetSize"))/1048576);
176  if (class_respondsToSelector(object_getClass(dev), sel_getUid("maxBufferLength")))
177  VUserLog(" Max buffer length : %lld MiB", (int64_t)objc_msgSend(dev, sel_getUid("maxBufferLength"))/1048576);
178  if (class_respondsToSelector(object_getClass(dev), sel_getUid("maxThreadgroupMemoryLength")))
179  VUserLog(" Threadgroup memory : %lld B", (int64_t)objc_msgSend(dev, sel_getUid("maxThreadgroupMemoryLength")));
180  VUserLog(" Low-power : %s", objc_msgSend(dev, sel_getUid("isLowPower")) ? "yes" : "no");
181  if (class_respondsToSelector(object_getClass(dev), sel_getUid("isRemovable")))
182  VUserLog(" Removable : %s", objc_msgSend(dev, sel_getUid("isRemovable")) ? "yes" : "no");
183  VUserLog(" Headless : %s", objc_msgSend(dev, sel_getUid("isHeadless")) ? "yes" : "no");
184 
185  if (objc_msgSend(dev, sel_getUid("supportsFeatureSet:"), 10005))
186  VUserLog(" Feature set : GPU Family 2 v1");
187  else if (objc_msgSend(dev, sel_getUid("supportsFeatureSet:"), 10004))
188  VUserLog(" Feature set : GPU Family 1 v4");
189  else if (objc_msgSend(dev, sel_getUid("supportsFeatureSet:"), 10003))
190  VUserLog(" Feature set : GPU Family 1 v3");
191  else if (objc_msgSend(dev, sel_getUid("supportsFeatureSet:"), 10001))
192  VUserLog(" Feature set : GPU Family 1 v2");
193  else if (objc_msgSend(dev, sel_getUid("supportsFeatureSet:"), 10000))
194  VUserLog(" Feature set : GPU Family 1 v1");
195  else
196  VUserLog(" Feature set : (unknown)");
197 
198  if (class_respondsToSelector(object_getClass(dev), sel_getUid("readWriteTextureSupport")))
199  VUserLog(" Read-write texture support tier : %lld", (int64_t)objc_msgSend(dev, sel_getUid("readWriteTextureSupport")));
200  if (class_respondsToSelector(object_getClass(dev), sel_getUid("argumentBuffersSupport")))
201  VUserLog(" Argument buffer support tier : %lld", (int64_t)objc_msgSend(dev, sel_getUid("argumentBuffersSupport")));
202  if (class_respondsToSelector(object_getClass(dev), sel_getUid("maxArgumentBufferSamplerCount")))
203  VUserLog(" Max argument buffers : %lld", (int64_t)objc_msgSend(dev, sel_getUid("maxArgumentBufferSamplerCount")));
204  if (class_respondsToSelector(object_getClass(dev), sel_getUid("areProgrammableSamplePositionsSupported")))
205  VUserLog(" Programmable sample position support: %s", objc_msgSend(dev, sel_getUid("areProgrammableSamplePositionsSupported")) ? "yes" : "no");
206  if (class_respondsToSelector(object_getClass(dev), sel_getUid("areRasterOrderGroupsSupported")))
207  VUserLog(" Raster order group support : %s", objc_msgSend(dev, sel_getUid("areRasterOrderGroupsSupported")) ? "yes" : "no");
208  }
209  CFRelease(mtlDevices);
210  }
211 }
212 
216 void VuoGlContext_reconfig(CGDirectDisplayID display, CGDisplayChangeSummaryFlags flags, void *userInfo)
217 {
218  if (!(flags & kCGDisplaySetModeFlag))
219  return;
220 
221  VuoList_VuoScreen screensList = VuoScreen_getList();
222  unsigned long screenCount = VuoListGetCount_VuoScreen(screensList);
223  VuoScreen *screens = VuoListGetData_VuoScreen(screensList);
224  VuoLocal(screensList);
225 
226  for (unsigned long i = 0; i < screenCount; ++i)
227  if (screens[i].id == display)
228  VUserLog("Display reconfigured: %s", screens[i].name);
229 }
230 
234  static void VuoGlContext_init()
235  {
236  dispatch_once(&VuoGlContextPoolCreated, ^{
237  VuoGlContext_poolSemaphore = dispatch_semaphore_create(1);
238 
239  if (!VuoGlContext_root)
241 
242  int ret = pthread_key_create(&VuoGlContextPerformKey, NULL);
243  if (ret)
244  VUserLog("Couldn't create the key for storing the GL Context state: %s", strerror(errno));
245  });
246  }
247 
255  {
257 
258  CGLContextObj context;
259 
260  dispatch_semaphore_wait(VuoGlContext_poolSemaphore, DISPATCH_TIME_FOREVER);
261  {
262 // VL();
263 // VuoLog_backtrace();
264 
266  {
267  context = VuoGlContext_avaialbleSharedContexts.back();
269 // VLog("Found existing context %p.", context);
270  }
271  else
272  {
274  if (!context)
275  {
276  VUserLog("Error: Couldn't create a context.");
277  return NULL;
278  }
279  VuoGlContext_allSharedContexts.push_back(context);
280 // VLog("Created context %p.", context);
281  }
282  }
283  dispatch_semaphore_signal(VuoGlContext_poolSemaphore);
284 
285  return context;
286  }
287 
293  void VuoGlContext_disuseF(VuoGlContext glContext, const char *file, const unsigned int linenumber, const char *func)
294  {
295  CGLContextObj cgl_ctx = (CGLContextObj)glContext;
296 // VLog("%p", context);
297 
298 #if 0
299  VGL();
300 
301  // Check whether there are any stale bindings (to help identify leaks).
302  // (Some checks are commented because they aren't available in OpenGL 2.1.)
303  VuoGlCheckBinding(GL_ARRAY_BUFFER_BINDING);
304 // VuoGlCheckBinding(GL_ATOMIC_COUNTER_BUFFER_BINDING);
305 // VuoGlCheckBinding(GL_COPY_READ_BUFFER_BINDING);
306 // VuoGlCheckBinding(GL_COPY_WRITE_BUFFER_BINDING);
307 // VuoGlCheckBinding(GL_DRAW_INDIRECT_BUFFER_BINDING);
308 // VuoGlCheckBinding(GL_DISPATCH_INDIRECT_BUFFER_BINDING);
309  VuoGlCheckBinding(GL_DRAW_FRAMEBUFFER_BINDING);
310  VuoGlCheckBinding(GL_ELEMENT_ARRAY_BUFFER_BINDING);
311  VuoGlCheckBinding(GL_FRAMEBUFFER_BINDING);
312  VuoGlCheckBinding(GL_PIXEL_PACK_BUFFER_BINDING);
313  VuoGlCheckBinding(GL_PIXEL_UNPACK_BUFFER_BINDING);
314 // VuoGlCheckBinding(GL_PROGRAM_PIPELINE_BINDING);
315  VuoGlCheckBinding(GL_READ_FRAMEBUFFER_BINDING);
316  VuoGlCheckBinding(GL_RENDERBUFFER_BINDING);
317 // VuoGlCheckBinding(GL_SAMPLER_BINDING);
318 // VuoGlCheckBinding(GL_SHADER_STORAGE_BUFFER_BINDING);
319 
320  GLint textureUnits;
321  glGetIntegerv(GL_MAX_TEXTURE_UNITS, &textureUnits);
322  for (GLint i=0; i<textureUnits; ++i)
323  {
324  glActiveTexture(GL_TEXTURE0+i);
325  VuoGlCheckTextureBinding(GL_TEXTURE_BINDING_1D,i);
326  VuoGlCheckTextureBinding(GL_TEXTURE_BINDING_1D_ARRAY_EXT,i);
327  VuoGlCheckTextureBinding(GL_TEXTURE_BINDING_2D,i);
328  VuoGlCheckTextureBinding(GL_TEXTURE_BINDING_2D_ARRAY_EXT,i);
329 // VuoGlCheckTextureBinding(GL_TEXTURE_BINDING_2D_MULTISAMPLE,i);
330 // VuoGlCheckTextureBinding(GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY,i);
331  VuoGlCheckTextureBinding(GL_TEXTURE_BINDING_3D,i);
332 // VuoGlCheckTextureBinding(GL_TEXTURE_BINDING_BUFFER,i);
333  VuoGlCheckTextureBinding(GL_TEXTURE_BINDING_CUBE_MAP,i);
334  VuoGlCheckTextureBinding(GL_TEXTURE_BINDING_RECTANGLE_ARB,i);
335  }
336 
337  VuoGlCheckBinding(GL_TRANSFORM_FEEDBACK_BUFFER_BINDING_EXT);
338  VuoGlCheckBinding(GL_UNIFORM_BUFFER_BINDING_EXT);
339  VuoGlCheckBinding(GL_VERTEX_ARRAY_BINDING_APPLE);
340 #endif
341 
342  dispatch_semaphore_wait(VuoGlContext_poolSemaphore, DISPATCH_TIME_FOREVER);
343  {
345  VuoGlContext_avaialbleSharedContexts.push_back(cgl_ctx);
346  else
347  VUserLog("Error: Disued context %p, which isn't in the global share pool. I'm not going to muddy the waters.", cgl_ctx);
348  }
349  dispatch_semaphore_signal(VuoGlContext_poolSemaphore);
350  }
351 
355  #define VuoGlContext_checkGL(cap, value) \
356  do { \
357  if (glIsEnabled(cap) != value) \
358  { \
359  VUserLog("Warning: Caller incorrectly left %s %s", #cap, value ? "disabled" : "enabled"); \
360  VuoLog_backtrace(); \
361  } \
362  } while (0)
363 
367  #define VuoGlContext_checkGLInt(key, value) \
368  do { \
369  GLint actualValue; \
370  glGetIntegerv(key, &actualValue); \
371  if (actualValue != value) \
372  { \
373  VUserLog("Warning: Caller incorrectly left %s set to something other than %s", #key, #value); \
374  VuoLog_backtrace(); \
375  } \
376  } while (0)
377 
378 
379 
392  void VuoGlContext_perform(void (^function)(CGLContextObj cgl_ctx))
393  {
394  if (!function)
395  return;
396 
398 
399  bool alreadyLockedOnThisThread = (bool)pthread_getspecific(VuoGlContextPerformKey);
400 
401  if (!alreadyLockedOnThisThread)
402  {
403  pthread_setspecific(VuoGlContextPerformKey, (void *)true);
404  CGLLockContext(VuoGlContext_root);
405  }
406 
407  function(VuoGlContext_root);
408 
409  // Ensure that `function` restored the standard OpenGL state.
410  if (!alreadyLockedOnThisThread && VuoIsDebugEnabled())
411  {
412  CGLContextObj cgl_ctx = VuoGlContext_root;
413 
414  VGL();
415 
416  VuoGlContext_checkGLInt(GL_DEPTH_WRITEMASK, true);
417  VuoGlContext_checkGL (GL_DEPTH_TEST, false);
418 
419  VuoGlContext_checkGL (GL_CULL_FACE, true);
420  VuoGlContext_checkGLInt(GL_CULL_FACE_MODE, GL_BACK);
421 
422  VuoGlContext_checkGL (GL_BLEND, true);
423 
424  VuoGlContext_checkGLInt(GL_BLEND_SRC_RGB, GL_ONE);
425  VuoGlContext_checkGLInt(GL_BLEND_DST_RGB, GL_ONE_MINUS_SRC_ALPHA);
426  VuoGlContext_checkGLInt(GL_BLEND_SRC_ALPHA, GL_ONE);
427  VuoGlContext_checkGLInt(GL_BLEND_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
428 
429  VuoGlContext_checkGLInt(GL_BLEND_EQUATION_RGB, GL_FUNC_ADD);
430  VuoGlContext_checkGLInt(GL_BLEND_EQUATION_ALPHA, GL_FUNC_ADD);
431 
432  VuoGlContext_checkGL(GL_SAMPLE_ALPHA_TO_COVERAGE, false);
433  VuoGlContext_checkGL(GL_SAMPLE_ALPHA_TO_ONE, false);
434 
436  }
437 
438  if (!alreadyLockedOnThisThread)
439  {
440  CGLUnlockContext(VuoGlContext_root);
441  pthread_setspecific(VuoGlContextPerformKey, (void *)false);
442  }
443  }
444 
448  static CGLContextObj VuoGlContext_create(CGLContextObj rootContext)
449  {
450  static dispatch_once_t info = 0;
451  dispatch_once(&info, ^{
452  if (VuoIsDebugEnabled())
453  {
455 
456  CGDisplayRegisterReconfigurationCallback(VuoGlContext_reconfig, NULL);
457  }
458  });
459 
460  CGLPixelFormatObj pf;
461  bool shouldDestroyPixelFormat = false;
462  if (rootContext)
463  pf = CGLGetPixelFormat(rootContext);
464  else
465  {
466  Boolean overridden = false;
467  GLint displayMask = (int)CFPreferencesGetAppIntegerValue(CFSTR("displayMask"), CFSTR("org.vuo.Editor"), &overridden);
468  if (!overridden)
469  {
470  // Maybe the preference is a hex string…
471  auto displayMaskString = (CFStringRef)CFPreferencesCopyAppValue(CFSTR("displayMask"), CFSTR("org.vuo.Editor"));
472  if (displayMaskString)
473  {
474  VuoText t = VuoText_makeFromCFString(displayMaskString);
475  CFRelease(displayMaskString);
476  VuoLocal(t);
477  overridden = sscanf(t, "0x%x", &displayMask) == 1;
478  }
479  if (!overridden)
480  // Still no preference, so let macOS automatically choose what it thinks is the best GPU.
481  displayMask = -1;
482  }
483 
484  VDebugLog("displayMask = %s (0x%x)%s",
485  std::bitset<32>(displayMask).to_string().c_str(),
486  displayMask, (displayMask & 0xff) == 0xff ? " (any)" : "");
487 
488  pf = (CGLPixelFormatObj)VuoGlContext_makePlatformPixelFormat(true, false, displayMask);
489  shouldDestroyPixelFormat = true;
490  }
491 
492  CGLContextObj context;
493  {
494  CGLError error = CGLCreateContext(pf, rootContext, &context);
495  if (shouldDestroyPixelFormat)
496  CGLDestroyPixelFormat(pf);
497  if (error != kCGLNoError)
498  {
499  VUserLog("Error: %s", CGLErrorString(error));
500  return NULL;
501  }
502  }
503 
504  if (VuoIsDebugEnabled())
505  {
506  GLint rendererID;
507  CGLGetParameter(context, kCGLCPCurrentRendererID, &rendererID);
508  VUserLog("Created OpenGL context %p%s on %s",
509  context,
510  rootContext ? VuoText_format(" (shared with %p)", rootContext) : " (not shared)",
511  VuoCglRenderer_getText(rendererID));
512  }
513 
514  // https://developer.apple.com/library/content/technotes/tn2085/_index.html
515  // But it doesn't seem to actually improve performance any on the various workloads I tried.
516 // CGLEnable(context, kCGLCEMPEngine);
517 
518  // Set the context's default state to commonly-used values,
519  // to hopefully minimize subsequent state changes.
520  {
521  CGLContextObj cgl_ctx = context;
522  glDepthMask(true);
523  glDisable(GL_DEPTH_TEST);
524  glEnable(GL_CULL_FACE);
525  glEnable(GL_BLEND);
526  glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
527  glBlendEquation(GL_FUNC_ADD);
528  glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
529  glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, -.5);
530  }
531 
532  return context;
533  }
534 
545 void VuoGlContext_setGlobalRootContext(void *rootContext)
546 {
548  {
549  VUserLog("Error: Called after VuoGlContextPool was initialized. Ignoring the new rootContext.");
550  return;
551  }
552 
553  VDebugLog("Setting global root context to %p", rootContext);
554  VuoGlContext_root = VuoGlContext_create((CGLContextObj)rootContext);
555 }
556 
561 #define VuoGlCheckBinding(pname) \
562 { \
563  GLint value; \
564  glGetIntegerv(pname, &value); \
565  if (value) \
566  { \
567  VuoLog(file, linenumber, func, #pname " (value %d) was still active when the context was disused. (This may result in leaks.)", value); \
568  VuoLog_backtrace(); \
569  } \
570 }
571 
576 #define VuoGlCheckTextureBinding(pname, unit) \
577 { \
578  GLint value; \
579  glGetIntegerv(pname, &value); \
580  if (value) \
581  { \
582  VuoLog(file, linenumber, func, #pname " (texture %d on unit %d) was still active when the context was disused. (This may result in leaks.)", value, unit); \
583  VuoLog_backtrace(); \
584  } \
585 }
586 
593 {
594  static GLint supportedSamples = 0;
595  static dispatch_once_t multisamplingCheck = 0;
596  dispatch_once(&multisamplingCheck, ^{
597  GLint rendererID;
598  CGLGetParameter((CGLContextObj)context, kCGLCPCurrentRendererID, &rendererID);
599  rendererID &= kCGLRendererIDMatchingMask;
600 
601  CGLContextObj cgl_ctx = (CGLContextObj)context;
602  const char *renderer = (const char *)glGetString(GL_RENDERER);
603 
604  if (rendererID == kCGLRendererIntelHD4000ID // https://b33p.net/kosada/node/8225#comment-31324
605  || rendererID == /*kCGLRendererIntelHD5000ID*/ 0x00024500 // https://b33p.net/kosada/node/10595
606  || strcmp(renderer, "NVIDIA GeForce 320M OpenGL Engine") == 0) // https://b33p.net/kosada/node/13477
607  supportedSamples = 0;
608  else
609  {
610  glGetIntegerv(GL_MAX_SAMPLES, &supportedSamples);
611  if (supportedSamples == 1)
612  supportedSamples = 0;
613  }
614  });
615  return supportedSamples;
616 }
617 
631 void *VuoGlContext_makePlatformPixelFormat(bool hasDepthBuffer, bool openGL32Core, GLint displayMask)
632 {
633  // Check whether it's OK to use multisampling on this GPU.
634  static dispatch_once_t multisamplingCheck = 0;
635  static int multisample = 0;
636  dispatch_once(&multisamplingCheck, ^{
637  // Create a temporary context so we can get the GPU renderer string.
638  CGLContextObj cgl_ctx;
639  {
640  CGLPixelFormatObj pf;
641  {
642  CGLPixelFormatAttribute pfa[14] = {
643  kCGLPFAAccelerated,
644  kCGLPFAAllowOfflineRenderers,
645 // kCGLPFANoRecovery,
646 // kCGLPFADoubleBuffer,
647  kCGLPFABackingVolatile,
648  kCGLPFAColorSize, (CGLPixelFormatAttribute) 24,
649  kCGLPFADepthSize, (CGLPixelFormatAttribute) (hasDepthBuffer ? 16 : 0),
650  (CGLPixelFormatAttribute) 0
651  };
652  GLint npix;
653  CGLError error = CGLChoosePixelFormat(pfa, &pf, &npix);
654  if (error != kCGLNoError)
655  {
656  VUserLog("Error: %s", CGLErrorString(error));
657  return;
658  }
659  }
660 
661  CGLError error = CGLCreateContext(pf, NULL, &cgl_ctx);
662  CGLDestroyPixelFormat(pf);
663  if (error != kCGLNoError)
664  {
665  VUserLog("Error: %s", CGLErrorString(error));
666  return;
667  }
668  }
669 
670 
671  // If the user set the `multisample` preference, use it.
672  Boolean overridden = false;
673  multisample = (int)CFPreferencesGetAppIntegerValue(CFSTR("multisample"), CFSTR("org.vuo.Editor"), &overridden);
674 
675  if (!overridden)
676  {
677  // …otherwise enable 4x multisampling (unless there's a known problem with this GPU model).
678 
679  int supportedSamples = VuoGlContext_getMaximumSupportedMultisampling(cgl_ctx);
680  multisample = MIN(4, supportedSamples);
681  }
682 
683  CGLDestroyContext(cgl_ctx);
684  });
685 
686 
687  CGLPixelFormatAttribute pfa[18];
688  int pfaIndex = 0;
689 
690  // If requesting a specific display, don't require acceleration.
691  if (displayMask == -1)
692  pfa[pfaIndex++] = kCGLPFAAccelerated;
693 
694  pfa[pfaIndex++] = kCGLPFAAllowOfflineRenderers;
695 // pfa[pfaIndex++] = kCGLPFANoRecovery;
696 
697  // https://b33p.net/kosada/node/12525
698 // pfa[pfaIndex++] = kCGLPFADoubleBuffer;
699  pfa[pfaIndex++] = kCGLPFABackingVolatile;
700 
701  pfa[pfaIndex++] = kCGLPFAColorSize; pfa[pfaIndex++] = (CGLPixelFormatAttribute) 24;
702  pfa[pfaIndex++] = kCGLPFADepthSize; pfa[pfaIndex++] = (CGLPixelFormatAttribute) (hasDepthBuffer ? 16 : 0);
703  if (openGL32Core)
704  {
705  pfa[pfaIndex++] = kCGLPFAOpenGLProfile; pfa[pfaIndex++] = (CGLPixelFormatAttribute) kCGLOGLPVersion_3_2_Core;
706  }
707 
708  if (multisample)
709  {
710  pfa[pfaIndex++] = kCGLPFAMultisample;
711  pfa[pfaIndex++] = kCGLPFASampleBuffers; pfa[pfaIndex++] = (CGLPixelFormatAttribute) 1;
712  pfa[pfaIndex++] = kCGLPFASamples; pfa[pfaIndex++] = (CGLPixelFormatAttribute) multisample;
713  }
714 
715  if (displayMask >= 0)
716  {
717  pfa[pfaIndex++] = kCGLPFADisplayMask;
718  pfa[pfaIndex++] = (CGLPixelFormatAttribute) displayMask;
719  }
720 
721  // software renderer
722  if ((displayMask & 0xff) == 0xff && displayMask != -1)
723  {
724  pfa[pfaIndex++] = kCGLPFARendererID;
725  pfa[pfaIndex++] = (CGLPixelFormatAttribute) kCGLRendererGenericFloatID;
726  }
727 
728  pfa[pfaIndex] = (CGLPixelFormatAttribute) 0;
729 
730  CGLPixelFormatObj pf;
731  GLint npix;
732  CGLError error = CGLChoosePixelFormat(pfa, &pf, &npix);
733  if (error == kCGLBadDisplay)
734  return (CGLPixelFormatObj)-1;
735  else if (error != kCGLNoError)
736  {
737  VUserLog("Error: %s", CGLErrorString(error));
738  return NULL;
739  }
740 
741  return (void *)pf;
742 }
743 
749 {
750  CGLContextObj cgl_ctx = (CGLContextObj)context;
751 
752  // GL_VERSION is something like:
753  // - Core Profile: `4.1 NVIDIA-10.17.5 355.10.05.45f01`
754  // - Core Profile: `4.1 ATI-2.4.10`
755  // - Compatibility Profile: `2.1 NVIDIA-10.17.5 355.10.05.45f01`
756  // - Compatibility Profile: `2.1 ATI-2.4.10`
757  const unsigned char *contextVersion = glGetString(GL_VERSION);
758  return contextVersion[0] == '3'
759  || contextVersion[0] == '4';
760 }
761 
762 
763 void _VGL_describe(GLenum error, CGLContextObj cgl_ctx, const char *file, const unsigned int linenumber, const char *func);
764 
768 void _VGL(CGLContextObj cgl_ctx, const char *file, const unsigned int linenumber, const char *func)
769 {
770  GLint vertexOnGPU, fragmentOnGPU;
771  CGLGetParameter(cgl_ctx, kCGLCPGPUVertexProcessing, &vertexOnGPU);
772  if (!vertexOnGPU)
773  VuoLog(file, linenumber, func, "OpenGL warning: Falling back to software renderer for vertex shader. This will slow things down.");
774  CGLGetParameter(cgl_ctx, kCGLCPGPUFragmentProcessing, &fragmentOnGPU);
775  if (!fragmentOnGPU)
776  VuoLog(file, linenumber, func, "OpenGL warning: Falling back to software renderer for fragment shader. This will slow things down.");
777 
778  bool foundError = false;
779  do
780  {
781  GLenum error = glGetError();
782  if (error == GL_NO_ERROR)
783  break;
784  _VGL_describe(error, cgl_ctx, file, linenumber, func);
785  foundError = true;
786  } while (true);
787 
788  if (foundError)
790 }
791 
795 void _VGL_describe(GLenum error, CGLContextObj cgl_ctx, const char *file, const unsigned int linenumber, const char *func)
796 {
797  // Text from http://www.opengl.org/sdk/docs/man/xhtml/glGetError.xml
798  const char *errorString = "(unknown)";
799  if (error == GL_INVALID_ENUM)
800  errorString = "GL_INVALID_ENUM (An unacceptable value is specified for an enumerated argument. The offending command is ignored and has no other side effect than to set the error flag.)";
801  else if (error == GL_INVALID_VALUE)
802  errorString = "GL_INVALID_VALUE (A numeric argument is out of range. The offending command is ignored and has no other side effect than to set the error flag.)";
803  else if (error == GL_INVALID_OPERATION)
804  errorString = "GL_INVALID_OPERATION (The specified operation is not allowed in the current state. The offending command is ignored and has no other side effect than to set the error flag.)";
805  else if (error == GL_INVALID_FRAMEBUFFER_OPERATION)
806  {
807  errorString = "GL_INVALID_FRAMEBUFFER_OPERATION (The framebuffer object is not complete. The offending command is ignored and has no other side effect than to set the error flag.)";
808  VuoLog(file, linenumber, func, "OpenGL error %d: %s", error, errorString);
809 
810  GLenum framebufferError = glCheckFramebufferStatus(GL_FRAMEBUFFER);
811  // Text from http://www.khronos.org/opengles/sdk/docs/man/xhtml/glCheckFramebufferStatus.xml
812  const char *framebufferErrorString = "(unknown)";
813  if (framebufferError == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
814  framebufferErrorString = "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT (Not all framebuffer attachment points are framebuffer attachment complete. This means that at least one attachment point with a renderbuffer or texture attached has its attached object no longer in existence or has an attached image with a width or height of zero, or the color attachment point has a non-color-renderable image attached, or the depth attachment point has a non-depth-renderable image attached, or the stencil attachment point has a non-stencil-renderable image attached.)";
815 // else if (framebufferError == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS)
816 // framebufferErrorString = "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS (Not all attached images have the same width and height.)";
817  else if (framebufferError == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
818  framebufferErrorString = "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT (No images are attached to the framebuffer.)";
819  else if (framebufferError == GL_FRAMEBUFFER_UNSUPPORTED)
820  framebufferErrorString = "GL_FRAMEBUFFER_UNSUPPORTED (The combination of internal formats of the attached images violates an implementation-dependent set of restrictions.)";
821  else if (framebufferError == GL_FRAMEBUFFER_COMPLETE)
822  framebufferErrorString = "GL_FRAMEBUFFER_COMPLETE (?)";
823  else if (framebufferError == GL_FRAMEBUFFER_UNDEFINED)
824  framebufferErrorString = "GL_FRAMEBUFFER_UNDEFINED";
825  VuoLog(file, linenumber, func, "OpenGL framebuffer error %d: %s", framebufferError, framebufferErrorString);
826 
827  return;
828  }
829  else if (error == GL_OUT_OF_MEMORY)
830  errorString = "GL_OUT_OF_MEMORY (There is not enough memory left to execute the command. The state of the GL is undefined, except for the state of the error flags, after this error is recorded.)";
831  else if (error == GL_STACK_UNDERFLOW)
832  errorString = "GL_STACK_UNDERFLOW (An attempt has been made to perform an operation that would cause an internal stack to underflow.)";
833  else if (error == GL_STACK_OVERFLOW)
834  errorString = "GL_STACK_OVERFLOW (An attempt has been made to perform an operation that would cause an internal stack to overflow.)";
835 
836  VuoLog(file, linenumber, func, "OpenGL error %d: %s", error, errorString);
838 }
839 
840 typedef std::map<VuoGlContext, GLuint> VuoShaderContextType;
841