Vuo  2.0.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  VUserLog("Error in CGLTexImageIOSurface2D(): %s", CGLErrorString(err));
545  VGL();
546  return false;
547  }
548  }
549  VuoGlPool_logVRAMAllocated(requiredBytes);
550  }
551  else
552  glBindTexture(target, name);
553 
554 
555  glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
556  glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
557 
558  glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
559  glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
560 
561  glBindTexture(target, 0);
562 
563  return name;
564 }
565 
576 void VuoGlTexturePool_disuse(VuoGlTexturePoolAllocation allocation, GLenum target, GLenum internalformat, unsigned short width, unsigned short height, GLuint name)
577 {
578  if (internalformat == 0)
579  {
580  VUserLog("Error: Can't recycle texture %d since we don't know its internalformat. Deleting.", name);
581  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
582  glDeleteTextures(1, &name);
583  });
584  VuoGlPool_logVRAMFreed(width * height * 1 /* unknown BPP */);
585  return;
586  }
587 
588  if (allocation == VuoGlTexturePool_AllocateIOSurface)
589  {
590  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
591  glDeleteTextures(1, &name);
592  });
593 // VuoGlTextureDescriptor descriptor(target, internalformat, width, height);
594 // VLog(" %s: Deleted texture %d (can't recycle IOSurface backing textures)", descriptor.toString().c_str(), name);
595  }
596  else
597  {
598  VuoGlTextureDescriptor descriptor(target, internalformat, width, height);
599  dispatch_semaphore_wait(VuoGlTexturePool_semaphore, DISPATCH_TIME_FOREVER);
600  {
601  auto it = &(*VuoGlTexturePool)[descriptor];
602  it->first.push(name);
603  it->second = VuoLogGetTime();
604  }
605  dispatch_semaphore_signal(VuoGlTexturePool_semaphore);
606 // VLog(" %s: Recycled texture %d", descriptor.toString().c_str(), name);
607  }
608 }
609 
610 
611 
612 
613 
617 typedef struct
618 {
619  unsigned int referenceCount;
620  VuoImage_freeCallback freeCallback;
621  void *freeCallbackContext;
622 } VuoGlTexture;
623 typedef map<GLuint, VuoGlTexture> VuoGlTextureReferenceCounts;
625 static dispatch_semaphore_t VuoGlTexture_referenceCountsSemaphore = NULL;
626 
629 static void __attribute__((constructor(101))) VuoGlTexture_init(void)
630 {
632  VuoGlTexture_referenceCountsSemaphore = dispatch_semaphore_create(1);
633 }
634 
640 void VuoGlTexture_retain(GLuint glTextureName, VuoImage_freeCallback freeCallback, void *freeCallbackContext)
641 {
642 // VLog("%d", glTextureName);
643  if (glTextureName == 0)
644  return;
645 
646  dispatch_semaphore_wait(VuoGlTexture_referenceCountsSemaphore, DISPATCH_TIME_FOREVER);
647 
648  VuoGlTextureReferenceCounts::iterator it = VuoGlTexture_referenceCounts->find(glTextureName);
649  if (it == VuoGlTexture_referenceCounts->end())
650  (*VuoGlTexture_referenceCounts)[glTextureName] = (VuoGlTexture){1, freeCallback, freeCallbackContext};
651  else
652  ++(*it).second.referenceCount;
653 
654  dispatch_semaphore_signal(VuoGlTexture_referenceCountsSemaphore);
655 }
656 
663 void VuoGlTexture_release(VuoGlTexturePoolAllocation allocation, GLuint glTextureTarget, GLenum internalformat, unsigned short width, unsigned short height, GLuint glTextureName)
664 {
665 // VLog(" %-11s %d (%s %s %dx%d)", VuoGlTexturePool_stringForAllocation(allocation), glTextureName, VuoGl_stringForConstant(glTextureTarget), VuoGl_stringForConstant(internalformat), width, height);
666  if (glTextureName == 0)
667  return;
668 
669  dispatch_semaphore_wait(VuoGlTexture_referenceCountsSemaphore, DISPATCH_TIME_FOREVER);
670 
671  VuoGlTextureReferenceCounts::iterator it = VuoGlTexture_referenceCounts->find(glTextureName);
672  if (it == VuoGlTexture_referenceCounts->end())
673  VUserLog("Error: VuoGlTexture_release() was called with OpenGL Texture Object %d, which was never retained.", glTextureName);
674  else
675  {
676  VuoGlTexture t = (*VuoGlTexture_referenceCounts)[glTextureName];
677  if (--t.referenceCount == 0)
678  {
679  if (t.freeCallback)
680  {
681  // Client-owned texture
682  struct _VuoImage i = (struct _VuoImage){glTextureName, internalformat, glTextureTarget, width, height, 1, t.freeCallbackContext, NULL, NULL, NULL};
683  t.freeCallback(&i);
684  }
685  else
686  {
687  // Vuo-owned texture
688  VuoGlTexturePool_disuse(allocation, glTextureTarget, internalformat, width, height, glTextureName);
689  }
690  VuoGlTexture_referenceCounts->erase(it);
691  }
692  else
693  (*VuoGlTexture_referenceCounts)[glTextureName] = t;
694  }
695 
696  dispatch_semaphore_signal(VuoGlTexture_referenceCountsSemaphore);
697 }
698 
708 void VuoGlTexture_disown(GLuint glTextureName)
709 {
710 // VLog("%d", glTextureName);
711  if (glTextureName == 0)
712  return;
713 
714  dispatch_semaphore_wait(VuoGlTexture_referenceCountsSemaphore, DISPATCH_TIME_FOREVER);
715 
716  VuoGlTextureReferenceCounts::iterator it = VuoGlTexture_referenceCounts->find(glTextureName);
717  if (it == VuoGlTexture_referenceCounts->end())
718  VUserLog("Error: VuoGlTexture_disown() was called with OpenGL Texture Object %d, which was never retained.", glTextureName);
719  else
720  {
721  VuoGlTexture t = (*VuoGlTexture_referenceCounts)[glTextureName];
722  if (t.referenceCount != 1)
723  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);
724  else
725  VuoGlTexture_referenceCounts->erase(it);
726  }
727 
728  dispatch_semaphore_signal(VuoGlTexture_referenceCountsSemaphore);
729 }
730 
731 
732 
736 typedef struct
737 {
738  IOSurfaceRef ioSurface;
739  GLuint texture;
740  unsigned short pixelsWide;
741  unsigned short pixelsHigh;
742  double lastUsedTime;
744 typedef pair<unsigned short,unsigned short> VuoGlTextureDimensionsType;
745 typedef map<VuoGlTextureDimensionsType, deque<VuoIoSurfacePoolEntryType> > VuoIoSurfacePoolType;
748 static dispatch_semaphore_t VuoIoSurfacePool_semaphore;
749 static CFStringRef receiverFinishedWithIoSurfaceKey = CFSTR("VuoReceiverFinished");
750 
751 const double VuoGlPool_cleanupInterval = 0.1;
752 static dispatch_source_t VuoGlPool_timer;
753 static dispatch_semaphore_t VuoGlPool_canceledAndCompleted;
754 
755 static unsigned long VuoGlPool_allocatedBytes = 0;
756 static unsigned long VuoGlPool_allocatedBytesMax = 0;
757 
767 void VuoGlPool_logVRAMAllocated(unsigned long bytesAllocated)
768 {
769 // VLog("Alloc %lu", bytesAllocated);
770  __sync_add_and_fetch(&VuoGlPool_allocatedBytes, bytesAllocated);
771 
772  // Not thread-safe, but the worst that could happen is that,
773  // if multiple threads exceed the limit simultaneously,
774  // we might not get the largest of the new maximums.
777 }
778 
788 void VuoGlPool_logVRAMFreed(unsigned long bytesFreed)
789 {
790 // VLog("Freed %lu", bytesFreed);
791  __sync_sub_and_fetch(&VuoGlPool_allocatedBytes, bytesFreed);
792 }
793 
797 static void VuoGlPool_cleanup(void *blah)
798 {
799 // VLog("{");
800  unsigned long totalTextureCount = 0;
801  static unsigned long totalTextureCountMax = 0;
802 
803  vector<GLuint> texturesToDelete;
804  vector<VuoIoSurfacePoolEntryType> iosurfaceTexturesToDisuse;
805 
806  dispatch_semaphore_wait(VuoGlTexturePool_semaphore, DISPATCH_TIME_FOREVER);
807  {
808  double now = VuoLogGetTime();
809  for (VuoGlTexturePoolType::iterator poolItem = VuoGlTexturePool->begin(); poolItem != VuoGlTexturePool->end(); )
810  {
811  unsigned long textureCount = poolItem->second.first.size();
812  double timeSinceLastUsed = now - poolItem->second.second;
813 // VLog(" %s: %lu unused texture%s (last used %.02gs ago)", poolItem->first.toString().c_str(), textureCount, textureCount == 1 ? "" : "s", timeSinceLastUsed);
814  if (timeSinceLastUsed > VuoGlPool_cleanupInterval)
815  {
816  if (textureCount)
817  {
818 // VLog(" Purging %lu expired texture%s", textureCount, textureCount == 1 ? "" : "s");
819  while (!poolItem->second.first.empty())
820  {
821  GLuint textureName = poolItem->second.first.front();
822  poolItem->second.first.pop();
823  texturesToDelete.push_back(textureName);
824  VuoGlPool_logVRAMFreed(poolItem->first.width * poolItem->first.height * VuoGlTexture_getBytesPerPixelForInternalFormat(poolItem->first.internalFormat));
825  }
826  }
827 
828  VuoGlTexturePool->erase(poolItem++);
829  }
830  else
831  {
832  totalTextureCount += textureCount;
833  ++poolItem;
834  }
835  }
836  }
837  dispatch_semaphore_signal(VuoGlTexturePool_semaphore);
838 
839  if (totalTextureCount > totalTextureCountMax)
840  totalTextureCountMax = totalTextureCount;
841 
842 
843  unsigned long totalIOSurfaceCount = 0;
844  static unsigned long totalIOSurfaceCountMax = 0;
845 
846  dispatch_semaphore_wait(VuoIoSurfacePool_semaphore, DISPATCH_TIME_FOREVER);
847  {
848  // Promote IOSurfaces from the quarantine to the pool, if the receiver has finished using them.
849  for (VuoIoSurfacePoolType::iterator quarantinedQueue = VuoIoSurfaceQuarantine->begin(); quarantinedQueue != VuoIoSurfaceQuarantine->end(); ++quarantinedQueue)
850  {
851  for (deque<VuoIoSurfacePoolEntryType>::iterator quarantinedIoSurfaceEntry = quarantinedQueue->second.begin(); quarantinedIoSurfaceEntry != quarantinedQueue->second.end();)
852  {
853  VuoIoSurfacePoolEntryType e = *quarantinedIoSurfaceEntry;
854  CFBooleanRef finished = (CFBooleanRef)IOSurfaceCopyValue(e.ioSurface, receiverFinishedWithIoSurfaceKey);
855  if (finished)
856  {
857  IOSurfaceRemoveValue(e.ioSurface, receiverFinishedWithIoSurfaceKey);
858 
859  (*VuoIoSurfacePool)[quarantinedQueue->first].push_back(e);
860  quarantinedIoSurfaceEntry = quarantinedQueue->second.erase(quarantinedIoSurfaceEntry);
861 
862 // VLog(" Promoted IOSurface %d + GL Texture %d (%dx%d) from quarantine to pool", IOSurfaceGetID(e.ioSurface), e.texture, quarantinedQueue->first.first, quarantinedQueue->first.second);
863  }
864  else
865  {
866  ++totalIOSurfaceCount;
867  ++quarantinedIoSurfaceEntry;
868  }
869  }
870  }
871 
872 
873  double now = VuoLogGetTime();
874  for (VuoIoSurfacePoolType::iterator poolQueue = VuoIoSurfacePool->begin(); poolQueue != VuoIoSurfacePool->end(); ++poolQueue)
875  {
876  for (deque<VuoIoSurfacePoolEntryType>::iterator poolIoSurfaceEntry = poolQueue->second.begin(); poolIoSurfaceEntry != poolQueue->second.end();)
877  {
878  VuoIoSurfacePoolEntryType e = *poolIoSurfaceEntry;
879  if (now - e.lastUsedTime > VuoGlPool_cleanupInterval * 2.)
880  {
881 // 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);
882 
883  CFRelease(e.ioSurface);
884  iosurfaceTexturesToDisuse.push_back(e);
885  poolIoSurfaceEntry = poolQueue->second.erase(poolIoSurfaceEntry);
886  }
887  else
888  {
889  ++totalIOSurfaceCount;
890  ++poolIoSurfaceEntry;
891  }
892  }
893  }
894  }
895  dispatch_semaphore_signal(VuoIoSurfacePool_semaphore);
896 
897  if (totalIOSurfaceCount > totalIOSurfaceCountMax)
898  totalIOSurfaceCountMax = totalIOSurfaceCount;
899 
900 
901  // Delete textures after we've released the pool semaphores, to avoid deadlock, and to avoid hogging the shared GL context.
902  if (texturesToDelete.size())
903  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
904  for (vector<GLuint>::const_iterator i = texturesToDelete.begin(); i != texturesToDelete.end(); ++i)
905  {
906  GLuint t = *i;
907  glDeleteTextures(1, &t);
908  // VuoGlPool_logVRAMFreed was called above when texturesToDelete was being built.
909  }
910  });
911  for (auto e : iosurfaceTexturesToDisuse)
912  VuoGlTexturePool_disuse(VuoGlTexturePool_AllocateIOSurface, GL_TEXTURE_RECTANGLE_ARB, GL_RGBA, e.pixelsWide, e.pixelsHigh, e.texture);
913 // VLog("}");
914 
915 
916  // Log VRAM use every 10 seconds.
917  static unsigned long cleanupCount = 0;
918  if ((++cleanupCount % (long)(10. / VuoGlPool_cleanupInterval) == 0)
919  && VuoIsDebugEnabled()
921  || totalTextureCount > 0 || totalTextureCountMax > 0
922  || totalIOSurfaceCount > 0 || totalIOSurfaceCountMax > 0))
923  {
924  __block unsigned long maximumTextureBytes;
925  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
926  maximumTextureBytes = VuoGlTexture_getMaximumTextureBytes(cgl_ctx);
927  });
928 
929  if (maximumTextureBytes > 0)
930  VUserLog("VRAM used: %5lu MB (%5lu MB max, %3lu%%). Textures in pool: %3lu (%3lu max). IOSurfaces in pool: %3lu (%3lu max).",
931  VuoGlPool_allocatedBytes/1024/1024,
932  VuoGlPool_allocatedBytesMax/1024/1024,
933  VuoGlPool_allocatedBytesMax*100/maximumTextureBytes,
934  totalTextureCount, totalTextureCountMax,
935  totalIOSurfaceCount, totalIOSurfaceCountMax);
936  else
937  VUserLog("VRAM used: %5lu MB (%5lu MB max). Textures in pool: %3lu (%3lu max). IOSurfaces in pool: %3lu (%3lu max).",
938  VuoGlPool_allocatedBytes/1024/1024,
939  VuoGlPool_allocatedBytesMax/1024/1024,
940  totalTextureCount, totalTextureCountMax,
941  totalIOSurfaceCount, totalIOSurfaceCountMax);
942  }
943 }
944 static void __attribute__((constructor)) VuoGlPool_init(void)
945 {
946  VuoGlPool_semaphore = dispatch_semaphore_create(1);
947 
948  VuoGlTexturePool_semaphore = dispatch_semaphore_create(1);
950 
951  VuoIoSurfacePool_semaphore = dispatch_semaphore_create(1);
954 
955  VuoGlPool_canceledAndCompleted = dispatch_semaphore_create(0);
956  VuoGlPool_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
957  dispatch_source_set_timer(VuoGlPool_timer, dispatch_walltime(NULL,0), NSEC_PER_SEC * VuoGlPool_cleanupInterval, NSEC_PER_SEC * VuoGlPool_cleanupInterval);
958  dispatch_source_set_event_handler_f(VuoGlPool_timer, VuoGlPool_cleanup);
959  dispatch_source_set_cancel_handler(VuoGlPool_timer, ^{
960  dispatch_semaphore_signal(VuoGlPool_canceledAndCompleted);
961  });
962  dispatch_resume(VuoGlPool_timer);
963 }
967 static void __attribute__((destructor)) VuoGlPool_fini(void)
968 {
969  dispatch_source_cancel(VuoGlPool_timer);
970 
971  // Wait for the last cleanup to complete.
972  dispatch_semaphore_wait(VuoGlPool_canceledAndCompleted, DISPATCH_TIME_FOREVER);
973 
974  delete VuoGlTexturePool;
975 
976  delete VuoIoSurfacePool;
977  delete VuoIoSurfaceQuarantine;
978 }
979 
984 VuoIoSurface VuoIoSurfacePool_use(VuoGlContext glContext, unsigned short pixelsWide, unsigned short pixelsHigh, GLuint *outputTexture)
985 {
986  VuoIoSurface ioSurface = NULL;
987  VuoGlTextureDimensionsType dimensions(pixelsWide,pixelsHigh);
988 
989  dispatch_semaphore_wait(VuoIoSurfacePool_semaphore, DISPATCH_TIME_FOREVER);
990  {
991  if ((*VuoIoSurfacePool)[dimensions].size())
992  {
993  VuoIoSurfacePoolEntryType e = (*VuoIoSurfacePool)[dimensions].front();
994  ioSurface = malloc(sizeof(VuoIoSurfacePoolEntryType));
995  memcpy(ioSurface, &e, sizeof(VuoIoSurfacePoolEntryType));
996  *outputTexture = e.texture;
997  (*VuoIoSurfacePool)[dimensions].pop_front();
998 // VLog(" Using recycled IOSurface %d + GL Texture %d (%dx%d) from pool", IOSurfaceGetID(e.ioSurface), *outputTexture, pixelsWide, pixelsHigh);
999  }
1000  }
1001  dispatch_semaphore_signal(VuoIoSurfacePool_semaphore);
1002 
1003  if (!ioSurface)
1004  {
1005  CFMutableDictionaryRef properties = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL);
1006 
1009  CFDictionaryAddValue(properties, kIOSurfaceIsGlobal, kCFBooleanTrue);
1010 
1011  long long pixelsWideLL = pixelsWide;
1012  CFNumberRef pixelsWideCF = CFNumberCreate(NULL, kCFNumberLongLongType, &pixelsWideLL);
1013  CFDictionaryAddValue(properties, kIOSurfaceWidth, pixelsWideCF);
1014 
1015  long long pixelsHighLL = pixelsHigh;
1016  CFNumberRef pixelsHighCF = CFNumberCreate(NULL, kCFNumberLongLongType, &pixelsHighLL);
1017  CFDictionaryAddValue(properties, kIOSurfaceHeight, pixelsHighCF);
1018 
1019  long long bytesPerElement = 4;
1020  CFNumberRef bytesPerElementCF = CFNumberCreate(NULL, kCFNumberLongLongType, &bytesPerElement);
1021  CFDictionaryAddValue(properties, kIOSurfaceBytesPerElement, bytesPerElementCF);
1022 
1023  CGLContextObj cgl_ctx = (CGLContextObj)glContext;
1024 
1026  ioSurface = e;
1027  e->ioSurface = IOSurfaceCreate(properties);
1028  e->pixelsWide = pixelsWide;
1029  e->pixelsHigh = pixelsHigh;
1030  e->lastUsedTime = -1;
1031  CFRelease(pixelsWideCF);
1032  CFRelease(pixelsHighCF);
1033  CFRelease(bytesPerElementCF);
1034  CFRelease(properties);
1035 
1036  *outputTexture = VuoGlTexturePool_use(cgl_ctx, VuoGlTexturePool_AllocateIOSurface, GL_TEXTURE_RECTANGLE_ARB, GL_RGBA, pixelsWide, pixelsHigh, GL_BGRA, e->ioSurface);
1037  e->texture = *outputTexture;
1038 
1039 // VLog(" Created IOSurface %d (%dx%d)", IOSurfaceGetID(e->ioSurface), pixelsWide, pixelsHigh);
1040  }
1041 
1042  return ioSurface;
1043 }
1044 
1049 {
1051  return IOSurfaceGetID(e->ioSurface);
1052 }
1053 
1058 {
1060  return e->ioSurface;
1061 }
1062 
1067 {
1069  return e->pixelsWide;
1070 }
1071 
1076 {
1078  return e->pixelsHigh;
1079 }
1080 
1085 {
1087  return e->texture;
1088 }
1089 
1102 void VuoIoSurfacePool_disuse(VuoIoSurface vis, bool quarantine)
1103 {
1105 // VLog(" Sender disusing IOSurface %d + GL Texture %d (%dx%d)", IOSurfaceGetID(e->ioSurface), e->texture, e->pixelsWide, e->pixelsHigh);
1106  VuoGlTextureDimensionsType dimensions(e->pixelsWide, e->pixelsHigh);
1107  e->lastUsedTime = VuoLogGetTime();
1108 
1109  dispatch_semaphore_wait(VuoIoSurfacePool_semaphore, DISPATCH_TIME_FOREVER);
1110  {
1111  if (quarantine)
1112  (*VuoIoSurfaceQuarantine)[dimensions].push_back(*e);
1113  else
1114  (*VuoIoSurfacePool)[dimensions].push_back(*e);
1115  }
1116  dispatch_semaphore_signal(VuoIoSurfacePool_semaphore);
1117 
1118  free(e);
1119 }
1120 
1126 {
1127  IOSurfaceRef ioSurface = (IOSurfaceRef)ios;
1128  IOSurfaceSetValue(ioSurface, receiverFinishedWithIoSurfaceKey, kCFBooleanTrue);
1129 }
1130 
1131 
1132 
1138 void VuoGlShader_parseShaderInfoLog(CGLContextObj cgl_ctx, GLenum type, GLuint obj, const GLchar *source, VuoShaderFile::Stage stage, VuoShaderIssues *outIssues)
1139 {
1140  int infologLength = 0;
1141  glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infologLength);
1142  if (infologLength > 0)
1143  {
1144  char *infoLog = (char *)malloc(infologLength);
1145  int charsWritten = 0;
1146  glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
1147 
1148  if (outIssues)
1149  {
1150  istringstream iss(infoLog);
1151  string ln;
1152  while (getline(iss, ln))
1153  {
1154  int lineNumber = 0;
1155  char *message = (char *)malloc(ln.length() + 1);
1156  char *quotation = (char *)malloc(ln.length() + 1);
1157  if (sscanf(ln.c_str(), "ERROR: 0:%d: '' : %[^\n]", &lineNumber, message) == 2)
1158  outIssues->addIssue(stage, lineNumber, message);
1159  else if (sscanf(ln.c_str(), "ERROR: 0:%d: '%[^']' : syntax error: %[^\n]", &lineNumber, quotation, message) == 3)
1160  outIssues->addIssue(stage, lineNumber, message + string(": '") + quotation + "'");
1161  else if (sscanf(ln.c_str(), "ERROR: 0:%d: %[^\n]", &lineNumber, message) == 2)
1162  outIssues->addIssue(stage, lineNumber, message);
1163  else if (sscanf(ln.c_str(), "WARNING: 0:%d: %[^\n]", &lineNumber, message) == 2)
1164  outIssues->addIssue(stage, lineNumber, message);
1165  else
1166  {
1167  VUserLog("Warning: Couldn't parse GLSL log message: \"%s\"", ln.c_str());
1168  outIssues->addIssue(stage, VuoShaderIssues::NoLine, ln);
1169  }
1170  free(message);
1171  free(quotation);
1172  }
1173  }
1174  else
1175  {
1176  VUserLog("%s", infoLog);
1177  VDebugLog("Source code:\n%s", source);
1178  }
1179 
1180  free(infoLog);
1181  }
1182 }
1183 
1184 map<GLenum, map<long, GLuint> > VuoGlShaderPool __attribute__((init_priority(101)));
1185 dispatch_semaphore_t VuoGlShaderPool_semaphore = NULL;
1186 
1190 __attribute__((constructor)) static void VuoGlShaderPool_init(void)
1191 {
1192  VuoGlShaderPool_semaphore = dispatch_semaphore_create(1);
1193 }
1194 
1200 static bool VuoGlShader_resolveIncludes(string &source, VuoShaderFile::Stage stage, VuoShaderIssues *outIssues)
1201 {
1202  const char *cwd = VuoGetWorkingDirectory();
1203  const string vuoFrameworkPath = VuoFileUtilities::getVuoFrameworkPath();
1204  const string vuoRunnerFrameworkPath = VuoFileUtilities::getVuoRunnerFrameworkPath();
1205  string userModulesPath = VuoFileUtilities::getUserModulesPath();
1206  if (!VuoFileUtilities::dirExists(userModulesPath))
1207  userModulesPath = "";
1208  string systemModulesPath = VuoFileUtilities::getSystemModulesPath();
1209  if (!VuoFileUtilities::dirExists(systemModulesPath))
1210  systemModulesPath = "";
1211 
1212  set<string> includedFiles;
1213 
1214  while (1)
1215  {
1216  const string includeKeyword = "#include";
1217 
1218  size_t includeStart = source.find(includeKeyword);
1219  if (includeStart == string::npos)
1220  return true;
1221 
1222  size_t offset = includeStart + includeKeyword.length();
1223 
1224  while (source[offset] == ' ')
1225  ++offset;
1226 
1227  if (source[offset] != '"'
1228  && source[offset] != '<')
1229  {
1231  if (outIssues)
1232  outIssues->addIssue(stage, VuoShaderIssues::NoLine, "Syntax error in #include statement. Expected syntax: #include \"filename.glsl\"");
1233  else
1234  VUserLog("Error: Syntax error in #include statement. Expected syntax: #include \"filename.glsl\"");
1235  return false;
1236  }
1237  ++offset;
1238 
1239  size_t filenameStart = offset;
1240 
1241  while (source[offset] != '"'
1242  && source[offset] != '>')
1243  ++offset;
1244 
1245  size_t filenameEnd = offset;
1246 
1247  string filename = source.substr(filenameStart, filenameEnd - filenameStart);
1248 
1249  vector<string> pathsToTest;
1250  if (strcmp(cwd, "/") != 0)
1251  pathsToTest.push_back(cwd + string("/") + filename);
1252  if (!vuoFrameworkPath.empty())
1253  pathsToTest.push_back(vuoFrameworkPath + "/Resources/shaders/" + filename);
1254  if (!vuoRunnerFrameworkPath.empty())
1255  pathsToTest.push_back(vuoRunnerFrameworkPath + "/Resources/shaders/" + filename);
1256  if (!userModulesPath.empty())
1257  pathsToTest.push_back(userModulesPath + "/" + filename);
1258  if (!systemModulesPath.empty())
1259  pathsToTest.push_back(systemModulesPath + "/" + filename);
1260  bool found = false;
1261  for (vector<string>::iterator i = pathsToTest.begin(); i != pathsToTest.end(); ++i)
1262  {
1264  {
1265  found = true;
1266 
1267  source.erase(includeStart, offset - includeStart + 1);
1268 
1269  // Prevent recursive includes.
1270  if (includedFiles.count(*i))
1271  {
1272  source.insert(includeStart,
1273  "\n"
1274  "// ============================================================\n"
1275  "// Skipped including file \"" + *i + "\" since it was already included.\n"
1276  "// ============================================================\n"
1277  "\n"
1278  );
1279  break;
1280  }
1281  includedFiles.insert(*i);
1282 
1283  source.insert(includeStart,
1284  "\n"
1285  "// ============================================================\n"
1286  "// Begin included file \"" + *i + "\"\n"
1288  + "\n"
1289  "// End included file \"" + *i + "\"\n"
1290  "// ============================================================\n"
1291  "\n"
1292  );
1293  break;
1294  }
1295  }
1296 
1297  if (!found)
1298  {
1300  if (outIssues)
1301  outIssues->addIssue(stage, VuoShaderIssues::NoLine, "Couldn't find include file \"" + filename + "\"");
1302  else
1303  VUserLog("Error: Couldn't find include file \"%s\".", filename.c_str());
1304  return false;
1305  }
1306  }
1307 }
1308 
1309 static const std::locale VuoGlPool_locale;
1310 static const std::collate<char> &VuoGlPool_collate = std::use_facet<std::collate<char> >(VuoGlPool_locale);
1311 
1329 GLuint VuoGlShader_use(VuoGlContext glContext, GLenum type, const char *source, void *outIssues)
1330 {
1331  long hash = VuoGlPool_collate.hash(source, source+strlen(source));
1332 
1333  dispatch_semaphore_wait(VuoGlShaderPool_semaphore, DISPATCH_TIME_FOREVER);
1334 
1335  GLuint shader;
1336  if (!outIssues && VuoGlShaderPool[type].find(hash) != VuoGlShaderPool[type].end())
1337  shader = VuoGlShaderPool[type][hash];
1338  else
1339  {
1340  CGLContextObj cgl_ctx = (CGLContextObj)glContext;
1341 
1342  string combinedSource = source;
1343 
1344  VuoShaderIssues *oi = static_cast<VuoShaderIssues *>(outIssues);
1345  VuoShaderFile::Stage stage;
1346  if (type == GL_VERTEX_SHADER)
1347  stage = VuoShaderFile::Vertex;
1348  else if (type == GL_GEOMETRY_SHADER_EXT)
1349  stage = VuoShaderFile::Geometry;
1350  else // if (type == GL_FRAGMENT_SHADER)
1351  stage = VuoShaderFile::Fragment;
1352 
1353  if (!VuoGlShader_resolveIncludes(combinedSource, stage, oi))
1354  {
1355  shader = 0;
1356  goto done;
1357  }
1358 
1359 // VLog("\n%s\n\n", combinedSource.c_str());
1360 
1361  shader = glCreateShader(type);
1362  GLint length = combinedSource.length();
1363  const GLchar *combinedSourceCString = combinedSource.c_str();
1364  glShaderSource(shader, 1, (const GLchar**)&combinedSourceCString, &length);
1365  glCompileShader(shader);
1366  VuoGlShader_parseShaderInfoLog(cgl_ctx, type, shader, combinedSourceCString, stage, oi);
1367 
1368  GLint status = GL_FALSE;
1369  glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
1370  if (status == GL_FALSE)
1371  {
1372  glDeleteShader(shader);
1373  shader = 0;
1374  goto done;
1375  }
1376 
1377  VuoGlShaderPool[type][hash] = shader;
1378  }
1379 
1380 done:
1381  dispatch_semaphore_signal(VuoGlShaderPool_semaphore);
1382 
1383  return shader;
1384 }
1385 
1386 // Too bad we can't use std::tuple yet…
1387 typedef pair<GLuint, pair<GLuint, pair<GLuint, pair<VuoMesh_ElementAssemblyMethod, unsigned int> > > > VuoGlProgramDescriptorType;
1388 typedef map<VuoGlProgramDescriptorType, VuoGlProgram> VuoGlProgramPoolType;
1390 static dispatch_semaphore_t VuoGlProgramPool_semaphore;
1391 static void __attribute__((constructor)) VuoGlProgramPool_init(void)
1392 {
1393  VuoGlProgramPool_semaphore = dispatch_semaphore_create(1);
1394 }
1395 
1396 typedef std::map<long, GLuint> VuoGlUniformMap;
1397 
1415 VuoGlProgram VuoGlProgram_use(VuoGlContext glContext, const char *description, GLuint vertexShaderName, GLuint geometryShaderName, GLuint fragmentShaderName, VuoMesh_ElementAssemblyMethod assemblyMethod, unsigned int expectedOutputPrimitiveCount, void *outIssues)
1416 {
1417 // VLog("looking for %d %d %d %d %d", vertexShaderName, geometryShaderName, fragmentShaderName, assemblyMethod, expectedOutputPrimitiveCount);
1418  VuoGlProgram program;
1419  VuoGlProgramDescriptorType e(vertexShaderName, std::make_pair(geometryShaderName, std::make_pair(fragmentShaderName, std::make_pair(assemblyMethod, expectedOutputPrimitiveCount))));
1420  dispatch_semaphore_wait(VuoGlProgramPool_semaphore, DISPATCH_TIME_FOREVER);
1421  VuoGlProgramPoolType::iterator it = VuoGlProgramPool.find(e);
1422  if (!outIssues && it != VuoGlProgramPool.end())
1423  program = it->second;
1424  else
1425  {
1426  CGLContextObj cgl_ctx = (CGLContextObj)glContext;
1427 
1428  GLuint programName = glCreateProgram();
1429  glAttachShader(programName, vertexShaderName);
1430  if (geometryShaderName)
1431  glAttachShader(programName, geometryShaderName);
1432  if (fragmentShaderName)
1433  glAttachShader(programName, fragmentShaderName);
1434 
1435  bool transformFeedback = false;
1436  // If there's no fragment shader, this program is being used for transform feedback.
1437  if (!fragmentShaderName)
1438  transformFeedback = true;
1439 
1440  // Make sure `position` is at location 0, since location 0 is required in order for glDraw*() to work.
1441  glBindAttribLocation(programName, 0, "position");
1442 
1443  if (geometryShaderName)
1444  {
1445  GLuint inputPrimitiveGlMode = GL_TRIANGLES;
1446  if (assemblyMethod == VuoMesh_IndividualLines
1447  || assemblyMethod == VuoMesh_LineStrip)
1448  inputPrimitiveGlMode = GL_LINES;
1449  else if (assemblyMethod == VuoMesh_Points)
1450  inputPrimitiveGlMode = GL_POINTS;
1451  glProgramParameteriEXT(programName, GL_GEOMETRY_INPUT_TYPE_EXT, inputPrimitiveGlMode);
1452 
1453  GLuint outputPrimitiveGlMode = GL_TRIANGLE_STRIP;
1454  if (transformFeedback)
1455  {
1456  // In transform feedback, the output primitive mode needs to match the input primitive mode.
1457  if (assemblyMethod == VuoMesh_IndividualLines
1458  || assemblyMethod == VuoMesh_LineStrip)
1459  outputPrimitiveGlMode = GL_LINE_STRIP;
1460  else if (assemblyMethod == VuoMesh_Points)
1461  outputPrimitiveGlMode = GL_POINTS;
1462  }
1463  glProgramParameteriEXT(programName, GL_GEOMETRY_OUTPUT_TYPE_EXT, outputPrimitiveGlMode);
1464 
1465  unsigned int expectedVertexCount = expectedOutputPrimitiveCount;
1466  if (outputPrimitiveGlMode == GL_TRIANGLE_STRIP)
1467  expectedVertexCount *= 3;
1468  else if (outputPrimitiveGlMode == GL_LINE_STRIP)
1469  expectedVertexCount *= 2;
1470  glProgramParameteriEXT(programName, GL_GEOMETRY_VERTICES_OUT_EXT, expectedVertexCount);
1471  }
1472 
1473  if (transformFeedback)
1474  {
1475  const GLchar *varyings[] = { "outPosition", "outNormal", "outTextureCoordinate", "outVertexColor" };
1476  glTransformFeedbackVaryingsEXT(programName, 4, varyings, GL_SEPARATE_ATTRIBS_EXT);
1477  }
1478 
1479  glLinkProgram(programName);
1480 
1481  {
1482  int infologLength = 0;
1483  glGetProgramiv(programName, GL_INFO_LOG_LENGTH, &infologLength);
1484  if (infologLength > 0)
1485  {
1486  char *infoLog = (char *)malloc(infologLength);
1487  int charsWritten = 0;
1488  glGetProgramInfoLog(programName, infologLength, &charsWritten, infoLog);
1489 
1490  if (outIssues)
1491  {
1492  VuoShaderIssues *oi = static_cast<VuoShaderIssues *>(outIssues);
1493 
1494  istringstream iss(infoLog);
1495  string ln;
1496  while (getline(iss, ln))
1497  {
1498  string s;
1499  if ( !(s = VuoStringUtilities::substrAfter(ln, "ERROR: ")).empty() )
1500  oi->addIssue(VuoShaderFile::Program, VuoShaderIssues::NoLine, s);
1501  else if ( !(s = VuoStringUtilities::substrAfter(ln, "WARNING: ")).empty() )
1502  oi->addIssue(VuoShaderFile::Program, VuoShaderIssues::NoLine, s);
1503  else
1504  {
1505  VUserLog("Warning: Couldn't parse GLSL log message: \"%s\"", ln.c_str());
1506  oi->addIssue(VuoShaderFile::Program, VuoShaderIssues::NoLine, ln);
1507  }
1508  }
1509  }
1510  else
1511  VUserLog("%s: %s", description, infoLog);
1512 
1513  free(infoLog);
1514  }
1515  }
1516 
1517  int linkStatus = 0;
1518  glGetProgramiv(programName, GL_LINK_STATUS, &linkStatus);
1519  if (linkStatus == GL_FALSE)
1520  {
1521  glDeleteProgram(programName);
1522  dispatch_semaphore_signal(VuoGlProgramPool_semaphore);
1523  return (VuoGlProgram){0,NULL};
1524  }
1525 
1526  program.programName = programName;
1527 
1528  GLint uniformCount;
1529  glGetProgramiv(programName, GL_ACTIVE_UNIFORMS, &uniformCount);
1530 
1531  GLint maxNameLength;
1532  glGetProgramiv(programName, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxNameLength);
1533  char *name = (char *)malloc(maxNameLength + 1);
1534 
1535  VuoGlUniformMap *uniforms = new VuoGlUniformMap;
1536  program.uniforms = (void *)uniforms;
1537 
1538  for (GLuint i = 0; i < uniformCount; ++i)
1539  {
1540  GLint size;
1541  glGetActiveUniform(programName, i, maxNameLength+1, NULL, &size, NULL, name);
1542 
1543  // The uniform location is _not_ the same as the active uniform index!
1544  size_t nameLen = strlen(name);
1545  long hash = VuoGlPool_collate.hash(name, name+nameLen);
1546  (*uniforms)[hash] = glGetUniformLocation(programName, name);
1547 
1548  if (size > 1)
1549  {
1550  // For arrays, glGetActiveUniform() returns only the first array index. Take care of the rest of the indices.
1551  // E.g., `uniform float offset[3];` would return `offset[0]` with size 3, so we need to synthesize `offset[1]` and `offset[2]`.
1552  if (name[nameLen-2] == '0' && name[nameLen-1] == ']')
1553  {
1554  name[nameLen-2] = 0;
1555  for (int i = 1; i < size; ++i)
1556  {
1557  std::stringstream ss;
1558  ss << name << i << "]";
1559  string sss = ss.str();
1560  long sHash = VuoGlPool_collate.hash(sss.data(), sss.data()+sss.length());
1561  (*uniforms)[sHash] = glGetUniformLocation(programName, sss.c_str());
1562  }
1563  }
1564  }
1565  }
1566 
1567  VuoGlProgramPool[e] = program;
1568  }
1569 
1570  dispatch_semaphore_signal(VuoGlProgramPool_semaphore);
1571  return program;
1572 }
1573 
1577 int VuoGlProgram_getUniformLocation(VuoGlProgram program, const char *uniformIdentifier)
1578 {
1579  VuoGlUniformMap *uniforms = (VuoGlUniformMap *)program.uniforms;
1580 
1581  long hash = VuoGlPool_collate.hash(uniformIdentifier, uniformIdentifier+strlen(uniformIdentifier));
1582 
1583  VuoGlUniformMap::iterator i = uniforms->find(hash);
1584  if (i != uniforms->end())
1585  return i->second;
1586 
1587  return -1;
1588 }
1589 
1591 #define RETURN_STRING_IF_EQUAL(value) if (constant == value) return strdup(#value)
1592 
1598 char *VuoGl_stringForConstant(GLenum constant)
1599 {
1600  if (constant == 0)
1601  return strdup("(GL_ZERO or GL_POINTS)");
1602  if (constant == 1)
1603  return strdup("(GL_ONE or GL_LINES)");
1604  RETURN_STRING_IF_EQUAL(GL_LINE_LOOP);
1605  RETURN_STRING_IF_EQUAL(GL_LINE_STRIP);
1606  RETURN_STRING_IF_EQUAL(GL_TRIANGLES);
1607  RETURN_STRING_IF_EQUAL(GL_TRIANGLE_STRIP);
1608  RETURN_STRING_IF_EQUAL(GL_TRIANGLE_FAN);
1609  RETURN_STRING_IF_EQUAL(GL_QUADS);
1610  RETURN_STRING_IF_EQUAL(GL_QUAD_STRIP);
1611  RETURN_STRING_IF_EQUAL(GL_POLYGON);
1612  RETURN_STRING_IF_EQUAL(GL_BACK);
1613  RETURN_STRING_IF_EQUAL(GL_FRONT);
1614  RETURN_STRING_IF_EQUAL(GL_R8);
1615  RETURN_STRING_IF_EQUAL(GL_RED);
1616  RETURN_STRING_IF_EQUAL(GL_RGB);
1617  RETURN_STRING_IF_EQUAL(GL_RGB16);
1618  RETURN_STRING_IF_EQUAL(GL_RGB16F_ARB);
1619  RETURN_STRING_IF_EQUAL(GL_RGB32F_ARB);
1620  RETURN_STRING_IF_EQUAL(GL_RGBA);
1621  RETURN_STRING_IF_EQUAL(GL_RGBA8);
1622  RETURN_STRING_IF_EQUAL(GL_RGBA16);
1623  RETURN_STRING_IF_EQUAL(GL_RGBA16F_ARB);
1624  RETURN_STRING_IF_EQUAL(GL_RGBA32F_ARB);
1625  RETURN_STRING_IF_EQUAL(GL_BGRA);
1626  RETURN_STRING_IF_EQUAL(GL_BGR_EXT);
1627  RETURN_STRING_IF_EQUAL(GL_LUMINANCE);
1628  RETURN_STRING_IF_EQUAL(GL_LUMINANCE8);
1629  RETURN_STRING_IF_EQUAL(GL_LUMINANCE16);
1630  RETURN_STRING_IF_EQUAL(GL_LUMINANCE16F_ARB);
1631  RETURN_STRING_IF_EQUAL(GL_LUMINANCE16I_EXT);
1632  RETURN_STRING_IF_EQUAL(GL_LUMINANCE32F_ARB);
1633  RETURN_STRING_IF_EQUAL(GL_LUMINANCE8_ALPHA8);
1634  RETURN_STRING_IF_EQUAL(GL_LUMINANCE16_ALPHA16);
1635  RETURN_STRING_IF_EQUAL(GL_LUMINANCE_ALPHA16F_ARB);
1636  RETURN_STRING_IF_EQUAL(GL_LUMINANCE_ALPHA32F_ARB);
1637  RETURN_STRING_IF_EQUAL(GL_LUMINANCE_ALPHA);
1638  RETURN_STRING_IF_EQUAL(GL_DEPTH_COMPONENT);
1639  RETURN_STRING_IF_EQUAL(GL_DEPTH_COMPONENT16);
1640  RETURN_STRING_IF_EQUAL(GL_TEXTURE_2D);
1641  RETURN_STRING_IF_EQUAL(GL_TEXTURE_RECTANGLE_ARB);
1642  RETURN_STRING_IF_EQUAL(GL_FLOAT);
1643  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_BYTE);
1644  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_BYTE_3_3_2);
1645  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT);
1646  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_4_4_4_4);
1647  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_5_5_5_1);
1648  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_8_8_APPLE);
1649  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_INT_8_8_8_8);
1650  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_INT_10_10_10_2);
1651  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_BYTE_2_3_3_REV);
1652  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_5_6_5);
1653  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_5_6_5_REV);
1654  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_4_4_4_4_REV);
1655  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_1_5_5_5_REV);
1656  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_INT_8_8_8_8_REV);
1657  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_INT_2_10_10_10_REV);
1658  RETURN_STRING_IF_EQUAL(GL_COMPRESSED_RGB_S3TC_DXT1_EXT);
1659  RETURN_STRING_IF_EQUAL(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT);
1660  RETURN_STRING_IF_EQUAL(GL_COMPRESSED_RED_RGTC1);
1661  RETURN_STRING_IF_EQUAL(GL_YCBCR_422_APPLE);
1662  RETURN_STRING_IF_EQUAL(GL_ALREADY_SIGNALED);
1663  RETURN_STRING_IF_EQUAL(GL_TIMEOUT_EXPIRED);
1664  RETURN_STRING_IF_EQUAL(GL_CONDITION_SATISFIED);
1665  RETURN_STRING_IF_EQUAL(GL_WAIT_FAILED);
1666 
1667  return VuoText_format("(unknown: %x)", constant);
1668 }