Vuo  2.4.0
VuoGlPool.cc
Go to the documentation of this file.
1
10#include "VuoGlPool.h"
11extern "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>
26using 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
41static map<VuoGlPoolType, map<unsigned long, vector<GLuint> > > VuoGlPool __attribute__((init_priority(101)));
42static dispatch_semaphore_t VuoGlPool_semaphore;
43
54GLuint 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
97void 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
109typedef map<GLuint, unsigned int> VuoGlPoolReferenceCounts;
110static VuoGlPoolReferenceCounts VuoGlPool_referenceCounts __attribute__((init_priority(101)));
111static dispatch_semaphore_t VuoGlPool_referenceCountsSemaphore = NULL;
112static void __attribute__((constructor)) VuoGlPool_referenceCountsInit(void)
113{
114 VuoGlPool_referenceCountsSemaphore = dispatch_semaphore_create(1);
115}
116
120void 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(VuoLog_moduleName, file, linenumber, func, "VuoGlPool_retain(%d)", glBufferName);
135}
136
140void 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(VuoLog_moduleName, 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(VuoLog_moduleName, file, linenumber, func, "VuoGlPool_release(%d)", glBufferName);
158}
159
160
161
162
163
168{
169public:
170 GLenum target;
172 unsigned short width;
173 unsigned short height;
174
178 VuoGlTextureDescriptor(GLenum target, GLenum internalFormat, unsigned short width, unsigned short height)
179 : target(target),
181 width(width),
183 {
184 }
185
189 bool operator<(const VuoGlTextureDescriptor &that) const
190 {
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};
210typedef pair<queue<GLuint>,double> VuoGlTextureLastUsed;
211typedef map<VuoGlTextureDescriptor, VuoGlTextureLastUsed> VuoGlTexturePoolType;
213static dispatch_semaphore_t VuoGlTexturePool_semaphore;
214
215
219GLuint 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
247unsigned 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
282unsigned 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
318unsigned 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 VUserLog("%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
479GLuint 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
589void 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
630typedef struct
631{
632 unsigned int referenceCount;
633 VuoImage_freeCallback freeCallback;
634 void *freeCallbackContext;
636typedef map<GLuint, VuoGlTexture> VuoGlTextureReferenceCounts;
638static dispatch_semaphore_t VuoGlTexture_referenceCountsSemaphore = NULL;
642static void __attribute__((constructor(101))) VuoGlTexture_init(void)
643{
645 VuoGlTexture_referenceCountsSemaphore = dispatch_semaphore_create(1);
646}
647
653void 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
676void 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 }
704 }
705 else
706 (*VuoGlTexture_referenceCounts)[glTextureName] = t;
707 }
708
709 dispatch_semaphore_signal(VuoGlTexture_referenceCountsSemaphore);
710}
711
721void 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
739 }
740
741 dispatch_semaphore_signal(VuoGlTexture_referenceCountsSemaphore);
742}
743
744
745
749typedef struct
750{
751 IOSurfaceRef ioSurface;
752 GLuint texture;
753 unsigned short pixelsWide;
754 unsigned short pixelsHigh;
755 double lastUsedTime;
757typedef pair<unsigned short,unsigned short> VuoGlTextureDimensionsType;
758typedef map<VuoGlTextureDimensionsType, deque<VuoIoSurfacePoolEntryType> > VuoIoSurfacePoolType;
761static dispatch_semaphore_t VuoIoSurfacePool_semaphore;
762static CFStringRef receiverFinishedWithIoSurfaceKey = CFSTR("VuoReceiverFinished");
763
764const double VuoGlPool_cleanupInterval = 0.1;
765static dispatch_source_t VuoGlPool_timer;
766static dispatch_semaphore_t VuoGlPool_canceledAndCompleted;
767
768static unsigned long VuoGlPool_allocatedBytes = 0;
769static unsigned long VuoGlPool_allocatedBytesMax = 0;
770
780void 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
801void VuoGlPool_logVRAMFreed(unsigned long bytesFreed)
802{
803// VLog("Freed %lu", bytesFreed);
804 __sync_sub_and_fetch(&VuoGlPool_allocatedBytes, bytesFreed);
805}
806
810static 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)
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,
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,
953 totalTextureCount, totalTextureCountMax,
954 totalIOSurfaceCount, totalIOSurfaceCountMax);
955 }
956}
957static 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}
980static 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;
991}
992
997VuoIoSurface 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
1122void 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
1158void 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
1204map<GLenum, map<long, GLuint> > VuoGlShaderPool __attribute__((init_priority(101)));
1205dispatch_semaphore_t VuoGlShaderPool_semaphore = NULL;
1206
1210__attribute__((constructor)) static void VuoGlShaderPool_init(void)
1211{
1212 VuoGlShaderPool_semaphore = dispatch_semaphore_create(1);
1213}
1214
1220static 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
1329static const std::locale VuoGlPool_locale;
1330static const std::collate<char> &VuoGlPool_collate = std::use_facet<std::collate<char> >(VuoGlPool_locale);
1331
1349GLuint 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);
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
1400done:
1401 dispatch_semaphore_signal(VuoGlShaderPool_semaphore);
1402
1403 return shader;
1404}
1405
1406// Too bad we can't use std::tuple yet…
1407typedef pair<GLuint, pair<GLuint, pair<GLuint, pair<VuoMesh_ElementAssemblyMethod, unsigned int> > > > VuoGlProgramDescriptorType;
1408typedef map<VuoGlProgramDescriptorType, VuoGlProgram> VuoGlProgramPoolType;
1410static dispatch_semaphore_t VuoGlProgramPool_semaphore;
1411static void __attribute__((constructor)) VuoGlProgramPool_init(void)
1412{
1413 VuoGlProgramPool_semaphore = dispatch_semaphore_create(1);
1414}
1415
1416typedef std::map<long, GLuint> VuoGlUniformMap;
1417
1435VuoGlProgram 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
1597int 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
1618char *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);
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}