Vuo  2.1.0
VuoGlPool.cc
Go to the documentation of this file.
1 
10 #include "VuoGlPool.h"
11 extern "C" {
12 #include "module.h"
13 }
14 
15 #include "VuoGlContext.h"
16 #include "VuoEventLoop.h"
17 
18 #include <vector>
19 #include <utility>
20 #include <queue>
21 #include <set>
22 #include <deque>
23 #include <map>
24 #include <locale>
25 #include <sstream>
26 using namespace std;
27 
28 #include "VuoStringUtilities.hh"
29 #include "VuoShaderIssues.hh"
30 
31 #include <dispatch/dispatch.h>
32 
33 #include <CoreFoundation/CoreFoundation.h>
34 #include <IOSurface/IOSurface.h>
35 
36 #include <OpenGL/CGLIOSurface.h>
37 #include <OpenGL/CGLMacro.h>
38 #include <ApplicationServices/ApplicationServices.h>
39 
40 
41 static map<VuoGlPoolType, map<unsigned long, vector<GLuint> > > VuoGlPool __attribute__((init_priority(101)));
42 static dispatch_semaphore_t VuoGlPool_semaphore;
43 
54 GLuint VuoGlPool_use(VuoGlContext glContext, VuoGlPoolType type, unsigned long size)
55 {
56  GLuint name = 0;
57 
58  dispatch_semaphore_wait(VuoGlPool_semaphore, DISPATCH_TIME_FOREVER);
59  {
60  if (VuoGlPool[type][size].size())
61  {
62  name = VuoGlPool[type][size].back();
63  VuoGlPool[type][size].pop_back();
64 // VLog("using recycled type=%d name=%d",type,name);
65  }
66  else
67  {
68  CGLContextObj cgl_ctx = (CGLContextObj)glContext;
69 
70  if (type == VuoGlPool_ArrayBuffer || type == VuoGlPool_ElementArrayBuffer)
71  {
72  glGenBuffers(1, &name);
73  GLenum bufferType = type == VuoGlPool_ArrayBuffer ? GL_ARRAY_BUFFER : GL_ELEMENT_ARRAY_BUFFER;
74  glBindBuffer(bufferType, name);
75  glBufferData(bufferType, size, NULL, GL_STREAM_DRAW);
76  glBindBuffer(bufferType, 0);
78 // VLog("allocated type=%d name=%d",type,name);
79  }
80  else
81  VUserLog("Unknown pool type %d.", type);
82  }
83  }
84  dispatch_semaphore_signal(VuoGlPool_semaphore);
85 
86  return name;
87 }
88 
97 void VuoGlPool_disuse(VuoGlPoolType type, unsigned long size, GLuint name)
98 {
99  dispatch_semaphore_wait(VuoGlPool_semaphore, DISPATCH_TIME_FOREVER);
100  {
101  if (type == VuoGlPool_ArrayBuffer || type == VuoGlPool_ElementArrayBuffer)
102  VuoGlPool[type][size].push_back(name);
103  else
104  VUserLog("Unknown pool type %d.", type);
105  }
106  dispatch_semaphore_signal(VuoGlPool_semaphore);
107 }
108 
109 typedef map<GLuint, unsigned int> VuoGlPoolReferenceCounts;
110 static VuoGlPoolReferenceCounts VuoGlPool_referenceCounts __attribute__((init_priority(101)));
111 static dispatch_semaphore_t VuoGlPool_referenceCountsSemaphore = NULL;
112 static void __attribute__((constructor)) VuoGlPool_referenceCountsInit(void)
113 {
114  VuoGlPool_referenceCountsSemaphore = dispatch_semaphore_create(1);
115 }
116 
120 void VuoGlPool_retainF(GLuint glBufferName, const char *file, unsigned int linenumber, const char *func)
121 {
122  if (glBufferName == 0)
123  return;
124 
125  dispatch_semaphore_wait(VuoGlPool_referenceCountsSemaphore, DISPATCH_TIME_FOREVER);
126 
127  VuoGlPoolReferenceCounts::iterator it = VuoGlPool_referenceCounts.find(glBufferName);
128  if (it == VuoGlPool_referenceCounts.end())
129  VuoGlPool_referenceCounts[glBufferName] = 1;
130  else
131  ++VuoGlPool_referenceCounts[glBufferName];
132 
133  dispatch_semaphore_signal(VuoGlPool_referenceCountsSemaphore);
134 // VuoLog(file, linenumber, func, "VuoGlPool_retain(%d)", glBufferName);
135 }
136 
140 void VuoGlPool_releaseF(VuoGlPoolType type, unsigned long size, GLuint glBufferName, const char *file, unsigned int linenumber, const char *func)
141 {
142  if (glBufferName == 0)
143  return;
144 
145  dispatch_semaphore_wait(VuoGlPool_referenceCountsSemaphore, DISPATCH_TIME_FOREVER);
146 
147  VuoGlPoolReferenceCounts::iterator it = VuoGlPool_referenceCounts.find(glBufferName);
148  if (it == VuoGlPool_referenceCounts.end())
149  VuoLog(file, linenumber, func, "Error: VuoGlPool_release() was called with OpenGL Buffer Object %d, which was never retained.", glBufferName);
150  else
151  {
152  if (--VuoGlPool_referenceCounts[glBufferName] == 0)
153  VuoGlPool_disuse(type, size, glBufferName);
154  }
155 
156  dispatch_semaphore_signal(VuoGlPool_referenceCountsSemaphore);
157 // VuoLog(file, linenumber, func, "VuoGlPool_release(%d)", glBufferName);
158 }
159 
160 
161 
162 
163 
168 {
169 public:
170  GLenum target;
171  GLenum internalFormat;
172  unsigned short width;
173  unsigned short height;
174 
178  VuoGlTextureDescriptor(GLenum target, GLenum internalFormat, unsigned short width, unsigned short height)
179  : target(target),
180  internalFormat(internalFormat),
181  width(width),
182  height(height)
183  {
184  }
185 
189  bool operator<(const VuoGlTextureDescriptor &that) const
190  {
192  VuoType_returnInequality(VuoInteger, internalFormat, that.internalFormat);
195  return false;
196  }
197 
201  string toString() const
202  {
203  char *z;
204  asprintf(&z, "%-24s %-21s %5dx%-5d", VuoGl_stringForConstant(target), VuoGl_stringForConstant(internalFormat), width, height);
205  string s(z);
206  free(z);
207  return s;
208  }
209 };
210 typedef pair<queue<GLuint>,double> VuoGlTextureLastUsed;
211 typedef map<VuoGlTextureDescriptor, VuoGlTextureLastUsed> VuoGlTexturePoolType;
213 static dispatch_semaphore_t VuoGlTexturePool_semaphore;
214 
215 
219 GLuint VuoGlTexture_getType(GLuint format)
220 {
221  if (format == GL_YCBCR_422_APPLE)
222  return GL_UNSIGNED_SHORT_8_8_APPLE;
223  else if (format == GL_RGB
224  || format == GL_LUMINANCE
225  || format == GL_LUMINANCE_ALPHA
226  || format == GL_BGR_EXT)
227  return GL_UNSIGNED_BYTE;
228  else if (format == GL_RGBA
229  || format == GL_RGBA8
230  || format == GL_BGRA)
231  return GL_UNSIGNED_INT_8_8_8_8_REV;
232  else if (format == GL_DEPTH_COMPONENT)
233  // Don't use GL_UNSIGNED_SHORT or GL_UNSIGNED_INT; they cause glBlitFramebufferEXT to output garbage on some GPUs.
234  // https://b33p.net/kosada/node/11305
235  return GL_UNSIGNED_BYTE;
236 
237  char *formatString = VuoGl_stringForConstant(format);
238  VUserLog("Unknown format %s", formatString);
239  free(formatString);
241  return 0;
242 }
243 
247 unsigned char VuoGlTexture_getChannelCount(GLuint format)
248 {
249  if (format == GL_LUMINANCE
250  || format == GL_LUMINANCE8
251  || format == GL_LUMINANCE16F_ARB
252  || format == GL_LUMINANCE32F_ARB
253  || format == GL_DEPTH_COMPONENT)
254  return 1;
255  else if (format == GL_YCBCR_422_APPLE
256  || format == GL_LUMINANCE_ALPHA
257  || format == GL_LUMINANCE_ALPHA16F_ARB
258  || format == GL_LUMINANCE_ALPHA32F_ARB)
259  return 2;
260  else if (format == GL_RGB
261  || format == GL_RGB16F_ARB
262  || format == GL_RGB32F_ARB
263  || format == GL_BGR_EXT)
264  return 3;
265  else if (format == GL_RGBA
266  || format == GL_RGBA8
267  || format == GL_RGBA16F_ARB
268  || format == GL_RGBA32F_ARB
269  || format == GL_BGRA)
270  return 4;
271 
272  char *formatString = VuoGl_stringForConstant(format);
273  VUserLog("Unknown format %s", formatString);
274  free(formatString);
276  return 1;
277 }
278 
282 unsigned char VuoGlTexture_getBytesPerPixel(GLuint internalformat, GLuint format)
283 {
284  if (internalformat == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT)
285  return 1;
286  if (internalformat == GL_COMPRESSED_RGB_S3TC_DXT1_EXT
287  || internalformat == GL_COMPRESSED_RED_RGTC1)
288  return 1; // Actually 0.5
289 
290  unsigned char bytes = VuoGlTexture_getChannelCount(format);
291  if (internalformat == GL_RGB
292  || internalformat == GL_RGBA
293  || internalformat == GL_RGBA8
294  || internalformat == GL_LUMINANCE8
295  || internalformat == GL_LUMINANCE8_ALPHA8)
296  return bytes;
297  else if (internalformat == GL_RGB16F_ARB
298  || internalformat == GL_RGBA16F_ARB
299  || internalformat == GL_DEPTH_COMPONENT
300  || internalformat == GL_LUMINANCE16F_ARB
301  || internalformat == GL_LUMINANCE_ALPHA16F_ARB)
302  return bytes * 2;
303  else if (internalformat == GL_RGB32F_ARB
304  || internalformat == GL_RGBA32F_ARB
305  || internalformat == GL_LUMINANCE32F_ARB
306  || internalformat == GL_LUMINANCE_ALPHA32F_ARB)
307  return bytes * 4;
308 
309  VUserLog("Unknown internalformat %s", VuoGl_stringForConstant(internalformat));
311  return 0;
312 }
313 
318 unsigned char VuoGlTexture_getBytesPerPixelForInternalFormat(GLuint internalformat)
319 {
320  if (internalformat == GL_COMPRESSED_RGB_S3TC_DXT1_EXT
321  || internalformat == GL_COMPRESSED_RED_RGTC1)
322  return 1; // Actually 0.5
323  if (internalformat == GL_LUMINANCE8
324  || internalformat == GL_LUMINANCE
325  || internalformat == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT)
326  return 1;
327  if (internalformat == GL_LUMINANCE8_ALPHA8
328  || internalformat == GL_LUMINANCE16F_ARB
329  || internalformat == GL_DEPTH_COMPONENT)
330  return 2;
331  if (internalformat == GL_RGB
332  || internalformat == GL_BGR)
333  return 3;
334  if (internalformat == GL_RGBA
335  || internalformat == GL_RGBA8
336  || internalformat == GL_BGRA
337  || internalformat == GL_LUMINANCE_ALPHA16F_ARB
338  || internalformat == GL_LUMINANCE32F_ARB)
339  return 4;
340  if (internalformat == GL_RGB16F_ARB)
341  return 6;
342  if (internalformat == GL_RGBA16F_ARB
343  || internalformat == GL_LUMINANCE_ALPHA32F_ARB)
344  return 8;
345  if (internalformat == GL_RGB32F_ARB)
346  return 12;
347  if (internalformat == GL_RGBA32F_ARB)
348  return 16;
349 
350  char *formatString = VuoGl_stringForConstant(internalformat);
351  VUserLog("Unknown internalformat %s", formatString);
352  free(formatString);
354  return 0;
355 }
356 
362 {
363  if (format == GL_BGRA
364  || format == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
365  || format == GL_LUMINANCE8_ALPHA8
366  || format == GL_LUMINANCE_ALPHA
367  || format == GL_LUMINANCE_ALPHA16F_ARB
368  || format == GL_LUMINANCE_ALPHA32F_ARB
369  || format == GL_RGBA
370  || format == GL_RGBA16F_ARB
371  || format == GL_RGBA32F_ARB
372  || format == GL_RGBA8)
373  return true;
374 
375  return false;
376 }
377 
382 {
383  CGLContextObj cgl_ctx = (CGLContextObj)glContext;
384  static unsigned long maximumTextureBytes = 0;
385  static dispatch_once_t maximumTextureBytesQuery = 0;
386  dispatch_once(&maximumTextureBytesQuery, ^{
387  GLint contextRendererID;
388  CGLGetParameter(cgl_ctx, kCGLCPCurrentRendererID, &contextRendererID);
389 
390  // https://b33p.net/kosada/node/12177
391  // https://developer.apple.com/library/mac/qa/qa1168/_index.html says:
392  // "If you are looking for the VRAM sizes of all the renderers on your system […]
393  // you may specify a -1/0xFFFFFFFF display mask in the CGLQueryRendererInfo() function.
394  CGLRendererInfoObj ri;
395  GLint rendererCount = 0;
396  CGLQueryRendererInfo(-1, &ri, &rendererCount);
397  for (int i = 0; i < rendererCount; ++i)
398  {
399  GLint rendererID;
400  CGLDescribeRenderer(ri, i, kCGLRPRendererID, &rendererID);
401  if (rendererID == contextRendererID)
402  {
403  GLint textureMegabytes = 0;
404  if (CGLDescribeRenderer(ri, i, kCGLRPTextureMemoryMegabytes, &textureMegabytes) == kCGLNoError)
405  {
406  // In OS X, the GPU seems to often crash with individual textures that occupy more than a certain amount of memory.
407  // See https://b33p.net/kosada/node/10791
408  // See https://b33p.net/kosada/node/12030
409  double fudge = .9;
410  if ((rendererID & kCGLRendererIDMatchingMask) == kCGLRendererIntelHD4000ID)
411  // Reduce on Intel 4000, since on macOS 10.14, textures larger than this fail with GL_OUT_OF_MEMORY.
412  fudge = .74;
413 
414  maximumTextureBytes = (textureMegabytes - 85) * 1048576UL * fudge;
415  VDebugLog("%ld MB", maximumTextureBytes / 1024 / 1024);
416  break;
417  }
418  }
419  }
420  CGLDestroyRendererInfo(ri);
421  });
422  return maximumTextureBytes;
423 }
424 
429 {
430  static GLint maximumTextureDimension = 0;
431  static dispatch_once_t maximumTextureDimensionQuery = 0;
432  dispatch_once(&maximumTextureDimensionQuery, ^{
433  CGLContextObj cgl_ctx = (CGLContextObj)glContext;
434  glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maximumTextureDimension);
435  });
436  return maximumTextureDimension;
437 }
438 
439 #pragma clang diagnostic push
440 #pragma clang diagnostic ignored "-Wunused-function"
441 
445 {
446  return allocation == VuoGlTexturePool_NoAllocation ? "NoAlloc" :
447  (allocation == VuoGlTexturePool_Allocate ? "Alloc" :
448  allocation == VuoGlTexturePool_AllocateIOSurface ? "AllocIOSurf" : "?");
449 }
450 #pragma clang diagnostic pop
451 
476 GLuint VuoGlTexturePool_use(VuoGlContext glContext, VuoGlTexturePoolAllocation allocation, GLenum target, GLenum internalformat, unsigned short width, unsigned short height, GLenum format, void *ioSurfaceRef)
477 {
478  if (allocation == VuoGlTexturePool_AllocateIOSurface
479  && target != GL_TEXTURE_RECTANGLE_ARB)
480  {
481  VUserLog("Error: To use VuoGlTexturePool_AllocateIOSurface, target must be GL_TEXTURE_RECTANGLE_ARB.");
482  return 0;
483  }
484 
485  unsigned char bytesPerPixel = VuoGlTexture_getBytesPerPixel(internalformat, format);
486  unsigned long requiredBytes = width * height * bytesPerPixel;
487  VuoGlTextureDescriptor descriptor(target, internalformat, width, height);
488 // VLog(" %-11s %s: Requested",VuoGlTexturePool_stringForAllocation(allocation), descriptor.toString().c_str());
489 
490  GLuint name = 0;
491  if (allocation != VuoGlTexturePool_AllocateIOSurface)
492  {
493  dispatch_semaphore_wait(VuoGlTexturePool_semaphore, DISPATCH_TIME_FOREVER);
494  auto it = VuoGlTexturePool->find(descriptor);
495  if (it != VuoGlTexturePool->end() && it->second.first.size())
496  {
497  name = it->second.first.front();
498  it->second.first.pop();
499 // if (name) VLog(" %s: Used recycled texture %d", descriptor.toString().c_str(), name);
500  it->second.second = VuoLogGetTime();
501  }
502  dispatch_semaphore_signal(VuoGlTexturePool_semaphore);
503  }
504 
505 
506  CGLContextObj cgl_ctx = (CGLContextObj)glContext;
507 
508  if (name == 0)
509  {
510  // Check texture size against available Texture Video RAM (to avoid GPU crashes / kernel panics).
511  unsigned long maximumTextureBytes = VuoGlTexture_getMaximumTextureBytes(glContext);
512  if (maximumTextureBytes > 0)
513  {
514  if (requiredBytes > maximumTextureBytes)
515  {
517  VUserLog("Not enough graphics memory for a %dx%d (%d bytes/pixel) texture. Requires %lu MB, have %lu MB.", width, height, bytesPerPixel, requiredBytes/1024/1024, maximumTextureBytes/1024/1024);
518  return 0;
519  }
520  }
521 
522  // Check texture size against the GPU's allowed texture size.
523  GLint maxDimension = VuoGlTexture_getMaximumTextureDimension(glContext);
524  if (width > maxDimension || height > maxDimension)
525  {
527  VUserLog("This GPU doesn't support textures of size %dx%d (GPU's limit is %dx%d).", width, height, maxDimension, maxDimension);
528  return 0;
529  }
530 
531  glGenTextures(1, &name);
532 // VLog(" %-11s %s: Generated new texture %d", VuoGlTexturePool_stringForAllocation(allocation), descriptor.toString().c_str(), name);
533  glBindTexture(target, name);
534  if (allocation == VuoGlTexturePool_Allocate)
535  {
536  glTexImage2D(target, 0, internalformat, width, height, 0, format, VuoGlTexture_getType(format), NULL);
537 // VLog("glTexImage2D(%s, 0, %s, %d, %d, 0, %s, %s, NULL); -> %d", VuoGl_stringForConstant(target), VuoGl_stringForConstant(internalformat), width, height, VuoGl_stringForConstant(format), VuoGl_stringForConstant(VuoGlTexture_getType(format)), name);
538  }
539  else if (allocation == VuoGlTexturePool_AllocateIOSurface)
540  {
541  CGLError err = CGLTexImageIOSurface2D(cgl_ctx, GL_TEXTURE_RECTANGLE_ARB, internalformat, width, height, format, VuoGlTexture_getType(format), (IOSurfaceRef)ioSurfaceRef, 0);
542  if (err != kCGLNoError)
543  {
544  char *internalFormatString = VuoGl_stringForConstant(internalformat);
545  char *formatString = VuoGl_stringForConstant(format);
546  char *typeString = VuoGl_stringForConstant(VuoGlTexture_getType(format));
547  VUserLog("Error in CGLTexImageIOSurface2D(context, GL_TEXTURE_RECTANGLE_ARB, %s, %hu, %hu, %s, %s, iosurface, 0): %s",
548  internalFormatString, width, height, formatString, typeString, CGLErrorString(err));
549  free(internalFormatString);
550  free(formatString);
551  free(typeString);
552  VGL();
553  return false;
554  }
555  }
556  VuoGlPool_logVRAMAllocated(requiredBytes);
557  }
558  else
559  glBindTexture(target, name);
560 
561 
562  glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
563  glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
564 
565  glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
566  glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
567 
568  glBindTexture(target, 0);
569 
570  return name;
571 }
572 
583 void VuoGlTexturePool_disuse(VuoGlTexturePoolAllocation allocation, GLenum target, GLenum internalformat, unsigned short width, unsigned short height, GLuint name)
584 {
585  if (internalformat == 0)
586  {
587  VUserLog("Error: Can't recycle texture %d since we don't know its internalformat. Deleting.", name);
588  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
589  glDeleteTextures(1, &name);
590  });
591  VuoGlPool_logVRAMFreed(width * height * 1 /* unknown BPP */);
592  return;
593  }
594 
595  if (allocation == VuoGlTexturePool_AllocateIOSurface)
596  {
597  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
598  glDeleteTextures(1, &name);
599  });
600 // VuoGlTextureDescriptor descriptor(target, internalformat, width, height);
601 // VLog(" %s: Deleted texture %d (can't recycle IOSurface backing textures)", descriptor.toString().c_str(), name);
602  }
603  else
604  {
605  VuoGlTextureDescriptor descriptor(target, internalformat, width, height);
606  dispatch_semaphore_wait(VuoGlTexturePool_semaphore, DISPATCH_TIME_FOREVER);
607  {
608  auto it = &(*VuoGlTexturePool)[descriptor];
609  it->first.push(name);
610  it->second = VuoLogGetTime();
611  }
612  dispatch_semaphore_signal(VuoGlTexturePool_semaphore);
613 // VLog(" %s: Recycled texture %d", descriptor.toString().c_str(), name);
614  }
615 }
616 
617 
618 
619 
620 
624 typedef struct
625 {
626  unsigned int referenceCount;
627  VuoImage_freeCallback freeCallback;
628  void *freeCallbackContext;
629 } VuoGlTexture;
630 typedef map<GLuint, VuoGlTexture> VuoGlTextureReferenceCounts;
632 static dispatch_semaphore_t VuoGlTexture_referenceCountsSemaphore = NULL;
633 
636 static void __attribute__((constructor(101))) VuoGlTexture_init(void)
637 {
639  VuoGlTexture_referenceCountsSemaphore = dispatch_semaphore_create(1);
640 }
641 
647 void VuoGlTexture_retain(GLuint glTextureName, VuoImage_freeCallback freeCallback, void *freeCallbackContext)
648 {
649 // VLog("%d", glTextureName);
650  if (glTextureName == 0)
651  return;
652 
653  dispatch_semaphore_wait(VuoGlTexture_referenceCountsSemaphore, DISPATCH_TIME_FOREVER);
654 
655  VuoGlTextureReferenceCounts::iterator it = VuoGlTexture_referenceCounts->find(glTextureName);
656  if (it == VuoGlTexture_referenceCounts->end())
657  (*VuoGlTexture_referenceCounts)[glTextureName] = (VuoGlTexture){1, freeCallback, freeCallbackContext};
658  else
659  ++(*it).second.referenceCount;
660 
661  dispatch_semaphore_signal(VuoGlTexture_referenceCountsSemaphore);
662 }
663 
670 void VuoGlTexture_release(VuoGlTexturePoolAllocation allocation, GLuint glTextureTarget, GLenum internalformat, unsigned short width, unsigned short height, GLuint glTextureName)
671 {
672 // VLog(" %-11s %d (%s %s %dx%d)", VuoGlTexturePool_stringForAllocation(allocation), glTextureName, VuoGl_stringForConstant(glTextureTarget), VuoGl_stringForConstant(internalformat), width, height);
673  if (glTextureName == 0)
674  return;
675 
676  dispatch_semaphore_wait(VuoGlTexture_referenceCountsSemaphore, DISPATCH_TIME_FOREVER);
677 
678  VuoGlTextureReferenceCounts::iterator it = VuoGlTexture_referenceCounts->find(glTextureName);
679  if (it == VuoGlTexture_referenceCounts->end())
680  VUserLog("Error: VuoGlTexture_release() was called with OpenGL Texture Object %d, which was never retained.", glTextureName);
681  else
682  {
683  VuoGlTexture t = (*VuoGlTexture_referenceCounts)[glTextureName];
684  if (--t.referenceCount == 0)
685  {
686  if (t.freeCallback)
687  {
688  // Client-owned texture
689  struct _VuoImage i = (struct _VuoImage){glTextureName, internalformat, glTextureTarget, width, height, 1, t.freeCallbackContext, NULL, NULL, NULL};
690  t.freeCallback(&i);
691  }
692  else
693  {
694  // Vuo-owned texture
695  VuoGlTexturePool_disuse(allocation, glTextureTarget, internalformat, width, height, glTextureName);
696  }
697  VuoGlTexture_referenceCounts->erase(it);
698  }
699  else
700  (*VuoGlTexture_referenceCounts)[glTextureName] = t;
701  }
702 
703  dispatch_semaphore_signal(VuoGlTexture_referenceCountsSemaphore);
704 }
705 
715 void VuoGlTexture_disown(GLuint glTextureName)
716 {
717 // VLog("%d", glTextureName);
718  if (glTextureName == 0)
719  return;
720 
721  dispatch_semaphore_wait(VuoGlTexture_referenceCountsSemaphore, DISPATCH_TIME_FOREVER);
722 
723  VuoGlTextureReferenceCounts::iterator it = VuoGlTexture_referenceCounts->find(glTextureName);
724  if (it == VuoGlTexture_referenceCounts->end())
725  VUserLog("Error: VuoGlTexture_disown() was called with OpenGL Texture Object %d, which was never retained.", glTextureName);
726  else
727  {
728  VuoGlTexture t = (*VuoGlTexture_referenceCounts)[glTextureName];
729  if (t.referenceCount != 1)
730  VUserLog("Error: VuoGlTexture_disown() was called with OpenGL Texture Object %d, which has a reference count of %d (but it should be 1).", glTextureName, t.referenceCount);
731  else
732  VuoGlTexture_referenceCounts->erase(it);
733  }
734 
735  dispatch_semaphore_signal(VuoGlTexture_referenceCountsSemaphore);
736 }
737 
738 
739 
743 typedef struct
744 {
745  IOSurfaceRef ioSurface;
746  GLuint texture;
747  unsigned short pixelsWide;
748  unsigned short pixelsHigh;
749  double lastUsedTime;
751 typedef pair<unsigned short,unsigned short> VuoGlTextureDimensionsType;
752 typedef map<VuoGlTextureDimensionsType, deque<VuoIoSurfacePoolEntryType> > VuoIoSurfacePoolType;
755 static dispatch_semaphore_t VuoIoSurfacePool_semaphore;
756 static CFStringRef receiverFinishedWithIoSurfaceKey = CFSTR("VuoReceiverFinished");
757 
758 const double VuoGlPool_cleanupInterval = 0.1;
759 static dispatch_source_t VuoGlPool_timer;
760 static dispatch_semaphore_t VuoGlPool_canceledAndCompleted;
761 
762 static unsigned long VuoGlPool_allocatedBytes = 0;
763 static unsigned long VuoGlPool_allocatedBytesMax = 0;
764 
774 void VuoGlPool_logVRAMAllocated(unsigned long bytesAllocated)
775 {
776 // VLog("Alloc %lu", bytesAllocated);
777  __sync_add_and_fetch(&VuoGlPool_allocatedBytes, bytesAllocated);
778 
779  // Not thread-safe, but the worst that could happen is that,
780  // if multiple threads exceed the limit simultaneously,
781  // we might not get the largest of the new maximums.
784 }
785 
795 void VuoGlPool_logVRAMFreed(unsigned long bytesFreed)
796 {
797 // VLog("Freed %lu", bytesFreed);
798  __sync_sub_and_fetch(&VuoGlPool_allocatedBytes, bytesFreed);
799 }
800 
804 static void VuoGlPool_cleanup(void *blah)
805 {
806 // VLog("{");
807  unsigned long totalTextureCount = 0;
808  static unsigned long totalTextureCountMax = 0;
809 
810  vector<GLuint> texturesToDelete;
811  vector<VuoIoSurfacePoolEntryType> iosurfaceTexturesToDisuse;
812 
813  dispatch_semaphore_wait(VuoGlTexturePool_semaphore, DISPATCH_TIME_FOREVER);
814  {
815  double now = VuoLogGetTime();
816  for (VuoGlTexturePoolType::iterator poolItem = VuoGlTexturePool->begin(); poolItem != VuoGlTexturePool->end(); )
817  {
818  unsigned long textureCount = poolItem->second.first.size();
819  double timeSinceLastUsed = now - poolItem->second.second;
820 // VLog(" %s: %lu unused texture%s (last used %.02gs ago)", poolItem->first.toString().c_str(), textureCount, textureCount == 1 ? "" : "s", timeSinceLastUsed);
821  if (timeSinceLastUsed > VuoGlPool_cleanupInterval)
822  {
823  if (textureCount)
824  {
825 // VLog(" Purging %lu expired texture%s", textureCount, textureCount == 1 ? "" : "s");
826  while (!poolItem->second.first.empty())
827  {
828  GLuint textureName = poolItem->second.first.front();
829  poolItem->second.first.pop();
830  texturesToDelete.push_back(textureName);
831  VuoGlPool_logVRAMFreed(poolItem->first.width * poolItem->first.height * VuoGlTexture_getBytesPerPixelForInternalFormat(poolItem->first.internalFormat));
832  }
833  }
834 
835  VuoGlTexturePool->erase(poolItem++);
836  }
837  else
838  {
839  totalTextureCount += textureCount;
840  ++poolItem;
841  }
842  }
843  }
844  dispatch_semaphore_signal(VuoGlTexturePool_semaphore);
845 
846  if (totalTextureCount > totalTextureCountMax)
847  totalTextureCountMax = totalTextureCount;
848 
849 
850  unsigned long totalIOSurfaceCount = 0;
851  static unsigned long totalIOSurfaceCountMax = 0;
852 
853  dispatch_semaphore_wait(VuoIoSurfacePool_semaphore, DISPATCH_TIME_FOREVER);
854  {
855  // Promote IOSurfaces from the quarantine to the pool, if the receiver has finished using them.
856  for (VuoIoSurfacePoolType::iterator quarantinedQueue = VuoIoSurfaceQuarantine->begin(); quarantinedQueue != VuoIoSurfaceQuarantine->end(); ++quarantinedQueue)
857  {
858  for (deque<VuoIoSurfacePoolEntryType>::iterator quarantinedIoSurfaceEntry = quarantinedQueue->second.begin(); quarantinedIoSurfaceEntry != quarantinedQueue->second.end();)
859  {
860  VuoIoSurfacePoolEntryType e = *quarantinedIoSurfaceEntry;
861  CFBooleanRef finished = (CFBooleanRef)IOSurfaceCopyValue(e.ioSurface, receiverFinishedWithIoSurfaceKey);
862  if (finished)
863  {
864  IOSurfaceRemoveValue(e.ioSurface, receiverFinishedWithIoSurfaceKey);
865 
866  (*VuoIoSurfacePool)[quarantinedQueue->first].push_back(e);
867  quarantinedIoSurfaceEntry = quarantinedQueue->second.erase(quarantinedIoSurfaceEntry);
868 
869 // VLog(" Promoted IOSurface %d + GL Texture %d (%dx%d) from quarantine to pool", IOSurfaceGetID(e.ioSurface), e.texture, quarantinedQueue->first.first, quarantinedQueue->first.second);
870  }
871  else
872  {
873  ++totalIOSurfaceCount;
874  ++quarantinedIoSurfaceEntry;
875  }
876  }
877  }
878 
879 
880  double now = VuoLogGetTime();
881  for (VuoIoSurfacePoolType::iterator poolQueue = VuoIoSurfacePool->begin(); poolQueue != VuoIoSurfacePool->end(); ++poolQueue)
882  {
883  for (deque<VuoIoSurfacePoolEntryType>::iterator poolIoSurfaceEntry = poolQueue->second.begin(); poolIoSurfaceEntry != poolQueue->second.end();)
884  {
885  VuoIoSurfacePoolEntryType e = *poolIoSurfaceEntry;
886  if (now - e.lastUsedTime > VuoGlPool_cleanupInterval * 2.)
887  {
888 // VLog(" Purging expired IOSurface %d + GL Texture %d (%dx%d) — it's %gs old", IOSurfaceGetID(e.ioSurface), e.texture, poolQueue->first.first, poolQueue->first.second, now - e.lastUsedTime);
889 
890  CFRelease(e.ioSurface);
891  iosurfaceTexturesToDisuse.push_back(e);
892  poolIoSurfaceEntry = poolQueue->second.erase(poolIoSurfaceEntry);
893  }
894  else
895  {
896  ++totalIOSurfaceCount;
897  ++poolIoSurfaceEntry;
898  }
899  }
900  }
901  }
902  dispatch_semaphore_signal(VuoIoSurfacePool_semaphore);
903 
904  if (totalIOSurfaceCount > totalIOSurfaceCountMax)
905  totalIOSurfaceCountMax = totalIOSurfaceCount;
906 
907 
908  // Delete textures after we've released the pool semaphores, to avoid deadlock, and to avoid hogging the shared GL context.
909  if (texturesToDelete.size())
910  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
911  for (vector<GLuint>::const_iterator i = texturesToDelete.begin(); i != texturesToDelete.end(); ++i)
912  {
913  GLuint t = *i;
914  glDeleteTextures(1, &t);
915  // VuoGlPool_logVRAMFreed was called above when texturesToDelete was being built.
916  }
917  });
918  for (auto e : iosurfaceTexturesToDisuse)
919  VuoGlTexturePool_disuse(VuoGlTexturePool_AllocateIOSurface, GL_TEXTURE_RECTANGLE_ARB, GL_RGBA, e.pixelsWide, e.pixelsHigh, e.texture);
920 // VLog("}");
921 
922 
923  // Log VRAM use every 10 seconds.
924  static unsigned long cleanupCount = 0;
925  if ((++cleanupCount % (long)(10. / VuoGlPool_cleanupInterval) == 0)
926  && VuoIsDebugEnabled()
928  || totalTextureCount > 0 || totalTextureCountMax > 0
929  || totalIOSurfaceCount > 0 || totalIOSurfaceCountMax > 0))
930  {
931  __block unsigned long maximumTextureBytes;
932  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
933  maximumTextureBytes = VuoGlTexture_getMaximumTextureBytes(cgl_ctx);
934  });
935 
936  if (maximumTextureBytes > 0)
937  VUserLog("VRAM used: %5lu MB (%5lu MB max, %3lu%%). Textures in pool: %3lu (%3lu max). IOSurfaces in pool: %3lu (%3lu max).",
938  VuoGlPool_allocatedBytes/1024/1024,
939  VuoGlPool_allocatedBytesMax/1024/1024,
940  VuoGlPool_allocatedBytesMax*100/maximumTextureBytes,
941  totalTextureCount, totalTextureCountMax,
942  totalIOSurfaceCount, totalIOSurfaceCountMax);
943  else
944  VUserLog("VRAM used: %5lu MB (%5lu MB max). Textures in pool: %3lu (%3lu max). IOSurfaces in pool: %3lu (%3lu max).",
945  VuoGlPool_allocatedBytes/1024/1024,
946  VuoGlPool_allocatedBytesMax/1024/1024,
947  totalTextureCount, totalTextureCountMax,
948  totalIOSurfaceCount, totalIOSurfaceCountMax);
949  }
950 }
951 static void __attribute__((constructor)) VuoGlPool_init(void)
952 {
953  VuoGlPool_semaphore = dispatch_semaphore_create(1);
954 
955  VuoGlTexturePool_semaphore = dispatch_semaphore_create(1);
957 
958  VuoIoSurfacePool_semaphore = dispatch_semaphore_create(1);
961 
962  VuoGlPool_canceledAndCompleted = dispatch_semaphore_create(0);
963  VuoGlPool_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
964  dispatch_source_set_timer(VuoGlPool_timer, dispatch_walltime(NULL,0), NSEC_PER_SEC * VuoGlPool_cleanupInterval, NSEC_PER_SEC * VuoGlPool_cleanupInterval);
965  dispatch_source_set_event_handler_f(VuoGlPool_timer, VuoGlPool_cleanup);
966  dispatch_source_set_cancel_handler(VuoGlPool_timer, ^{
967  dispatch_semaphore_signal(VuoGlPool_canceledAndCompleted);
968  });
969  dispatch_resume(VuoGlPool_timer);
970 }
974 static void __attribute__((destructor)) VuoGlPool_fini(void)
975 {
976  dispatch_source_cancel(VuoGlPool_timer);
977 
978  // Wait for the last cleanup to complete.
979  dispatch_semaphore_wait(VuoGlPool_canceledAndCompleted, DISPATCH_TIME_FOREVER);
980 
981  delete VuoGlTexturePool;
982 
983  delete VuoIoSurfacePool;
984  delete VuoIoSurfaceQuarantine;
985 }
986 
991 VuoIoSurface VuoIoSurfacePool_use(VuoGlContext glContext, unsigned short pixelsWide, unsigned short pixelsHigh, GLuint *outputTexture)
992 {
993  VuoIoSurface ioSurface = NULL;
994  VuoGlTextureDimensionsType dimensions(pixelsWide,pixelsHigh);
995 
996  dispatch_semaphore_wait(VuoIoSurfacePool_semaphore, DISPATCH_TIME_FOREVER);
997  {
998  if ((*VuoIoSurfacePool)[dimensions].size())
999  {
1000  VuoIoSurfacePoolEntryType e = (*VuoIoSurfacePool)[dimensions].front();
1001  ioSurface = malloc(sizeof(VuoIoSurfacePoolEntryType));
1002  memcpy(ioSurface, &e, sizeof(VuoIoSurfacePoolEntryType));
1003  *outputTexture = e.texture;
1004  (*VuoIoSurfacePool)[dimensions].pop_front();
1005 // VLog(" Using recycled IOSurface %d + GL Texture %d (%dx%d) from pool", IOSurfaceGetID(e.ioSurface), *outputTexture, pixelsWide, pixelsHigh);
1006  }
1007  }
1008  dispatch_semaphore_signal(VuoIoSurfacePool_semaphore);
1009 
1010  if (!ioSurface)
1011  {
1012  CFMutableDictionaryRef properties = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL);
1013 
1016  CFDictionaryAddValue(properties, kIOSurfaceIsGlobal, kCFBooleanTrue);
1017 
1018  long long pixelsWideLL = pixelsWide;
1019  CFNumberRef pixelsWideCF = CFNumberCreate(NULL, kCFNumberLongLongType, &pixelsWideLL);
1020  CFDictionaryAddValue(properties, kIOSurfaceWidth, pixelsWideCF);
1021 
1022  long long pixelsHighLL = pixelsHigh;
1023  CFNumberRef pixelsHighCF = CFNumberCreate(NULL, kCFNumberLongLongType, &pixelsHighLL);
1024  CFDictionaryAddValue(properties, kIOSurfaceHeight, pixelsHighCF);
1025 
1026  long long bytesPerElement = 4;
1027  CFNumberRef bytesPerElementCF = CFNumberCreate(NULL, kCFNumberLongLongType, &bytesPerElement);
1028  CFDictionaryAddValue(properties, kIOSurfaceBytesPerElement, bytesPerElementCF);
1029 
1030  CGLContextObj cgl_ctx = (CGLContextObj)glContext;
1031 
1033  ioSurface = e;
1034  e->ioSurface = IOSurfaceCreate(properties);
1035  e->pixelsWide = pixelsWide;
1036  e->pixelsHigh = pixelsHigh;
1037  e->lastUsedTime = -1;
1038  CFRelease(pixelsWideCF);
1039  CFRelease(pixelsHighCF);
1040  CFRelease(bytesPerElementCF);
1041  CFRelease(properties);
1042 
1043  *outputTexture = VuoGlTexturePool_use(cgl_ctx, VuoGlTexturePool_AllocateIOSurface, GL_TEXTURE_RECTANGLE_ARB, GL_RGBA, pixelsWide, pixelsHigh, GL_BGRA, e->ioSurface);
1044  e->texture = *outputTexture;
1045 
1046 // VLog(" Created IOSurface %d (%dx%d)", IOSurfaceGetID(e->ioSurface), pixelsWide, pixelsHigh);
1047  }
1048 
1049  return ioSurface;
1050 }
1051 
1056 {
1058  return IOSurfaceGetID(e->ioSurface);
1059 }
1060 
1065 {
1067  return e->ioSurface;
1068 }
1069 
1074 {
1076  return e->pixelsWide;
1077 }
1078 
1083 {
1085  return e->pixelsHigh;
1086 }
1087 
1092 {
1094  return e->texture;
1095 }
1096 
1109 void VuoIoSurfacePool_disuse(VuoIoSurface vis, bool quarantine)
1110 {
1112 // VLog(" Sender disusing IOSurface %d + GL Texture %d (%dx%d)", IOSurfaceGetID(e->ioSurface), e->texture, e->pixelsWide, e->pixelsHigh);
1113  VuoGlTextureDimensionsType dimensions(e->pixelsWide, e->pixelsHigh);
1114  e->lastUsedTime = VuoLogGetTime();
1115 
1116  dispatch_semaphore_wait(VuoIoSurfacePool_semaphore, DISPATCH_TIME_FOREVER);
1117  {
1118  if (quarantine)
1119  (*VuoIoSurfaceQuarantine)[dimensions].push_back(*e);
1120  else
1121  (*VuoIoSurfacePool)[dimensions].push_back(*e);
1122  }
1123  dispatch_semaphore_signal(VuoIoSurfacePool_semaphore);
1124 
1125  free(e);
1126 }
1127 
1133 {
1134  IOSurfaceRef ioSurface = (IOSurfaceRef)ios;
1135  IOSurfaceSetValue(ioSurface, receiverFinishedWithIoSurfaceKey, kCFBooleanTrue);
1136 }
1137 
1138 
1139 
1145 void VuoGlShader_parseShaderInfoLog(CGLContextObj cgl_ctx, GLenum type, GLuint obj, const GLchar *source, VuoShaderFile::Stage stage, VuoShaderIssues *outIssues)
1146 {
1147  int infologLength = 0;
1148  glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infologLength);
1149  if (infologLength > 0)
1150  {
1151  char *infoLog = (char *)malloc(infologLength);
1152  int charsWritten = 0;
1153  glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
1154 
1155  if (outIssues)
1156  {
1157  istringstream iss(infoLog);
1158  string ln;
1159  while (getline(iss, ln))
1160  {
1161  int lineNumber = 0;
1162  char *message = (char *)malloc(ln.length() + 1);
1163  char *quotation = (char *)malloc(ln.length() + 1);
1164  if (sscanf(ln.c_str(), "ERROR: 0:%d: '' : %[^\n]", &lineNumber, message) == 2)
1165  outIssues->addIssue(stage, lineNumber, message);
1166  else if (sscanf(ln.c_str(), "ERROR: 0:%d: '%[^']' : syntax error: %[^\n]", &lineNumber, quotation, message) == 3)
1167  outIssues->addIssue(stage, lineNumber, message + string(": '") + quotation + "'");
1168  else if (sscanf(ln.c_str(), "ERROR: 0:%d: %[^\n]", &lineNumber, message) == 2)
1169  outIssues->addIssue(stage, lineNumber, message);
1170  else if (sscanf(ln.c_str(), "WARNING: 0:%d: %[^\n]", &lineNumber, message) == 2)
1171  outIssues->addIssue(stage, lineNumber, message);
1172  else
1173  {
1174  VUserLog("Warning: Couldn't parse GLSL log message: \"%s\"", ln.c_str());
1175  outIssues->addIssue(stage, VuoShaderIssues::NoLine, ln);
1176  }
1177  free(message);
1178  free(quotation);
1179  }
1180  }
1181  else
1182  {
1183  VUserLog("%s", infoLog);
1184  VDebugLog("Source code:\n%s", source);
1185  }
1186 
1187  free(infoLog);
1188  }
1189 }
1190 
1191 map<GLenum, map<long, GLuint> > VuoGlShaderPool __attribute__((init_priority(101)));
1192 dispatch_semaphore_t VuoGlShaderPool_semaphore = NULL;
1193 
1197 __attribute__((constructor)) static void VuoGlShaderPool_init(void)
1198 {
1199  VuoGlShaderPool_semaphore = dispatch_semaphore_create(1);
1200 }
1201 
1207 static bool VuoGlShader_resolveIncludes(string &source, VuoShaderFile::Stage stage, VuoShaderIssues *outIssues)
1208 {
1209  const char *cwd = VuoGetWorkingDirectory();
1210  const string vuoFrameworkPath = VuoFileUtilities::getVuoFrameworkPath();
1211  const string vuoRunnerFrameworkPath = VuoFileUtilities::getVuoRunnerFrameworkPath();
1212  string userModulesPath = VuoFileUtilities::getUserModulesPath();
1213  if (!VuoFileUtilities::dirExists(userModulesPath))
1214  userModulesPath = "";
1215  string systemModulesPath = VuoFileUtilities::getSystemModulesPath();
1216  if (!VuoFileUtilities::dirExists(systemModulesPath))
1217  systemModulesPath = "";
1218 
1219  set<string> includedFiles;
1220 
1221  while (1)
1222  {
1223  const string includeKeyword = "#include";
1224 
1225  size_t includeStart = source.find(includeKeyword);
1226  if (includeStart == string::npos)
1227  return true;
1228 
1229  size_t offset = includeStart + includeKeyword.length();
1230 
1231  while (source[offset] == ' ')
1232  ++offset;
1233 
1234  if (source[offset] != '"'
1235  && source[offset] != '<')
1236  {
1238  if (outIssues)
1239  outIssues->addIssue(stage, VuoShaderIssues::NoLine, "Syntax error in #include statement. Expected syntax: #include \"filename.glsl\"");
1240  else
1241  VUserLog("Error: Syntax error in #include statement. Expected syntax: #include \"filename.glsl\"");
1242  return false;
1243  }
1244  ++offset;
1245 
1246  size_t filenameStart = offset;
1247 
1248  while (source[offset] != '"'
1249  && source[offset] != '>')
1250  ++offset;
1251 
1252  size_t filenameEnd = offset;
1253 
1254  string filename = source.substr(filenameStart, filenameEnd - filenameStart);
1255 
1256  vector<string> pathsToTest;
1257  if (strcmp(cwd, "/") != 0)
1258  pathsToTest.push_back(cwd + string("/") + filename);
1259  if (!vuoFrameworkPath.empty())
1260  pathsToTest.push_back(vuoFrameworkPath + "/Resources/shaders/" + filename);
1261  if (!vuoRunnerFrameworkPath.empty())
1262  pathsToTest.push_back(vuoRunnerFrameworkPath + "/Resources/shaders/" + filename);
1263  if (!userModulesPath.empty())
1264  pathsToTest.push_back(userModulesPath + "/" + filename);
1265  if (!systemModulesPath.empty())
1266  pathsToTest.push_back(systemModulesPath + "/" + filename);
1267  bool found = false;
1268  for (vector<string>::iterator i = pathsToTest.begin(); i != pathsToTest.end(); ++i)
1269  {
1271  {
1272  found = true;
1273 
1274  source.erase(includeStart, offset - includeStart + 1);
1275 
1276  // Prevent recursive includes.
1277  if (includedFiles.count(*i))
1278  {
1279  source.insert(includeStart,
1280  "\n"
1281  "// ============================================================\n"
1282  "// Skipped including file \"" + *i + "\" since it was already included.\n"
1283  "// ============================================================\n"
1284  "\n"
1285  );
1286  break;
1287  }
1288  includedFiles.insert(*i);
1289 
1290  source.insert(includeStart,
1291  "\n"
1292  "// ============================================================\n"
1293  "// Begin included file \"" + *i + "\"\n"
1295  + "\n"
1296  "// End included file \"" + *i + "\"\n"
1297  "// ============================================================\n"
1298  "\n"
1299  );
1300  break;
1301  }
1302  }
1303 
1304  if (!found)
1305  {
1307  if (outIssues)
1308  outIssues->addIssue(stage, VuoShaderIssues::NoLine, "Couldn't find include file \"" + filename + "\"");
1309  else
1310  VUserLog("Error: Couldn't find include file \"%s\".", filename.c_str());
1311  return false;
1312  }
1313  }
1314 }
1315 
1316 static const std::locale VuoGlPool_locale;
1317 static const std::collate<char> &VuoGlPool_collate = std::use_facet<std::collate<char> >(VuoGlPool_locale);
1318 
1336 GLuint VuoGlShader_use(VuoGlContext glContext, GLenum type, const char *source, void *outIssues)
1337 {
1338  long hash = VuoGlPool_collate.hash(source, source+strlen(source));
1339 
1340  dispatch_semaphore_wait(VuoGlShaderPool_semaphore, DISPATCH_TIME_FOREVER);
1341 
1342  GLuint shader;
1343  if (!outIssues && VuoGlShaderPool[type].find(hash) != VuoGlShaderPool[type].end())
1344  shader = VuoGlShaderPool[type][hash];
1345  else
1346  {
1347  CGLContextObj cgl_ctx = (CGLContextObj)glContext;
1348 
1349  string combinedSource = source;
1350 
1351  VuoShaderIssues *oi = static_cast<VuoShaderIssues *>(outIssues);
1352  VuoShaderFile::Stage stage;
1353  if (type == GL_VERTEX_SHADER)
1354  stage = VuoShaderFile::Vertex;
1355  else if (type == GL_GEOMETRY_SHADER_EXT)
1356  stage = VuoShaderFile::Geometry;
1357  else // if (type == GL_FRAGMENT_SHADER)
1358  stage = VuoShaderFile::Fragment;
1359 
1360  if (!VuoGlShader_resolveIncludes(combinedSource, stage, oi))
1361  {
1362  shader = 0;
1363  goto done;
1364  }
1365 
1366 // VLog("\n%s\n\n", combinedSource.c_str());
1367 
1368  shader = glCreateShader(type);
1369  GLint length = combinedSource.length();
1370  const GLchar *combinedSourceCString = combinedSource.c_str();
1371  glShaderSource(shader, 1, (const GLchar**)&combinedSourceCString, &length);
1372  glCompileShader(shader);
1373  VuoGlShader_parseShaderInfoLog(cgl_ctx, type, shader, combinedSourceCString, stage, oi);
1374 
1375  GLint status = GL_FALSE;
1376  glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
1377  if (status == GL_FALSE)
1378  {
1379  glDeleteShader(shader);
1380  shader = 0;
1381  goto done;
1382  }
1383 
1384  VuoGlShaderPool[type][hash] = shader;
1385  }
1386 
1387 done:
1388  dispatch_semaphore_signal(VuoGlShaderPool_semaphore);
1389 
1390  return shader;
1391 }
1392 
1393 // Too bad we can't use std::tuple yet…
1394 typedef pair<GLuint, pair<GLuint, pair<GLuint, pair<VuoMesh_ElementAssemblyMethod, unsigned int> > > > VuoGlProgramDescriptorType;
1395 typedef map<VuoGlProgramDescriptorType, VuoGlProgram> VuoGlProgramPoolType;
1397 static dispatch_semaphore_t VuoGlProgramPool_semaphore;
1398 static void __attribute__((constructor)) VuoGlProgramPool_init(void)
1399 {
1400  VuoGlProgramPool_semaphore = dispatch_semaphore_create(1);
1401 }
1402 
1403 typedef std::map<long, GLuint> VuoGlUniformMap;
1404 
1422 VuoGlProgram VuoGlProgram_use(VuoGlContext glContext, const char *description, GLuint vertexShaderName, GLuint geometryShaderName, GLuint fragmentShaderName, VuoMesh_ElementAssemblyMethod assemblyMethod, unsigned int expectedOutputPrimitiveCount, void *outIssues)
1423 {
1424 // VLog("looking for %d %d %d %d %d", vertexShaderName, geometryShaderName, fragmentShaderName, assemblyMethod, expectedOutputPrimitiveCount);
1425  VuoGlProgram program;
1426  VuoGlProgramDescriptorType e(vertexShaderName, std::make_pair(geometryShaderName, std::make_pair(fragmentShaderName, std::make_pair(assemblyMethod, expectedOutputPrimitiveCount))));
1427  dispatch_semaphore_wait(VuoGlProgramPool_semaphore, DISPATCH_TIME_FOREVER);
1428  VuoGlProgramPoolType::iterator it = VuoGlProgramPool.find(e);
1429  if (!outIssues && it != VuoGlProgramPool.end())
1430  program = it->second;
1431  else
1432  {
1433  CGLContextObj cgl_ctx = (CGLContextObj)glContext;
1434 
1435  GLuint programName = glCreateProgram();
1436  glAttachShader(programName, vertexShaderName);
1437  if (geometryShaderName)
1438  glAttachShader(programName, geometryShaderName);
1439  if (fragmentShaderName)
1440  glAttachShader(programName, fragmentShaderName);
1441 
1442  bool transformFeedback = false;
1443  // If there's no fragment shader, this program is being used for transform feedback.
1444  if (!fragmentShaderName)
1445  transformFeedback = true;
1446 
1447  // Make sure `position` is at location 0, since location 0 is required in order for glDraw*() to work.
1448  glBindAttribLocation(programName, 0, "position");
1449 
1450  if (geometryShaderName)
1451  {
1452  GLuint inputPrimitiveGlMode = GL_TRIANGLES;
1453  if (assemblyMethod == VuoMesh_IndividualLines
1454  || assemblyMethod == VuoMesh_LineStrip)
1455  inputPrimitiveGlMode = GL_LINES;
1456  else if (assemblyMethod == VuoMesh_Points)
1457  inputPrimitiveGlMode = GL_POINTS;
1458  glProgramParameteriEXT(programName, GL_GEOMETRY_INPUT_TYPE_EXT, inputPrimitiveGlMode);
1459 
1460  GLuint outputPrimitiveGlMode = GL_TRIANGLE_STRIP;
1461  if (transformFeedback)
1462  {
1463  // In transform feedback, the output primitive mode needs to match the input primitive mode.
1464  if (assemblyMethod == VuoMesh_IndividualLines
1465  || assemblyMethod == VuoMesh_LineStrip)
1466  outputPrimitiveGlMode = GL_LINE_STRIP;
1467  else if (assemblyMethod == VuoMesh_Points)
1468  outputPrimitiveGlMode = GL_POINTS;
1469  }
1470  glProgramParameteriEXT(programName, GL_GEOMETRY_OUTPUT_TYPE_EXT, outputPrimitiveGlMode);
1471 
1472  unsigned int expectedVertexCount = expectedOutputPrimitiveCount;
1473  if (outputPrimitiveGlMode == GL_TRIANGLE_STRIP)
1474  expectedVertexCount *= 3;
1475  else if (outputPrimitiveGlMode == GL_LINE_STRIP)
1476  expectedVertexCount *= 2;
1477  glProgramParameteriEXT(programName, GL_GEOMETRY_VERTICES_OUT_EXT, expectedVertexCount);
1478  }
1479 
1480  if (transformFeedback)
1481  {
1482  const GLchar *varyings[] = { "outPosition", "outNormal", "outTextureCoordinate", "outVertexColor" };
1483  glTransformFeedbackVaryingsEXT(programName, 4, varyings, GL_SEPARATE_ATTRIBS_EXT);
1484  }
1485 
1486  glLinkProgram(programName);
1487 
1488  {
1489  int infologLength = 0;
1490  glGetProgramiv(programName, GL_INFO_LOG_LENGTH, &infologLength);
1491  if (infologLength > 0)
1492  {
1493  char *infoLog = (char *)malloc(infologLength);
1494  int charsWritten = 0;
1495  glGetProgramInfoLog(programName, infologLength, &charsWritten, infoLog);
1496 
1497  if (outIssues)
1498  {
1499  VuoShaderIssues *oi = static_cast<VuoShaderIssues *>(outIssues);
1500 
1501  istringstream iss(infoLog);
1502  string ln;
1503  while (getline(iss, ln))
1504  {
1505  string s;
1506  if ( !(s = VuoStringUtilities::substrAfter(ln, "ERROR: ")).empty() )
1507  oi->addIssue(VuoShaderFile::Program, VuoShaderIssues::NoLine, s);
1508  else if ( !(s = VuoStringUtilities::substrAfter(ln, "WARNING: ")).empty() )
1509  oi->addIssue(VuoShaderFile::Program, VuoShaderIssues::NoLine, s);
1510  else
1511  {
1512  VUserLog("Warning: Couldn't parse GLSL log message: \"%s\"", ln.c_str());
1513  oi->addIssue(VuoShaderFile::Program, VuoShaderIssues::NoLine, ln);
1514  }
1515  }
1516  }
1517  else
1518  VUserLog("%s: %s", description, infoLog);
1519 
1520  free(infoLog);
1521  }
1522  }
1523 
1524  int linkStatus = 0;
1525  glGetProgramiv(programName, GL_LINK_STATUS, &linkStatus);
1526  if (linkStatus == GL_FALSE)
1527  {
1528  glDeleteProgram(programName);
1529  dispatch_semaphore_signal(VuoGlProgramPool_semaphore);
1530  return (VuoGlProgram){0,NULL};
1531  }
1532 
1533  program.programName = programName;
1534 
1535  GLint uniformCount;
1536  glGetProgramiv(programName, GL_ACTIVE_UNIFORMS, &uniformCount);
1537 
1538  GLint maxNameLength;
1539  glGetProgramiv(programName, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxNameLength);
1540  char *name = (char *)malloc(maxNameLength + 1);
1541 
1542  VuoGlUniformMap *uniforms = new VuoGlUniformMap;
1543  program.uniforms = (void *)uniforms;
1544 
1545  for (GLuint i = 0; i < uniformCount; ++i)
1546  {
1547  GLint size;
1548  glGetActiveUniform(programName, i, maxNameLength+1, NULL, &size, NULL, name);
1549 
1550  // The uniform location is _not_ the same as the active uniform index!
1551  size_t nameLen = strlen(name);
1552  long hash = VuoGlPool_collate.hash(name, name+nameLen);
1553  (*uniforms)[hash] = glGetUniformLocation(programName, name);
1554 
1555  if (size > 1)
1556  {
1557  // For arrays, glGetActiveUniform() returns only the first array index. Take care of the rest of the indices.
1558  // E.g., `uniform float offset[3];` would return `offset[0]` with size 3, so we need to synthesize `offset[1]` and `offset[2]`.
1559  if (name[nameLen-2] == '0' && name[nameLen-1] == ']')
1560  {
1561  name[nameLen-2] = 0;
1562  for (int i = 1; i < size; ++i)
1563  {
1564  std::stringstream ss;
1565  ss << name << i << "]";
1566  string sss = ss.str();
1567  long sHash = VuoGlPool_collate.hash(sss.data(), sss.data()+sss.length());
1568  (*uniforms)[sHash] = glGetUniformLocation(programName, sss.c_str());
1569  }
1570  }
1571  }
1572  }
1573 
1574  VuoGlProgramPool[e] = program;
1575  }
1576 
1577  dispatch_semaphore_signal(VuoGlProgramPool_semaphore);
1578  return program;
1579 }
1580 
1584 int VuoGlProgram_getUniformLocation(VuoGlProgram program, const char *uniformIdentifier)
1585 {
1586  VuoGlUniformMap *uniforms = (VuoGlUniformMap *)program.uniforms;
1587 
1588  long hash = VuoGlPool_collate.hash(uniformIdentifier, uniformIdentifier+strlen(uniformIdentifier));
1589 
1590  VuoGlUniformMap::iterator i = uniforms->find(hash);
1591  if (i != uniforms->end())
1592  return i->second;
1593 
1594  return -1;
1595 }
1596 
1598 #define RETURN_STRING_IF_EQUAL(value) if (constant == value) return strdup(#value)
1599 
1605 char *VuoGl_stringForConstant(GLenum constant)
1606 {
1607  if (constant == 0)
1608  return strdup("(GL_ZERO or GL_POINTS)");
1609  if (constant == 1)
1610  return strdup("(GL_ONE or GL_LINES)");
1611  RETURN_STRING_IF_EQUAL(GL_LINE_LOOP);
1612  RETURN_STRING_IF_EQUAL(GL_LINE_STRIP);
1613  RETURN_STRING_IF_EQUAL(GL_TRIANGLES);
1614  RETURN_STRING_IF_EQUAL(GL_TRIANGLE_STRIP);
1615  RETURN_STRING_IF_EQUAL(GL_TRIANGLE_FAN);
1616  RETURN_STRING_IF_EQUAL(GL_QUADS);
1617  RETURN_STRING_IF_EQUAL(GL_QUAD_STRIP);
1618  RETURN_STRING_IF_EQUAL(GL_POLYGON);
1619  RETURN_STRING_IF_EQUAL(GL_BACK);
1620  RETURN_STRING_IF_EQUAL(GL_FRONT);
1621  RETURN_STRING_IF_EQUAL(GL_R8);
1622  RETURN_STRING_IF_EQUAL(GL_RED);
1623  RETURN_STRING_IF_EQUAL(GL_RGB);
1624  RETURN_STRING_IF_EQUAL(GL_RGB16);
1625  RETURN_STRING_IF_EQUAL(GL_RGB16F_ARB);
1626  RETURN_STRING_IF_EQUAL(GL_RGB32F_ARB);
1627  RETURN_STRING_IF_EQUAL(GL_RGBA);
1628  RETURN_STRING_IF_EQUAL(GL_RGBA8);
1629  RETURN_STRING_IF_EQUAL(GL_RGBA16);
1630  RETURN_STRING_IF_EQUAL(GL_RGBA16F_ARB);
1631  RETURN_STRING_IF_EQUAL(GL_RGBA32F_ARB);
1632  RETURN_STRING_IF_EQUAL(GL_BGRA);
1633  RETURN_STRING_IF_EQUAL(GL_BGR_EXT);
1634  RETURN_STRING_IF_EQUAL(GL_LUMINANCE);
1635  RETURN_STRING_IF_EQUAL(GL_LUMINANCE8);
1636  RETURN_STRING_IF_EQUAL(GL_LUMINANCE16);
1637  RETURN_STRING_IF_EQUAL(GL_LUMINANCE16F_ARB);
1638  RETURN_STRING_IF_EQUAL(GL_LUMINANCE16I_EXT);
1639  RETURN_STRING_IF_EQUAL(GL_LUMINANCE32F_ARB);
1640  RETURN_STRING_IF_EQUAL(GL_LUMINANCE8_ALPHA8);
1641  RETURN_STRING_IF_EQUAL(GL_LUMINANCE16_ALPHA16);
1642  RETURN_STRING_IF_EQUAL(GL_LUMINANCE_ALPHA16F_ARB);
1643  RETURN_STRING_IF_EQUAL(GL_LUMINANCE_ALPHA32F_ARB);
1644  RETURN_STRING_IF_EQUAL(GL_LUMINANCE_ALPHA);
1645  RETURN_STRING_IF_EQUAL(GL_DEPTH_COMPONENT);
1646  RETURN_STRING_IF_EQUAL(GL_DEPTH_COMPONENT16);
1647  RETURN_STRING_IF_EQUAL(GL_TEXTURE_2D);
1648  RETURN_STRING_IF_EQUAL(GL_TEXTURE_RECTANGLE_ARB);
1649  RETURN_STRING_IF_EQUAL(GL_FLOAT);
1650  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_BYTE);
1651  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_BYTE_3_3_2);
1652  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT);
1653  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_4_4_4_4);
1654  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_5_5_5_1);
1655  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_8_8_APPLE);
1656  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_INT_8_8_8_8);
1657  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_INT_10_10_10_2);
1658  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_BYTE_2_3_3_REV);
1659  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_5_6_5);
1660  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_5_6_5_REV);
1661  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_4_4_4_4_REV);
1662  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_1_5_5_5_REV);
1663  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_INT_8_8_8_8_REV);
1664  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_INT_2_10_10_10_REV);
1665  RETURN_STRING_IF_EQUAL(GL_COMPRESSED_RGB_S3TC_DXT1_EXT);
1666  RETURN_STRING_IF_EQUAL(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT);
1667  RETURN_STRING_IF_EQUAL(GL_COMPRESSED_RED_RGTC1);
1668  RETURN_STRING_IF_EQUAL(GL_YCBCR_422_APPLE);
1669  RETURN_STRING_IF_EQUAL(GL_ALREADY_SIGNALED);
1670  RETURN_STRING_IF_EQUAL(GL_TIMEOUT_EXPIRED);
1671  RETURN_STRING_IF_EQUAL(GL_CONDITION_SATISFIED);
1672  RETURN_STRING_IF_EQUAL(GL_WAIT_FAILED);
1673 
1674  return VuoText_format("(unknown: %x)", constant);
1675 }