Vuo  2.3.2
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 #pragma clang diagnostic push
384 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
385  CGLContextObj cgl_ctx = (CGLContextObj)glContext;
386  static unsigned long maximumTextureBytes = 0;
387  static dispatch_once_t maximumTextureBytesQuery = 0;
388  dispatch_once(&maximumTextureBytesQuery, ^{
389  GLint contextRendererID;
390  CGLGetParameter(cgl_ctx, kCGLCPCurrentRendererID, &contextRendererID);
391 
392  // https://b33p.net/kosada/node/12177
393  // https://developer.apple.com/library/mac/qa/qa1168/_index.html says:
394  // "If you are looking for the VRAM sizes of all the renderers on your system […]
395  // you may specify a -1/0xFFFFFFFF display mask in the CGLQueryRendererInfo() function.
396  CGLRendererInfoObj ri;
397  GLint rendererCount = 0;
398  CGLQueryRendererInfo(-1, &ri, &rendererCount);
399  for (int i = 0; i < rendererCount; ++i)
400  {
401  GLint rendererID;
402  CGLDescribeRenderer(ri, i, kCGLRPRendererID, &rendererID);
403  if (rendererID == contextRendererID)
404  {
405  GLint textureMegabytes = 0;
406  if (CGLDescribeRenderer(ri, i, kCGLRPTextureMemoryMegabytes, &textureMegabytes) == kCGLNoError)
407  {
408  // In OS X, the GPU seems to often crash with individual textures that occupy more than a certain amount of memory.
409  // See https://b33p.net/kosada/node/10791
410  // See https://b33p.net/kosada/node/12030
411  double fudge = .9;
412  if ((rendererID & kCGLRendererIDMatchingMask) == kCGLRendererIntelHD4000ID)
413  // Reduce on Intel 4000, since on macOS 10.14, textures larger than this fail with GL_OUT_OF_MEMORY.
414  fudge = .74;
415 
416  maximumTextureBytes = (textureMegabytes - 85) * 1048576UL * fudge;
417  VDebugLog("%ld MB", maximumTextureBytes / 1024 / 1024);
418  break;
419  }
420  }
421  }
422  CGLDestroyRendererInfo(ri);
423  });
424  return maximumTextureBytes;
425 #pragma clang diagnostic pop
426 }
427 
432 {
433  static GLint maximumTextureDimension = 0;
434  static dispatch_once_t maximumTextureDimensionQuery = 0;
435  dispatch_once(&maximumTextureDimensionQuery, ^{
436  CGLContextObj cgl_ctx = (CGLContextObj)glContext;
437  glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maximumTextureDimension);
438  });
439  return maximumTextureDimension;
440 }
441 
442 #pragma clang diagnostic push
443 #pragma clang diagnostic ignored "-Wunused-function"
448 {
449  return allocation == VuoGlTexturePool_NoAllocation ? "NoAlloc" :
450  (allocation == VuoGlTexturePool_Allocate ? "Alloc" :
451  allocation == VuoGlTexturePool_AllocateIOSurface ? "AllocIOSurf" : "?");
452 }
453 #pragma clang diagnostic pop
454 
479 GLuint VuoGlTexturePool_use(VuoGlContext glContext, VuoGlTexturePoolAllocation allocation, GLenum target, GLenum internalformat, unsigned short width, unsigned short height, GLenum format, void *ioSurfaceRef)
480 {
481  if (allocation == VuoGlTexturePool_AllocateIOSurface
482  && target != GL_TEXTURE_RECTANGLE_ARB)
483  {
484  VUserLog("Error: To use VuoGlTexturePool_AllocateIOSurface, target must be GL_TEXTURE_RECTANGLE_ARB.");
485  return 0;
486  }
487 
488  unsigned char bytesPerPixel = VuoGlTexture_getBytesPerPixel(internalformat, format);
489  unsigned long requiredBytes = width * height * bytesPerPixel;
490  VuoGlTextureDescriptor descriptor(target, internalformat, width, height);
491 // VLog(" %-11s %s: Requested",VuoGlTexturePool_stringForAllocation(allocation), descriptor.toString().c_str());
492 
493  GLuint name = 0;
494  if (allocation != VuoGlTexturePool_AllocateIOSurface)
495  {
496  dispatch_semaphore_wait(VuoGlTexturePool_semaphore, DISPATCH_TIME_FOREVER);
497  auto it = VuoGlTexturePool->find(descriptor);
498  if (it != VuoGlTexturePool->end() && it->second.first.size())
499  {
500  name = it->second.first.front();
501  it->second.first.pop();
502 // if (name) VLog(" %s: Used recycled texture %d", descriptor.toString().c_str(), name);
503  it->second.second = VuoLogGetTime();
504  }
505  dispatch_semaphore_signal(VuoGlTexturePool_semaphore);
506  }
507 
508 
509  CGLContextObj cgl_ctx = (CGLContextObj)glContext;
510 
511  if (name == 0)
512  {
513  // Check texture size against available Texture Video RAM (to avoid GPU crashes / kernel panics).
514  unsigned long maximumTextureBytes = VuoGlTexture_getMaximumTextureBytes(glContext);
515  if (maximumTextureBytes > 0)
516  {
517  if (requiredBytes > maximumTextureBytes)
518  {
520  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);
521  return 0;
522  }
523  }
524 
525  // Check texture size against the GPU's allowed texture size.
526  GLint maxDimension = VuoGlTexture_getMaximumTextureDimension(glContext);
527  if (width > maxDimension || height > maxDimension)
528  {
530  VUserLog("This GPU doesn't support textures of size %dx%d (GPU's limit is %dx%d).", width, height, maxDimension, maxDimension);
531  return 0;
532  }
533 
534  glGenTextures(1, &name);
535 // VLog(" %-11s %s: Generated new texture %d", VuoGlTexturePool_stringForAllocation(allocation), descriptor.toString().c_str(), name);
536  glBindTexture(target, name);
537  if (allocation == VuoGlTexturePool_Allocate)
538  {
539  glTexImage2D(target, 0, internalformat, width, height, 0, format, VuoGlTexture_getType(format), NULL);
540 // 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);
541  }
542  else if (allocation == VuoGlTexturePool_AllocateIOSurface)
543  {
544 #pragma clang diagnostic push
545 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
546  CGLError err = CGLTexImageIOSurface2D(cgl_ctx, GL_TEXTURE_RECTANGLE_ARB, internalformat, width, height, format, VuoGlTexture_getType(format), (IOSurfaceRef)ioSurfaceRef, 0);
547  if (err != kCGLNoError)
548  {
549  char *internalFormatString = VuoGl_stringForConstant(internalformat);
550  char *formatString = VuoGl_stringForConstant(format);
551  char *typeString = VuoGl_stringForConstant(VuoGlTexture_getType(format));
552  VUserLog("Error in CGLTexImageIOSurface2D(context, GL_TEXTURE_RECTANGLE_ARB, %s, %hu, %hu, %s, %s, iosurface, 0): %s",
553  internalFormatString, width, height, formatString, typeString, CGLErrorString(err));
554  free(internalFormatString);
555  free(formatString);
556  free(typeString);
557  VGL();
558  return false;
559  }
560 #pragma clang diagnostic pop
561  }
562  VuoGlPool_logVRAMAllocated(requiredBytes);
563  }
564  else
565  glBindTexture(target, name);
566 
567 
568  glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
569  glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
570 
571  glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
572  glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
573 
574  glBindTexture(target, 0);
575 
576  return name;
577 }
578 
589 void VuoGlTexturePool_disuse(VuoGlTexturePoolAllocation allocation, GLenum target, GLenum internalformat, unsigned short width, unsigned short height, GLuint name)
590 {
591  if (internalformat == 0)
592  {
593  VUserLog("Error: Can't recycle texture %d since we don't know its internalformat. Deleting.", name);
594  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
595  glDeleteTextures(1, &name);
596  });
597  VuoGlPool_logVRAMFreed(width * height * 1 /* unknown BPP */);
598  return;
599  }
600 
601  if (allocation == VuoGlTexturePool_AllocateIOSurface)
602  {
603  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
604  glDeleteTextures(1, &name);
605  });
606 // VuoGlTextureDescriptor descriptor(target, internalformat, width, height);
607 // VLog(" %s: Deleted texture %d (can't recycle IOSurface backing textures)", descriptor.toString().c_str(), name);
608  }
609  else
610  {
611  VuoGlTextureDescriptor descriptor(target, internalformat, width, height);
612  dispatch_semaphore_wait(VuoGlTexturePool_semaphore, DISPATCH_TIME_FOREVER);
613  {
614  auto it = &(*VuoGlTexturePool)[descriptor];
615  it->first.push(name);
616  it->second = VuoLogGetTime();
617  }
618  dispatch_semaphore_signal(VuoGlTexturePool_semaphore);
619 // VLog(" %s: Recycled texture %d", descriptor.toString().c_str(), name);
620  }
621 }
622 
623 
624 
625 
626 
630 typedef struct
631 {
632  unsigned int referenceCount;
633  VuoImage_freeCallback freeCallback;
634  void *freeCallbackContext;
635 } VuoGlTexture;
636 typedef map<GLuint, VuoGlTexture> VuoGlTextureReferenceCounts;
638 static dispatch_semaphore_t VuoGlTexture_referenceCountsSemaphore = NULL;
642 static void __attribute__((constructor(101))) VuoGlTexture_init(void)
643 {
645  VuoGlTexture_referenceCountsSemaphore = dispatch_semaphore_create(1);
646 }
647 
653 void VuoGlTexture_retain(GLuint glTextureName, VuoImage_freeCallback freeCallback, void *freeCallbackContext)
654 {
655 // VLog("%d", glTextureName);
656  if (glTextureName == 0)
657  return;
658 
659  dispatch_semaphore_wait(VuoGlTexture_referenceCountsSemaphore, DISPATCH_TIME_FOREVER);
660 
661  VuoGlTextureReferenceCounts::iterator it = VuoGlTexture_referenceCounts->find(glTextureName);
662  if (it == VuoGlTexture_referenceCounts->end())
663  (*VuoGlTexture_referenceCounts)[glTextureName] = (VuoGlTexture){1, freeCallback, freeCallbackContext};
664  else
665  ++(*it).second.referenceCount;
666 
667  dispatch_semaphore_signal(VuoGlTexture_referenceCountsSemaphore);
668 }
669 
676 void VuoGlTexture_release(VuoGlTexturePoolAllocation allocation, GLuint glTextureTarget, GLenum internalformat, unsigned short width, unsigned short height, GLuint glTextureName)
677 {
678 // VLog(" %-11s %d (%s %s %dx%d)", VuoGlTexturePool_stringForAllocation(allocation), glTextureName, VuoGl_stringForConstant(glTextureTarget), VuoGl_stringForConstant(internalformat), width, height);
679  if (glTextureName == 0)
680  return;
681 
682  dispatch_semaphore_wait(VuoGlTexture_referenceCountsSemaphore, DISPATCH_TIME_FOREVER);
683 
684  VuoGlTextureReferenceCounts::iterator it = VuoGlTexture_referenceCounts->find(glTextureName);
685  if (it == VuoGlTexture_referenceCounts->end())
686  VUserLog("Error: VuoGlTexture_release() was called with OpenGL Texture Object %d, which was never retained.", glTextureName);
687  else
688  {
689  VuoGlTexture t = (*VuoGlTexture_referenceCounts)[glTextureName];
690  if (--t.referenceCount == 0)
691  {
692  if (t.freeCallback)
693  {
694  // Client-owned texture
695  struct _VuoImage i = (struct _VuoImage){glTextureName, internalformat, glTextureTarget, width, height, 1, t.freeCallbackContext, NULL, NULL, NULL};
696  t.freeCallback(&i);
697  }
698  else
699  {
700  // Vuo-owned texture
701  VuoGlTexturePool_disuse(allocation, glTextureTarget, internalformat, width, height, glTextureName);
702  }
703  VuoGlTexture_referenceCounts->erase(it);
704  }
705  else
706  (*VuoGlTexture_referenceCounts)[glTextureName] = t;
707  }
708 
709  dispatch_semaphore_signal(VuoGlTexture_referenceCountsSemaphore);
710 }
711 
721 void VuoGlTexture_disown(GLuint glTextureName)
722 {
723 // VLog("%d", glTextureName);
724  if (glTextureName == 0)
725  return;
726 
727  dispatch_semaphore_wait(VuoGlTexture_referenceCountsSemaphore, DISPATCH_TIME_FOREVER);
728 
729  VuoGlTextureReferenceCounts::iterator it = VuoGlTexture_referenceCounts->find(glTextureName);
730  if (it == VuoGlTexture_referenceCounts->end())
731  VUserLog("Error: VuoGlTexture_disown() was called with OpenGL Texture Object %d, which was never retained.", glTextureName);
732  else
733  {
734  VuoGlTexture t = (*VuoGlTexture_referenceCounts)[glTextureName];
735  if (t.referenceCount != 1)
736  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);
737  else
738  VuoGlTexture_referenceCounts->erase(it);
739  }
740 
741  dispatch_semaphore_signal(VuoGlTexture_referenceCountsSemaphore);
742 }
743 
744 
745 
749 typedef struct
750 {
751  IOSurfaceRef ioSurface;
752  GLuint texture;
753  unsigned short pixelsWide;
754  unsigned short pixelsHigh;
755  double lastUsedTime;
757 typedef pair<unsigned short,unsigned short> VuoGlTextureDimensionsType;
758 typedef map<VuoGlTextureDimensionsType, deque<VuoIoSurfacePoolEntryType> > VuoIoSurfacePoolType;
761 static dispatch_semaphore_t VuoIoSurfacePool_semaphore;
762 static CFStringRef receiverFinishedWithIoSurfaceKey = CFSTR("VuoReceiverFinished");
763 
764 const double VuoGlPool_cleanupInterval = 0.1;
765 static dispatch_source_t VuoGlPool_timer;
766 static dispatch_semaphore_t VuoGlPool_canceledAndCompleted;
767 
768 static unsigned long VuoGlPool_allocatedBytes = 0;
769 static unsigned long VuoGlPool_allocatedBytesMax = 0;
770 
780 void VuoGlPool_logVRAMAllocated(unsigned long bytesAllocated)
781 {
782 // VLog("Alloc %lu", bytesAllocated);
783  __sync_add_and_fetch(&VuoGlPool_allocatedBytes, bytesAllocated);
784 
785  // Not thread-safe, but the worst that could happen is that,
786  // if multiple threads exceed the limit simultaneously,
787  // we might not get the largest of the new maximums.
790 }
791 
801 void VuoGlPool_logVRAMFreed(unsigned long bytesFreed)
802 {
803 // VLog("Freed %lu", bytesFreed);
804  __sync_sub_and_fetch(&VuoGlPool_allocatedBytes, bytesFreed);
805 }
806 
810 static void VuoGlPool_cleanup(void *blah)
811 {
812 // VLog("{");
813  unsigned long totalTextureCount = 0;
814  static unsigned long totalTextureCountMax = 0;
815 
816  vector<GLuint> texturesToDelete;
817  vector<VuoIoSurfacePoolEntryType> iosurfaceTexturesToDisuse;
818 
819  dispatch_semaphore_wait(VuoGlTexturePool_semaphore, DISPATCH_TIME_FOREVER);
820  {
821  double now = VuoLogGetTime();
822  for (VuoGlTexturePoolType::iterator poolItem = VuoGlTexturePool->begin(); poolItem != VuoGlTexturePool->end(); )
823  {
824  unsigned long textureCount = poolItem->second.first.size();
825  double timeSinceLastUsed = now - poolItem->second.second;
826 // VLog(" %s: %lu unused texture%s (last used %.02gs ago)", poolItem->first.toString().c_str(), textureCount, textureCount == 1 ? "" : "s", timeSinceLastUsed);
827  if (timeSinceLastUsed > VuoGlPool_cleanupInterval)
828  {
829  if (textureCount)
830  {
831 // VLog(" Purging %lu expired texture%s", textureCount, textureCount == 1 ? "" : "s");
832  while (!poolItem->second.first.empty())
833  {
834  GLuint textureName = poolItem->second.first.front();
835  poolItem->second.first.pop();
836  texturesToDelete.push_back(textureName);
837  VuoGlPool_logVRAMFreed(poolItem->first.width * poolItem->first.height * VuoGlTexture_getBytesPerPixelForInternalFormat(poolItem->first.internalFormat));
838  }
839  }
840 
841  VuoGlTexturePool->erase(poolItem++);
842  }
843  else
844  {
845  totalTextureCount += textureCount;
846  ++poolItem;
847  }
848  }
849  }
850  dispatch_semaphore_signal(VuoGlTexturePool_semaphore);
851 
852  if (totalTextureCount > totalTextureCountMax)
853  totalTextureCountMax = totalTextureCount;
854 
855 
856  unsigned long totalIOSurfaceCount = 0;
857  static unsigned long totalIOSurfaceCountMax = 0;
858 
859  dispatch_semaphore_wait(VuoIoSurfacePool_semaphore, DISPATCH_TIME_FOREVER);
860  {
861  // Promote IOSurfaces from the quarantine to the pool, if the receiver has finished using them.
862  for (VuoIoSurfacePoolType::iterator quarantinedQueue = VuoIoSurfaceQuarantine->begin(); quarantinedQueue != VuoIoSurfaceQuarantine->end(); ++quarantinedQueue)
863  {
864  for (deque<VuoIoSurfacePoolEntryType>::iterator quarantinedIoSurfaceEntry = quarantinedQueue->second.begin(); quarantinedIoSurfaceEntry != quarantinedQueue->second.end();)
865  {
866  VuoIoSurfacePoolEntryType e = *quarantinedIoSurfaceEntry;
867  CFBooleanRef finished = (CFBooleanRef)IOSurfaceCopyValue(e.ioSurface, receiverFinishedWithIoSurfaceKey);
868  if (finished)
869  {
870  IOSurfaceRemoveValue(e.ioSurface, receiverFinishedWithIoSurfaceKey);
871 
872  (*VuoIoSurfacePool)[quarantinedQueue->first].push_back(e);
873  quarantinedIoSurfaceEntry = quarantinedQueue->second.erase(quarantinedIoSurfaceEntry);
874 
875 // VLog(" Promoted IOSurface %d + GL Texture %d (%dx%d) from quarantine to pool", IOSurfaceGetID(e.ioSurface), e.texture, quarantinedQueue->first.first, quarantinedQueue->first.second);
876  }
877  else
878  {
879  ++totalIOSurfaceCount;
880  ++quarantinedIoSurfaceEntry;
881  }
882  }
883  }
884 
885 
886  double now = VuoLogGetTime();
887  for (VuoIoSurfacePoolType::iterator poolQueue = VuoIoSurfacePool->begin(); poolQueue != VuoIoSurfacePool->end(); ++poolQueue)
888  {
889  for (deque<VuoIoSurfacePoolEntryType>::iterator poolIoSurfaceEntry = poolQueue->second.begin(); poolIoSurfaceEntry != poolQueue->second.end();)
890  {
891  VuoIoSurfacePoolEntryType e = *poolIoSurfaceEntry;
892  if (now - e.lastUsedTime > VuoGlPool_cleanupInterval * 2.)
893  {
894 // 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);
895 
896  CFRelease(e.ioSurface);
897  iosurfaceTexturesToDisuse.push_back(e);
898  poolIoSurfaceEntry = poolQueue->second.erase(poolIoSurfaceEntry);
899  }
900  else
901  {
902  ++totalIOSurfaceCount;
903  ++poolIoSurfaceEntry;
904  }
905  }
906  }
907  }
908  dispatch_semaphore_signal(VuoIoSurfacePool_semaphore);
909 
910  if (totalIOSurfaceCount > totalIOSurfaceCountMax)
911  totalIOSurfaceCountMax = totalIOSurfaceCount;
912 
913 
914  // Delete textures after we've released the pool semaphores, to avoid deadlock, and to avoid hogging the shared GL context.
915  if (texturesToDelete.size())
916  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
917  for (vector<GLuint>::const_iterator i = texturesToDelete.begin(); i != texturesToDelete.end(); ++i)
918  {
919  GLuint t = *i;
920  glDeleteTextures(1, &t);
921  // VuoGlPool_logVRAMFreed was called above when texturesToDelete was being built.
922  }
923  });
924  for (auto e : iosurfaceTexturesToDisuse)
925  VuoGlTexturePool_disuse(VuoGlTexturePool_AllocateIOSurface, GL_TEXTURE_RECTANGLE_ARB, GL_RGBA, e.pixelsWide, e.pixelsHigh, e.texture);
926 // VLog("}");
927 
928 
929  // Log VRAM use every 10 seconds.
930  static unsigned long cleanupCount = 0;
931  if ((++cleanupCount % (long)(10. / VuoGlPool_cleanupInterval) == 0)
932  && VuoIsDebugEnabled()
934  || totalTextureCount > 0 || totalTextureCountMax > 0
935  || totalIOSurfaceCount > 0 || totalIOSurfaceCountMax > 0))
936  {
937  __block unsigned long maximumTextureBytes;
938  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
939  maximumTextureBytes = VuoGlTexture_getMaximumTextureBytes(cgl_ctx);
940  });
941 
942  if (maximumTextureBytes > 0)
943  VUserLog("VRAM used: %5lu MB (%5lu MB max, %3lu%%). Textures in pool: %3lu (%3lu max). IOSurfaces in pool: %3lu (%3lu max).",
944  VuoGlPool_allocatedBytes/1024/1024,
945  VuoGlPool_allocatedBytesMax/1024/1024,
946  VuoGlPool_allocatedBytesMax*100/maximumTextureBytes,
947  totalTextureCount, totalTextureCountMax,
948  totalIOSurfaceCount, totalIOSurfaceCountMax);
949  else
950  VUserLog("VRAM used: %5lu MB (%5lu MB max). Textures in pool: %3lu (%3lu max). IOSurfaces in pool: %3lu (%3lu max).",
951  VuoGlPool_allocatedBytes/1024/1024,
952  VuoGlPool_allocatedBytesMax/1024/1024,
953  totalTextureCount, totalTextureCountMax,
954  totalIOSurfaceCount, totalIOSurfaceCountMax);
955  }
956 }
957 static void __attribute__((constructor)) VuoGlPool_init(void)
958 {
959  VuoGlPool_semaphore = dispatch_semaphore_create(1);
960 
961  VuoGlTexturePool_semaphore = dispatch_semaphore_create(1);
963 
964  VuoIoSurfacePool_semaphore = dispatch_semaphore_create(1);
967 
968  VuoGlPool_canceledAndCompleted = dispatch_semaphore_create(0);
969  VuoGlPool_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
970  dispatch_source_set_timer(VuoGlPool_timer, dispatch_walltime(NULL,0), NSEC_PER_SEC * VuoGlPool_cleanupInterval, NSEC_PER_SEC * VuoGlPool_cleanupInterval);
971  dispatch_source_set_event_handler_f(VuoGlPool_timer, VuoGlPool_cleanup);
972  dispatch_source_set_cancel_handler(VuoGlPool_timer, ^{
973  dispatch_semaphore_signal(VuoGlPool_canceledAndCompleted);
974  });
975  dispatch_resume(VuoGlPool_timer);
976 }
980 static void __attribute__((destructor)) VuoGlPool_fini(void)
981 {
982  dispatch_source_cancel(VuoGlPool_timer);
983 
984  // Wait for the last cleanup to complete.
985  dispatch_semaphore_wait(VuoGlPool_canceledAndCompleted, DISPATCH_TIME_FOREVER);
986 
987  delete VuoGlTexturePool;
988 
989  delete VuoIoSurfacePool;
990  delete VuoIoSurfaceQuarantine;
991 }
992 
997 VuoIoSurface VuoIoSurfacePool_use(VuoGlContext glContext, unsigned short pixelsWide, unsigned short pixelsHigh, GLuint *outputTexture)
998 {
999  VuoIoSurface ioSurface = NULL;
1000  VuoGlTextureDimensionsType dimensions(pixelsWide,pixelsHigh);
1001 
1002  dispatch_semaphore_wait(VuoIoSurfacePool_semaphore, DISPATCH_TIME_FOREVER);
1003  {
1004  if ((*VuoIoSurfacePool)[dimensions].size())
1005  {
1006  VuoIoSurfacePoolEntryType e = (*VuoIoSurfacePool)[dimensions].front();
1007  ioSurface = malloc(sizeof(VuoIoSurfacePoolEntryType));
1008  memcpy(ioSurface, &e, sizeof(VuoIoSurfacePoolEntryType));
1009  *outputTexture = e.texture;
1010  (*VuoIoSurfacePool)[dimensions].pop_front();
1011 // VLog(" Using recycled IOSurface %d + GL Texture %d (%dx%d) from pool", IOSurfaceGetID(e.ioSurface), *outputTexture, pixelsWide, pixelsHigh);
1012  }
1013  }
1014  dispatch_semaphore_signal(VuoIoSurfacePool_semaphore);
1015 
1016  if (!ioSurface)
1017  {
1018  CFMutableDictionaryRef properties = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL);
1019 
1020 #pragma clang diagnostic push
1021 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
1024  CFDictionaryAddValue(properties, kIOSurfaceIsGlobal, kCFBooleanTrue);
1025 #pragma clang diagnostic pop
1026 
1027  long long pixelsWideLL = pixelsWide;
1028  CFNumberRef pixelsWideCF = CFNumberCreate(NULL, kCFNumberLongLongType, &pixelsWideLL);
1029  CFDictionaryAddValue(properties, kIOSurfaceWidth, pixelsWideCF);
1030 
1031  long long pixelsHighLL = pixelsHigh;
1032  CFNumberRef pixelsHighCF = CFNumberCreate(NULL, kCFNumberLongLongType, &pixelsHighLL);
1033  CFDictionaryAddValue(properties, kIOSurfaceHeight, pixelsHighCF);
1034 
1035  long long bytesPerElement = 4;
1036  CFNumberRef bytesPerElementCF = CFNumberCreate(NULL, kCFNumberLongLongType, &bytesPerElement);
1037  CFDictionaryAddValue(properties, kIOSurfaceBytesPerElement, bytesPerElementCF);
1038 
1039  uint32_t pixelFormat = 'BGRA'; // kCVPixelFormatType_32BGRA;
1040  CFNumberRef pixelFormatCF = CFNumberCreate(NULL, kCFNumberSInt32Type, &pixelFormat);
1041  CFDictionaryAddValue(properties, kIOSurfacePixelFormat, pixelFormatCF);
1042 
1043  CGLContextObj cgl_ctx = (CGLContextObj)glContext;
1044 
1046  ioSurface = e;
1047  e->ioSurface = IOSurfaceCreate(properties);
1048  e->pixelsWide = pixelsWide;
1049  e->pixelsHigh = pixelsHigh;
1050  e->lastUsedTime = -1;
1051  CFRelease(pixelsWideCF);
1052  CFRelease(pixelsHighCF);
1053  CFRelease(bytesPerElementCF);
1054  CFRelease(properties);
1055 
1056  *outputTexture = VuoGlTexturePool_use(cgl_ctx, VuoGlTexturePool_AllocateIOSurface, GL_TEXTURE_RECTANGLE_ARB, GL_RGBA, pixelsWide, pixelsHigh, GL_BGRA, e->ioSurface);
1057  e->texture = *outputTexture;
1058 
1059 // VLog(" Created IOSurface %d (%dx%d)", IOSurfaceGetID(e->ioSurface), pixelsWide, pixelsHigh);
1060  }
1061 
1062  return ioSurface;
1063 }
1064 
1069 {
1071  return IOSurfaceGetID(e->ioSurface);
1072 }
1073 
1078 {
1080  return e->ioSurface;
1081 }
1082 
1087 {
1089  return e->pixelsWide;
1090 }
1091 
1096 {
1098  return e->pixelsHigh;
1099 }
1100 
1105 {
1107  return e->texture;
1108 }
1109 
1122 void VuoIoSurfacePool_disuse(VuoIoSurface vis, bool quarantine)
1123 {
1125 // VLog(" Sender disusing IOSurface %d + GL Texture %d (%dx%d)", IOSurfaceGetID(e->ioSurface), e->texture, e->pixelsWide, e->pixelsHigh);
1126  VuoGlTextureDimensionsType dimensions(e->pixelsWide, e->pixelsHigh);
1127  e->lastUsedTime = VuoLogGetTime();
1128 
1129  dispatch_semaphore_wait(VuoIoSurfacePool_semaphore, DISPATCH_TIME_FOREVER);
1130  {
1131  if (quarantine)
1132  (*VuoIoSurfaceQuarantine)[dimensions].push_back(*e);
1133  else
1134  (*VuoIoSurfacePool)[dimensions].push_back(*e);
1135  }
1136  dispatch_semaphore_signal(VuoIoSurfacePool_semaphore);
1137 
1138  free(e);
1139 }
1140 
1146 {
1147  IOSurfaceRef ioSurface = (IOSurfaceRef)ios;
1148  IOSurfaceSetValue(ioSurface, receiverFinishedWithIoSurfaceKey, kCFBooleanTrue);
1149 }
1150 
1151 
1152 
1158 void VuoGlShader_parseShaderInfoLog(CGLContextObj cgl_ctx, GLenum type, GLuint obj, const GLchar *source, VuoShaderFile::Stage stage, VuoShaderIssues *outIssues)
1159 {
1160  int infologLength = 0;
1161  glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infologLength);
1162  if (infologLength > 0)
1163  {
1164  char *infoLog = (char *)malloc(infologLength);
1165  int charsWritten = 0;
1166  glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
1167 
1168  if (outIssues)
1169  {
1170  istringstream iss(infoLog);
1171  string ln;
1172  while (getline(iss, ln))
1173  {
1174  int lineNumber = 0;
1175  char *message = (char *)malloc(ln.length() + 1);
1176  char *quotation = (char *)malloc(ln.length() + 1);
1177  if (sscanf(ln.c_str(), "ERROR: 0:%d: '' : %[^\n]", &lineNumber, message) == 2)
1178  outIssues->addIssue(stage, lineNumber, message);
1179  else if (sscanf(ln.c_str(), "ERROR: 0:%d: '%[^']' : syntax error: %[^\n]", &lineNumber, quotation, message) == 3)
1180  outIssues->addIssue(stage, lineNumber, message + string(": '") + quotation + "'");
1181  else if (sscanf(ln.c_str(), "ERROR: 0:%d: %[^\n]", &lineNumber, message) == 2)
1182  outIssues->addIssue(stage, lineNumber, message);
1183  else if (sscanf(ln.c_str(), "WARNING: 0:%d: %[^\n]", &lineNumber, message) == 2)
1184  outIssues->addIssue(stage, lineNumber, message);
1185  else
1186  {
1187  VUserLog("Warning: Couldn't parse GLSL log message: \"%s\"", ln.c_str());
1188  outIssues->addIssue(stage, VuoShaderIssues::NoLine, ln);
1189  }
1190  free(message);
1191  free(quotation);
1192  }
1193  }
1194  else
1195  {
1196  VUserLog("%s", infoLog);
1197  VDebugLog("Source code:\n%s", source);
1198  }
1199 
1200  free(infoLog);
1201  }
1202 }
1203 
1204 map<GLenum, map<long, GLuint> > VuoGlShaderPool __attribute__((init_priority(101)));
1205 dispatch_semaphore_t VuoGlShaderPool_semaphore = NULL;
1206 
1210 __attribute__((constructor)) static void VuoGlShaderPool_init(void)
1211 {
1212  VuoGlShaderPool_semaphore = dispatch_semaphore_create(1);
1213 }
1214 
1220 static bool VuoGlShader_resolveIncludes(string &source, VuoShaderFile::Stage stage, VuoShaderIssues *outIssues)
1221 {
1222  const char *cwd = VuoGetWorkingDirectory();
1223  const string vuoFrameworkPath = VuoFileUtilities::getVuoFrameworkPath();
1224  const string vuoRunnerFrameworkPath = VuoFileUtilities::getVuoRunnerFrameworkPath();
1225  string userModulesPath = VuoFileUtilities::getUserModulesPath();
1226  if (!VuoFileUtilities::dirExists(userModulesPath))
1227  userModulesPath = "";
1228  string systemModulesPath = VuoFileUtilities::getSystemModulesPath();
1229  if (!VuoFileUtilities::dirExists(systemModulesPath))
1230  systemModulesPath = "";
1231 
1232  set<string> includedFiles;
1233 
1234  while (1)
1235  {
1236  const string includeKeyword = "#include";
1237 
1238  size_t includeStart = source.find(includeKeyword);
1239  if (includeStart == string::npos)
1240  return true;
1241 
1242  size_t offset = includeStart + includeKeyword.length();
1243 
1244  while (source[offset] == ' ')
1245  ++offset;
1246 
1247  if (source[offset] != '"'
1248  && source[offset] != '<')
1249  {
1251  if (outIssues)
1252  outIssues->addIssue(stage, VuoShaderIssues::NoLine, "Syntax error in #include statement. Expected syntax: #include \"filename.glsl\"");
1253  else
1254  VUserLog("Error: Syntax error in #include statement. Expected syntax: #include \"filename.glsl\"");
1255  return false;
1256  }
1257  ++offset;
1258 
1259  size_t filenameStart = offset;
1260 
1261  while (source[offset] != '"'
1262  && source[offset] != '>')
1263  ++offset;
1264 
1265  size_t filenameEnd = offset;
1266 
1267  string filename = source.substr(filenameStart, filenameEnd - filenameStart);
1268 
1269  vector<string> pathsToTest;
1270  if (strcmp(cwd, "/") != 0)
1271  pathsToTest.push_back(cwd + string("/") + filename);
1272  if (!vuoFrameworkPath.empty())
1273  pathsToTest.push_back(vuoFrameworkPath + "/Resources/shaders/" + filename);
1274  if (!vuoRunnerFrameworkPath.empty())
1275  pathsToTest.push_back(vuoRunnerFrameworkPath + "/Resources/shaders/" + filename);
1276  if (!userModulesPath.empty())
1277  pathsToTest.push_back(userModulesPath + "/" + filename);
1278  if (!systemModulesPath.empty())
1279  pathsToTest.push_back(systemModulesPath + "/" + filename);
1280  bool found = false;
1281  for (vector<string>::iterator i = pathsToTest.begin(); i != pathsToTest.end(); ++i)
1282  {
1284  {
1285  found = true;
1286 
1287  source.erase(includeStart, offset - includeStart + 1);
1288 
1289  // Prevent recursive includes.
1290  if (includedFiles.count(*i))
1291  {
1292  source.insert(includeStart,
1293  "\n"
1294  "// ============================================================\n"
1295  "// Skipped including file \"" + *i + "\" since it was already included.\n"
1296  "// ============================================================\n"
1297  "\n"
1298  );
1299  break;
1300  }
1301  includedFiles.insert(*i);
1302 
1303  source.insert(includeStart,
1304  "\n"
1305  "// ============================================================\n"
1306  "// Begin included file \"" + *i + "\"\n"
1308  + "\n"
1309  "// End included file \"" + *i + "\"\n"
1310  "// ============================================================\n"
1311  "\n"
1312  );
1313  break;
1314  }
1315  }
1316 
1317  if (!found)
1318  {
1320  if (outIssues)
1321  outIssues->addIssue(stage, VuoShaderIssues::NoLine, "Couldn't find include file \"" + filename + "\"");
1322  else
1323  VUserLog("Error: Couldn't find include file \"%s\".", filename.c_str());
1324  return false;
1325  }
1326  }
1327 }
1328 
1329 static const std::locale VuoGlPool_locale;
1330 static const std::collate<char> &VuoGlPool_collate = std::use_facet<std::collate<char> >(VuoGlPool_locale);
1331 
1349 GLuint VuoGlShader_use(VuoGlContext glContext, GLenum type, const char *source, void *outIssues)
1350 {
1351  long hash = VuoGlPool_collate.hash(source, source+strlen(source));
1352 
1353  dispatch_semaphore_wait(VuoGlShaderPool_semaphore, DISPATCH_TIME_FOREVER);
1354 
1355  GLuint shader;
1356  if (!outIssues && VuoGlShaderPool[type].find(hash) != VuoGlShaderPool[type].end())
1357  shader = VuoGlShaderPool[type][hash];
1358  else
1359  {
1360  CGLContextObj cgl_ctx = (CGLContextObj)glContext;
1361 
1362  string combinedSource = source;
1363 
1364  VuoShaderIssues *oi = static_cast<VuoShaderIssues *>(outIssues);
1365  VuoShaderFile::Stage stage;
1366  if (type == GL_VERTEX_SHADER)
1367  stage = VuoShaderFile::Vertex;
1368  else if (type == GL_GEOMETRY_SHADER_EXT)
1369  stage = VuoShaderFile::Geometry;
1370  else // if (type == GL_FRAGMENT_SHADER)
1371  stage = VuoShaderFile::Fragment;
1372 
1373  if (!VuoGlShader_resolveIncludes(combinedSource, stage, oi))
1374  {
1375  shader = 0;
1376  goto done;
1377  }
1378 
1379 // VLog("\n%s\n\n", combinedSource.c_str());
1380 
1381  shader = glCreateShader(type);
1382  GLint length = combinedSource.length();
1383  const GLchar *combinedSourceCString = combinedSource.c_str();
1384  glShaderSource(shader, 1, (const GLchar**)&combinedSourceCString, &length);
1385  glCompileShader(shader);
1386  VuoGlShader_parseShaderInfoLog(cgl_ctx, type, shader, combinedSourceCString, stage, oi);
1387 
1388  GLint status = GL_FALSE;
1389  glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
1390  if (status == GL_FALSE)
1391  {
1392  glDeleteShader(shader);
1393  shader = 0;
1394  goto done;
1395  }
1396 
1397  VuoGlShaderPool[type][hash] = shader;
1398  }
1399 
1400 done:
1401  dispatch_semaphore_signal(VuoGlShaderPool_semaphore);
1402 
1403  return shader;
1404 }
1405 
1406 // Too bad we can't use std::tuple yet…
1407 typedef pair<GLuint, pair<GLuint, pair<GLuint, pair<VuoMesh_ElementAssemblyMethod, unsigned int> > > > VuoGlProgramDescriptorType;
1408 typedef map<VuoGlProgramDescriptorType, VuoGlProgram> VuoGlProgramPoolType;
1410 static dispatch_semaphore_t VuoGlProgramPool_semaphore;
1411 static void __attribute__((constructor)) VuoGlProgramPool_init(void)
1412 {
1413  VuoGlProgramPool_semaphore = dispatch_semaphore_create(1);
1414 }
1415 
1416 typedef std::map<long, GLuint> VuoGlUniformMap;
1417 
1435 VuoGlProgram VuoGlProgram_use(VuoGlContext glContext, const char *description, GLuint vertexShaderName, GLuint geometryShaderName, GLuint fragmentShaderName, VuoMesh_ElementAssemblyMethod assemblyMethod, unsigned int expectedOutputPrimitiveCount, void *outIssues)
1436 {
1437 // VLog("looking for %d %d %d %d %d", vertexShaderName, geometryShaderName, fragmentShaderName, assemblyMethod, expectedOutputPrimitiveCount);
1438  VuoGlProgram program;
1439  VuoGlProgramDescriptorType e(vertexShaderName, std::make_pair(geometryShaderName, std::make_pair(fragmentShaderName, std::make_pair(assemblyMethod, expectedOutputPrimitiveCount))));
1440  dispatch_semaphore_wait(VuoGlProgramPool_semaphore, DISPATCH_TIME_FOREVER);
1441  VuoGlProgramPoolType::iterator it = VuoGlProgramPool.find(e);
1442  if (!outIssues && it != VuoGlProgramPool.end())
1443  program = it->second;
1444  else
1445  {
1446  CGLContextObj cgl_ctx = (CGLContextObj)glContext;
1447 
1448  GLuint programName = glCreateProgram();
1449  glAttachShader(programName, vertexShaderName);
1450  if (geometryShaderName)
1451  glAttachShader(programName, geometryShaderName);
1452  if (fragmentShaderName)
1453  glAttachShader(programName, fragmentShaderName);
1454 
1455  bool transformFeedback = false;
1456  // If there's no fragment shader, this program is being used for transform feedback.
1457  if (!fragmentShaderName)
1458  transformFeedback = true;
1459 
1460  // Make sure `position` is at location 0, since location 0 is required in order for glDraw*() to work.
1461  glBindAttribLocation(programName, 0, "position");
1462 
1463  if (geometryShaderName)
1464  {
1465  GLuint inputPrimitiveGlMode = GL_TRIANGLES;
1466  if (assemblyMethod == VuoMesh_IndividualLines
1467  || assemblyMethod == VuoMesh_LineStrip)
1468  inputPrimitiveGlMode = GL_LINES;
1469  else if (assemblyMethod == VuoMesh_Points)
1470  inputPrimitiveGlMode = GL_POINTS;
1471  glProgramParameteriEXT(programName, GL_GEOMETRY_INPUT_TYPE_EXT, inputPrimitiveGlMode);
1472 
1473  GLuint outputPrimitiveGlMode = GL_TRIANGLE_STRIP;
1474  if (transformFeedback)
1475  {
1476  // In transform feedback, the output primitive mode needs to match the input primitive mode.
1477  if (assemblyMethod == VuoMesh_IndividualLines
1478  || assemblyMethod == VuoMesh_LineStrip)
1479  outputPrimitiveGlMode = GL_LINE_STRIP;
1480  else if (assemblyMethod == VuoMesh_Points)
1481  outputPrimitiveGlMode = GL_POINTS;
1482  }
1483  glProgramParameteriEXT(programName, GL_GEOMETRY_OUTPUT_TYPE_EXT, outputPrimitiveGlMode);
1484 
1485  unsigned int expectedVertexCount = expectedOutputPrimitiveCount;
1486  if (outputPrimitiveGlMode == GL_TRIANGLE_STRIP)
1487  expectedVertexCount *= 3;
1488  else if (outputPrimitiveGlMode == GL_LINE_STRIP)
1489  expectedVertexCount *= 2;
1490  glProgramParameteriEXT(programName, GL_GEOMETRY_VERTICES_OUT_EXT, expectedVertexCount);
1491  }
1492 
1493  if (transformFeedback)
1494  {
1495  const GLchar *varyings[] = { "outPosition", "outNormal", "outTextureCoordinate", "outVertexColor" };
1496  glTransformFeedbackVaryingsEXT(programName, 4, varyings, GL_SEPARATE_ATTRIBS_EXT);
1497  }
1498 
1499  glLinkProgram(programName);
1500 
1501  {
1502  int infologLength = 0;
1503  glGetProgramiv(programName, GL_INFO_LOG_LENGTH, &infologLength);
1504  if (infologLength > 0)
1505  {
1506  char *infoLog = (char *)malloc(infologLength);
1507  int charsWritten = 0;
1508  glGetProgramInfoLog(programName, infologLength, &charsWritten, infoLog);
1509 
1510  if (outIssues)
1511  {
1512  VuoShaderIssues *oi = static_cast<VuoShaderIssues *>(outIssues);
1513 
1514  istringstream iss(infoLog);
1515  string ln;
1516  while (getline(iss, ln))
1517  {
1518  string s;
1519  if ( !(s = VuoStringUtilities::substrAfter(ln, "ERROR: ")).empty() )
1520  oi->addIssue(VuoShaderFile::Program, VuoShaderIssues::NoLine, s);
1521  else if ( !(s = VuoStringUtilities::substrAfter(ln, "WARNING: ")).empty() )
1522  oi->addIssue(VuoShaderFile::Program, VuoShaderIssues::NoLine, s);
1523  else
1524  {
1525  VUserLog("Warning: Couldn't parse GLSL log message: \"%s\"", ln.c_str());
1526  oi->addIssue(VuoShaderFile::Program, VuoShaderIssues::NoLine, ln);
1527  }
1528  }
1529  }
1530  else
1531  VUserLog("%s: %s", description, infoLog);
1532 
1533  free(infoLog);
1534  }
1535  }
1536 
1537  int linkStatus = 0;
1538  glGetProgramiv(programName, GL_LINK_STATUS, &linkStatus);
1539  if (linkStatus == GL_FALSE)
1540  {
1541  glDeleteProgram(programName);
1542  dispatch_semaphore_signal(VuoGlProgramPool_semaphore);
1543  return (VuoGlProgram){0,NULL};
1544  }
1545 
1546  program.programName = programName;
1547 
1548  GLint uniformCount;
1549  glGetProgramiv(programName, GL_ACTIVE_UNIFORMS, &uniformCount);
1550 
1551  GLint maxNameLength;
1552  glGetProgramiv(programName, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxNameLength);
1553  char *name = (char *)malloc(maxNameLength + 1);
1554 
1555  VuoGlUniformMap *uniforms = new VuoGlUniformMap;
1556  program.uniforms = (void *)uniforms;
1557 
1558  for (GLuint i = 0; i < uniformCount; ++i)
1559  {
1560  GLint size;
1561  glGetActiveUniform(programName, i, maxNameLength+1, NULL, &size, NULL, name);
1562 
1563  // The uniform location is _not_ the same as the active uniform index!
1564  size_t nameLen = strlen(name);
1565  long hash = VuoGlPool_collate.hash(name, name+nameLen);
1566  (*uniforms)[hash] = glGetUniformLocation(programName, name);
1567 
1568  if (size > 1)
1569  {
1570  // For arrays, glGetActiveUniform() returns only the first array index. Take care of the rest of the indices.
1571  // E.g., `uniform float offset[3];` would return `offset[0]` with size 3, so we need to synthesize `offset[1]` and `offset[2]`.
1572  if (name[nameLen-2] == '0' && name[nameLen-1] == ']')
1573  {
1574  name[nameLen-2] = 0;
1575  for (int i = 1; i < size; ++i)
1576  {
1577  std::stringstream ss;
1578  ss << name << i << "]";
1579  string sss = ss.str();
1580  long sHash = VuoGlPool_collate.hash(sss.data(), sss.data()+sss.length());
1581  (*uniforms)[sHash] = glGetUniformLocation(programName, sss.c_str());
1582  }
1583  }
1584  }
1585  }
1586 
1587  VuoGlProgramPool[e] = program;
1588  }
1589 
1590  dispatch_semaphore_signal(VuoGlProgramPool_semaphore);
1591  return program;
1592 }
1593 
1597 int VuoGlProgram_getUniformLocation(VuoGlProgram program, const char *uniformIdentifier)
1598 {
1599  VuoGlUniformMap *uniforms = (VuoGlUniformMap *)program.uniforms;
1600 
1601  long hash = VuoGlPool_collate.hash(uniformIdentifier, uniformIdentifier+strlen(uniformIdentifier));
1602 
1603  VuoGlUniformMap::iterator i = uniforms->find(hash);
1604  if (i != uniforms->end())
1605  return i->second;
1606 
1607  return -1;
1608 }
1609 
1611 #define RETURN_STRING_IF_EQUAL(value) if (constant == value) return strdup(#value)
1612 
1618 char *VuoGl_stringForConstant(GLenum constant)
1619 {
1620  if (constant == 0)
1621  return strdup("(GL_ZERO or GL_POINTS)");
1622  if (constant == 1)
1623  return strdup("(GL_ONE or GL_LINES)");
1624  RETURN_STRING_IF_EQUAL(GL_LINE_LOOP);
1625  RETURN_STRING_IF_EQUAL(GL_LINE_STRIP);
1626  RETURN_STRING_IF_EQUAL(GL_TRIANGLES);
1627  RETURN_STRING_IF_EQUAL(GL_TRIANGLE_STRIP);
1628  RETURN_STRING_IF_EQUAL(GL_TRIANGLE_FAN);
1629  RETURN_STRING_IF_EQUAL(GL_QUADS);
1630  RETURN_STRING_IF_EQUAL(GL_QUAD_STRIP);
1631  RETURN_STRING_IF_EQUAL(GL_POLYGON);
1632  RETURN_STRING_IF_EQUAL(GL_BACK);
1633  RETURN_STRING_IF_EQUAL(GL_FRONT);
1634  RETURN_STRING_IF_EQUAL(GL_R8);
1635  RETURN_STRING_IF_EQUAL(GL_RED);
1636  RETURN_STRING_IF_EQUAL(GL_RGB);
1637  RETURN_STRING_IF_EQUAL(GL_RGB16);
1638  RETURN_STRING_IF_EQUAL(GL_RGB16F_ARB);
1639  RETURN_STRING_IF_EQUAL(GL_RGB32F_ARB);
1640  RETURN_STRING_IF_EQUAL(GL_RGBA);
1641  RETURN_STRING_IF_EQUAL(GL_RGBA8);
1642  RETURN_STRING_IF_EQUAL(GL_RGBA16);
1643  RETURN_STRING_IF_EQUAL(GL_RGBA16F_ARB);
1644  RETURN_STRING_IF_EQUAL(GL_RGBA32F_ARB);
1645  RETURN_STRING_IF_EQUAL(GL_BGRA);
1646  RETURN_STRING_IF_EQUAL(GL_BGR_EXT);
1647  RETURN_STRING_IF_EQUAL(GL_LUMINANCE);
1648  RETURN_STRING_IF_EQUAL(GL_LUMINANCE8);
1649  RETURN_STRING_IF_EQUAL(GL_LUMINANCE16);
1650  RETURN_STRING_IF_EQUAL(GL_LUMINANCE16F_ARB);
1651  RETURN_STRING_IF_EQUAL(GL_LUMINANCE16I_EXT);
1652  RETURN_STRING_IF_EQUAL(GL_LUMINANCE32F_ARB);
1653  RETURN_STRING_IF_EQUAL(GL_LUMINANCE8_ALPHA8);
1654  RETURN_STRING_IF_EQUAL(GL_LUMINANCE16_ALPHA16);
1655  RETURN_STRING_IF_EQUAL(GL_LUMINANCE_ALPHA16F_ARB);
1656  RETURN_STRING_IF_EQUAL(GL_LUMINANCE_ALPHA32F_ARB);
1657  RETURN_STRING_IF_EQUAL(GL_LUMINANCE_ALPHA);
1658  RETURN_STRING_IF_EQUAL(GL_DEPTH_COMPONENT);
1659  RETURN_STRING_IF_EQUAL(GL_DEPTH_COMPONENT16);
1660  RETURN_STRING_IF_EQUAL(GL_TEXTURE_2D);
1661  RETURN_STRING_IF_EQUAL(GL_TEXTURE_RECTANGLE_ARB);
1662  RETURN_STRING_IF_EQUAL(GL_FLOAT);
1663  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_BYTE);
1664  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_BYTE_3_3_2);
1665  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT);
1666  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_4_4_4_4);
1667  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_5_5_5_1);
1668  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_8_8_APPLE);
1669  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_INT_8_8_8_8);
1670  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_INT_10_10_10_2);
1671  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_BYTE_2_3_3_REV);
1672  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_5_6_5);
1673  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_5_6_5_REV);
1674  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_4_4_4_4_REV);
1675  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_SHORT_1_5_5_5_REV);
1676  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_INT_8_8_8_8_REV);
1677  RETURN_STRING_IF_EQUAL(GL_UNSIGNED_INT_2_10_10_10_REV);
1678  RETURN_STRING_IF_EQUAL(GL_COMPRESSED_RGB_S3TC_DXT1_EXT);
1679  RETURN_STRING_IF_EQUAL(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT);
1680  RETURN_STRING_IF_EQUAL(GL_COMPRESSED_RED_RGTC1);
1681  RETURN_STRING_IF_EQUAL(GL_YCBCR_422_APPLE);
1682  RETURN_STRING_IF_EQUAL(GL_ALREADY_SIGNALED);
1683  RETURN_STRING_IF_EQUAL(GL_TIMEOUT_EXPIRED);
1684  RETURN_STRING_IF_EQUAL(GL_CONDITION_SATISFIED);
1685  RETURN_STRING_IF_EQUAL(GL_WAIT_FAILED);
1686 
1687  return VuoText_format("(unknown: %x)", constant);
1688 }