Vuo  2.1.2
VuoImage.c
Go to the documentation of this file.
1 
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include "type.h"
14 #include "node.h"
15 #include "VuoImage.h"
16 #include "VuoImageRenderer.h"
17 #include "VuoGlContext.h"
18 #include "VuoGlPool.h"
19 
20 #include <CoreFoundation/CoreFoundation.h>
21 
22 #include <IOSurface/IOSurfaceAPI.h>
23 
24 #include <OpenGL/OpenGL.h>
25 #include <OpenGL/CGLMacro.h>
27 void glBindVertexArray(GLuint array);
28 void glDeleteVertexArrays(GLsizei n, const GLuint *arrays);
29 void glGenVertexArrays(GLsizei n, GLuint *arrays);
31 
32 
34 #ifdef VUO_COMPILER
36  "title" : "Image",
37  "description" : "An image residing in GPU memory (GL Texture Object).",
38  "keywords" : [ ],
39  "version" : "1.0.0",
40  "dependencies" : [
41  "VuoBoolean",
42  "VuoColor",
43  "VuoImageColorDepth",
44  "VuoImageWrapMode",
45  "VuoPoint2d",
46  "VuoGlContext",
47  "VuoGlPool",
48  "VuoImageRenderer",
49  "VuoImageBlur",
50  "VuoImageMapColors",
51  "CoreFoundation.framework",
52  "IOSurface.framework"
53  ]
54  });
55 #endif
56 
58 
66 void VuoImage_free(void *texture)
67 {
68  VuoImage t = (VuoImage)texture;
69 // VLog("Freeing image %p %s",t,VuoImage_getSummary(t));
70 
71  // Detach the CPU memory from the GL texture before recycling.
72  if (t->cpuQueueInitialized && json_object_object_length(t->cpuData))
73  {
74  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
75  glBindTexture(t->glTextureTarget, t->glTextureName);
76  GLenum format = GL_BGRA;
77  if (t->glInternalFormat == GL_DEPTH_COMPONENT)
78  format = GL_DEPTH_COMPONENT;
79  GLenum type = VuoGlTexture_getType(format);
80 // VLog("glTexImage2D(%s, 0, %s, %ld, %ld, 0, %s, %s, NULL);", VuoGl_stringForConstant(t->glTextureTarget), VuoGl_stringForConstant(t->glInternalFormat), t->pixelsWide, t->pixelsHigh, VuoGl_stringForConstant(format), VuoGl_stringForConstant(type));
81  glTexImage2D(t->glTextureTarget, 0, t->glInternalFormat, t->pixelsWide, t->pixelsHigh, 0, format, type, NULL);
82  glBindTexture(t->glTextureTarget, 0);
83  });
84  }
85 
86  VuoGlTexture_release(VuoGlTexturePool_Allocate, t->glTextureTarget, t->glInternalFormat, t->pixelsWide, t->pixelsHigh, t->glTextureName);
87 
88  if (t->cpuQueueInitialized)
89  {
90  dispatch_release(t->cpuQueue);
91 
92  json_object_object_foreach(t->cpuData, key, value)
93  {
94 // VLog("%p: freeing %s",t,key);
95  json_object *o;
96  json_object_object_get_ex(value, "buffer", &o);
97  void *buffer = (void *)json_object_get_int64(o);
98 
99  json_object_object_get_ex(value, "freeCallback", &o);
100  void (^freeCallback)(void *) = (void (^)(void *))json_object_get_int64(o);
101 
102  freeCallback(buffer);
103 
104  Block_release(freeCallback);
105  }
106 
107  json_object_put(t->cpuData);
108  }
109 
110  free(t);
111 }
112 
116 static VuoImage VuoImage_make_internal(unsigned int glTextureName, unsigned int glInternalFormat, unsigned long int pixelsWide, unsigned long int pixelsHigh, VuoImage_freeCallback freeCallback, void *freeCallbackContext)
117 {
118  VuoImage t = (VuoImage)malloc(sizeof(struct _VuoImage));
120 
121  t->glTextureName = glTextureName;
122  t->glTextureTarget = GL_TEXTURE_2D;
123  t->glInternalFormat = glInternalFormat;
124  t->pixelsWide = pixelsWide;
125  t->pixelsHigh = pixelsHigh;
126  t->scaleFactor = 1;
127 
128  t->freeCallbackContext = freeCallbackContext;
129 
130  VuoGlTexture_retain(glTextureName, freeCallback, freeCallbackContext);
131 
132  t->cpuQueueInitialized = 0;
133 
134 // VLog("Made image %p %s",t,VuoImage_getSummary(t));
135  return t;
136 }
137 
156 VuoImage VuoImage_make(unsigned int glTextureName, unsigned int glInternalFormat, unsigned long int pixelsWide, unsigned long int pixelsHigh)
157 {
158  if (!glTextureName || !pixelsWide || !pixelsHigh)
159  return NULL;
160 
161  VuoImage t = VuoImage_make_internal(glTextureName, glInternalFormat, pixelsWide, pixelsHigh, NULL, NULL);
162  return t;
163 }
164 
187 VuoImage VuoImage_makeClientOwned(unsigned int glTextureName, unsigned int glInternalFormat, unsigned long int pixelsWide, unsigned long int pixelsHigh, VuoImage_freeCallback freeCallback, void *freeCallbackContext)
188 {
189  if (!freeCallback)
190  {
191  VUserLog("Error: freeCallback may not be NULL.");
192  return NULL;
193  }
194 
195  if (!glTextureName || !pixelsWide || !pixelsHigh)
196  return NULL;
197 
198  return VuoImage_make_internal(glTextureName, glInternalFormat, pixelsWide, pixelsHigh, freeCallback, freeCallbackContext);
199 }
200 
225 VuoImage VuoImage_makeClientOwnedGlTextureRectangle(unsigned int glTextureName, unsigned int glInternalFormat, unsigned long int pixelsWide, unsigned long int pixelsHigh, VuoImage_freeCallback freeCallback, void *freeCallbackContext)
226 {
227  VuoImage t = VuoImage_makeClientOwned(glTextureName, glInternalFormat, pixelsWide, pixelsHigh, freeCallback, freeCallbackContext);
228  if (!t)
229  return NULL;
230 
231  t->glTextureTarget = GL_TEXTURE_RECTANGLE_ARB;
232 
233  return t;
234 }
235 
259 VuoImage VuoImage_makeFromBuffer(const void *pixels, unsigned int format, unsigned int pixelsWide, unsigned int pixelsHigh, VuoImageColorDepth colorDepth, void (^freeCallback)(void *pixels))
260 {
261  return VuoImage_makeFromBufferWithStride(pixels, format, pixelsWide, pixelsHigh, 0, colorDepth, freeCallback);
262 }
263 
267 VuoImage VuoImage_makeFromBufferWithStride(const void *pixels, unsigned int format, unsigned int pixelsWide, unsigned int pixelsHigh, unsigned int bytesPerRow, VuoImageColorDepth colorDepth, void (^freeCallback)(void *pixels))
268 {
269  if (!pixels || !pixelsWide || !pixelsHigh)
270  return NULL;
271 
272  if (!freeCallback)
273  {
274  static bool freeCallbackWarningEmitted = false;
275  if (!freeCallbackWarningEmitted)
276  {
277  freeCallbackWarningEmitted = true;
278  VUserLog("VuoImage_makeFromBuffer() and VuoImage_makeFromBufferWithStride() now take ownership of `pixels`, and therefore `freeCallback` is required. Since there's no `freeCallback`, I'm giving up and outputting an empty image. Please update the plugin listed in the backtrace below.");
280  }
281  return NULL;
282  }
283 
284  __block GLenum internalformat;
285  __block GLuint glTextureName;
286  __block int alignment = 1;
287  __block bool customRowLength = false;
288  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
289 
290  internalformat = VuoImageColorDepth_getGlInternalFormat(format, colorDepth);
291 // VLog("Using format=%s -> internalformat=%s", VuoGl_stringForConstant(format), VuoGl_stringForConstant(internalformat));
292  glTextureName = VuoGlTexturePool_use(cgl_ctx, VuoGlTexturePool_Allocate, GL_TEXTURE_2D, internalformat, pixelsWide, pixelsHigh, format, NULL);
293  if (!glTextureName)
294  return;
295 
296  int bytesPerPixel = VuoGlTexture_getChannelCount(format);
297  GLuint glType;
298  if (colorDepth == VuoImageColorDepth_8)
299  glType = VuoGlTexture_getType(format);
300  else if (colorDepth == VuoImageColorDepth_16)
301  {
302  glType = GL_HALF_FLOAT_ARB;
303  bytesPerPixel *= 2;
304  }
305  else // if (colorDepth == VuoImageColorDepth_32)
306  {
307  glType = GL_FLOAT;
308  bytesPerPixel *= 4;
309  }
310 
311  if (!bytesPerRow
312  || bytesPerRow == bytesPerPixel * pixelsWide)
313  // Tightly-packed.
314  alignment = 1;
315  else
316  {
317  if (bytesPerRow % 4 == 0)
318  alignment = 4;
319  else if (bytesPerRow % 8 == 0)
320  alignment = 8;
321  else if (bytesPerRow % 2 == 0)
322  alignment = 2;
323  else if (bytesPerRow % bytesPerPixel == 0)
324  {
325  GLuint rowPixels = bytesPerRow / bytesPerPixel;
326  glPixelStorei(GL_UNPACK_ROW_LENGTH, rowPixels);
327  customRowLength = true;
328  }
329  else
330  {
331  VUserLog("Not sure how to handle this stride:");
332  VUserLog(" %dx%d",pixelsWide,pixelsHigh);
333  VUserLog(" bytesPerRow = %d",bytesPerRow);
334  VUserLog(" bytesPerPixel = %d",bytesPerPixel);
335  GLint leftoverBytes = bytesPerRow - bytesPerPixel*pixelsWide;
336  VUserLog(" leftoverBytes = %d",leftoverBytes);
337  return;
338  }
339  }
340  if (alignment != 4)
341  glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);
342 
343  glBindTexture(GL_TEXTURE_2D, glTextureName);
344  glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
345 // VLog("glTexImage2D(GL_TEXTURE_2D, 0, %s, %d, %d, 0, %s, %s, %p);", VuoGl_stringForConstant(internalformat), pixelsWide, pixelsHigh, VuoGl_stringForConstant(format), VuoGl_stringForConstant(glType), pixels);
346  glTexImage2D(GL_TEXTURE_2D, 0, internalformat, pixelsWide, pixelsHigh, 0, format, glType, (GLvoid *)pixels);
347  glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_FALSE);
348  glBindTexture(GL_TEXTURE_2D, 0);
349 
350  if (alignment != 4)
351  glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
352  if (customRowLength)
353  glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
354 
355  // Since returning from this function implies that the texture is ready to use,
356  // flush before returning, to ensure that the enqueued commands to create the texture actually get executed
357  // before it gets used on a different context.
358  glFlushRenderAPPLE();
359 // glFinish();
360 
361  });
362  if (!glTextureName)
363  return NULL;
364 
365  VuoImage image = VuoImage_make(glTextureName, internalformat, pixelsWide, pixelsHigh);
366 
367  dispatch_once(&image->cpuQueueInitialized, ^{});
368  image->cpuQueue = dispatch_queue_create("org.vuo.image.cpu", NULL);
369  image->cpuData = json_object_new_object();
370  char *key = VuoGl_stringForConstant(format);
371  if (alignment != 1)
372  {
373  char *keyWithAlignment;
374  asprintf(&keyWithAlignment, "%s alignment=%d", key, alignment);
375  free(key);
376  key = keyWithAlignment;
377  }
378  if (customRowLength)
379  {
380  char *keyWithRowLength;
381  asprintf(&keyWithRowLength, "%s rowLength=%d", key, bytesPerRow);
382  free(key);
383  key = keyWithRowLength;
384  }
385  json_object *cpuEntry = json_object_new_object();
386  json_object_object_add(cpuEntry, "buffer", json_object_new_int64((long long)pixels));
387  json_object_object_add(cpuEntry, "freeCallback", json_object_new_int64((long long)Block_copy(freeCallback)));
388  json_object_object_add(image->cpuData, key, cpuEntry);
389 // VLog("%p: populated %s",image,key);
390  free(key);
391 
392  return image;
393 }
394 
419 const unsigned char *VuoImage_getBuffer(VuoImage image, unsigned int requestedFormat)
420 {
421  if (!image)
422  return NULL;
423 
424  if (image->glInternalFormat == GL_DEPTH_COMPONENT && requestedFormat != GL_DEPTH_COMPONENT16)
425  {
426  VUserLog("Error: Image has format GL_DEPTH_COMPONENT, which must be fetched as GL_DEPTH_COMPONENT16.");
427  return NULL;
428  }
429 
430  dispatch_once(&image->cpuQueueInitialized, ^{
431  image->cpuQueue = dispatch_queue_create("org.vuo.image.cpu", NULL);
432  image->cpuData = json_object_new_object();
433  });
434 
435  __block unsigned char *pixels = NULL;
436  dispatch_sync(image->cpuQueue, ^{
437  char *key = VuoGl_stringForConstant(requestedFormat);
438  struct json_object *value;
439  if (json_object_object_get_ex(image->cpuData, key, &value))
440  {
441  json_object *o;
442  json_object_object_get_ex(value, "buffer", &o);
443  pixels = (unsigned char *)json_object_get_int64(o);
444  }
445  else
446  {
447  unsigned int channels;
448  if (requestedFormat == GL_LUMINANCE
449  || requestedFormat == GL_R16
450  || requestedFormat == GL_DEPTH_COMPONENT16)
451  channels = 1;
452  else if (requestedFormat == GL_LUMINANCE_ALPHA)
453  channels = 2;
454  else if (requestedFormat == GL_RGB
455  || requestedFormat == GL_BGR)
456  channels = 3;
457  else if (requestedFormat == GL_RGBA
458  || requestedFormat == GL_BGRA
459  || requestedFormat == GL_RGBA16I_EXT
460  || requestedFormat == GL_RGBA16F_ARB
461  || requestedFormat == GL_RGBA32F_ARB)
462  channels = 4;
463  else
464  {
465  VUserLog("Error: Unknown format %s.", VuoGl_stringForConstant(requestedFormat));
466  return;
467  }
468 
469  unsigned int bytesPerChannel = 1;
470  GLuint type = GL_UNSIGNED_BYTE;
471  if (requestedFormat == GL_RGBA16I_EXT
472  || requestedFormat == GL_R16
473  || requestedFormat == GL_DEPTH_COMPONENT16)
474  {
475  bytesPerChannel = 2;
476  type = GL_UNSIGNED_SHORT;
477  }
478  else if (requestedFormat == GL_RGBA16F_ARB)
479  {
480  bytesPerChannel = 2;
481  type = GL_HALF_FLOAT_ARB;
482  }
483  else if (requestedFormat == GL_RGBA32F_ARB)
484  {
485  bytesPerChannel = 4;
486  type = GL_FLOAT;
487  }
488 
489  GLuint actualFormat = requestedFormat;
490  if (requestedFormat == GL_RGBA16I_EXT
491  || requestedFormat == GL_RGBA16F_ARB
492  || requestedFormat == GL_RGBA32F_ARB)
493  actualFormat = GL_BGRA;
494  else if (requestedFormat == GL_DEPTH_COMPONENT16)
495  actualFormat = GL_DEPTH_COMPONENT;
496  else if (requestedFormat == GL_R16)
497  actualFormat = GL_RED;
498 
499  size_t pixelBufferSize = image->pixelsWide * image->pixelsHigh * channels * bytesPerChannel;
500  pixels = (unsigned char *)malloc(pixelBufferSize);
501 
502  // In the seal, use zeroes for the alpha channel,
503  // to lessen the chance that we collide with valid image data.
504  const char *tamperEvidentSeal = "Vuo\0Ima\0ge_\0get\0Buf\0fer\0()\0";
505  int tamperEvidentSealLength = strlen(tamperEvidentSeal);
506  if (pixelBufferSize > tamperEvidentSealLength)
507  strlcpy((char *)pixels, tamperEvidentSeal, pixelBufferSize);
508 
509  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
510 
511  // If each row is quad-aligned, OpenGL doesn't add any padding (good).
512  bool openGLAddsPadding = ((image->pixelsWide * channels * bytesPerChannel) % 4 != 0);
513  if (openGLAddsPadding)
514  // Remove the padding, since VuoImage_getBuffer promises tightly-packed buffers.
515  glPixelStorei(GL_PACK_ALIGNMENT, 1);
516 
517  glBindTexture(image->glTextureTarget, image->glTextureName);
518 // VLog("glGetTexImage(%s, 0, %s, %s, …); on texture internalformat %s", VuoGl_stringForConstant(image->glTextureTarget), VuoGl_stringForConstant(actualFormat), VuoGl_stringForConstant(type), VuoGl_stringForConstant(image->glInternalFormat));
519  glGetTexImage(image->glTextureTarget, 0, actualFormat, type, (GLvoid *)pixels);
520  glBindTexture(image->glTextureTarget, 0);
521 
522  if (openGLAddsPadding)
523  // Restore the default.
524  glPixelStorei(GL_PACK_ALIGNMENT, 4);
525 
526  if (pixelBufferSize > tamperEvidentSealLength && strncmp((char *)pixels, tamperEvidentSeal, strlen(tamperEvidentSeal)) == 0)
527  {
528  GLenum error = glGetError();
529  if (error == GL_NO_ERROR)
530  // But as of macOS 10.14.4, calling glGetTexImage on an IOSurface
531  // now fills the buffer with garbage (instead of leaving the buffer unmodified),
532  // and still doesn't return an error, so we no longer have a way to detect this situation.
533  VUserLog("Warning: glGetTexImage() says it was successful, but it didn't actually copy any data. This might happen if the input texture has an IOSurface bound to it.");
534  else
535  VUserLog("OpenGL Error: %d", error);
536  free(pixels);
537  pixels = NULL;
538  return;
539  }
540 
541  });
542  if (!pixels)
543  return;
544 
545  json_object *cpuEntry = json_object_new_object();
546  json_object_object_add(cpuEntry, "buffer", json_object_new_int64((long long)pixels));
547  json_object_object_add(cpuEntry, "freeCallback", json_object_new_int64((long long)Block_copy( ^(void *buffer){ free(buffer); } )));
548  json_object_object_add(image->cpuData, key, cpuEntry);
549 // VLog("%p: populated %s",image,key);
550  }
551  free(key);
552  });
553 
554  return pixels;
555 }
556 
561 {
562  __block GLint wrapModeGL;
563  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
564 
565  glBindTexture(image->glTextureTarget, image->glTextureName);
566 
567  glGetTexParameteriv(image->glTextureTarget, GL_TEXTURE_WRAP_S, &wrapModeGL);
568  // Ignore GL_TEXTURE_WRAP_T since Vuo assumes it's the same as _S.
569 
570  glBindTexture(image->glTextureTarget, 0);
571 
572  });
573 
574  if (wrapModeGL == GL_CLAMP_TO_EDGE)
575  return VuoImageWrapMode_ClampEdge;
576  else if (wrapModeGL == GL_REPEAT)
577  return VuoImageWrapMode_Repeat;
578  else if (wrapModeGL == GL_MIRRORED_REPEAT)
579  return VuoImageWrapMode_MirroredRepeat;
580 
581  return VuoImageWrapMode_None;
582 }
583 
588 {
589  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
590 
591  glBindTexture(image->glTextureTarget, image->glTextureName);
592 
593  GLint wrapModeGL = GL_CLAMP_TO_BORDER;
594  if (wrapMode == VuoImageWrapMode_ClampEdge)
595  wrapModeGL = GL_CLAMP_TO_EDGE;
596  else if (wrapMode == VuoImageWrapMode_Repeat)
597  wrapModeGL = GL_REPEAT;
598  else if (wrapMode == VuoImageWrapMode_MirroredRepeat)
599  wrapModeGL = GL_MIRRORED_REPEAT;
600 
601  glTexParameteri(image->glTextureTarget, GL_TEXTURE_WRAP_S, wrapModeGL);
602  glTexParameteri(image->glTextureTarget, GL_TEXTURE_WRAP_T, wrapModeGL);
603 
604  glBindTexture(image->glTextureTarget, 0);
605 
606  // Ensure the command queue gets executed before we return,
607  // since the VuoShader might immediately be used on another context.
608  glFlushRenderAPPLE();
609 
610  });
611 }
612 
616 VuoImage VuoImage_makeColorImage(VuoColor color, unsigned int pixelsWide, unsigned int pixelsHigh)
617 {
618  if (!pixelsWide || !pixelsHigh)
619  return NULL;
620 
622  VuoRetain(shader);
623  VuoImage image = VuoImageRenderer_render(shader, pixelsWide, pixelsHigh, VuoImageColorDepth_8);
624  VuoRelease(shader);
625  return image;
626 }
627 
639 VuoImage VuoImage_makeCopy(VuoImage image, bool flip, unsigned int forcePixelsWide, unsigned int forcePixelsHigh, bool forceAlpha)
640 {
641  VuoShader shader = NULL;
642  if (image->glTextureTarget == GL_TEXTURE_2D)
643  shader = VuoShader_makeUnlitAlphaPassthruImageShader(image, flip);
644  else if (image->glTextureTarget == GL_TEXTURE_RECTANGLE_ARB)
646  else
647  {
648  VUserLog("Error: Unknown glTextureTarget %s", VuoGl_stringForConstant(image->glTextureTarget));
649  return NULL;
650  }
651  VuoRetain(shader);
652 
653  if (forceAlpha)
654  shader->isTransparent = true;
655 
656  VuoImage img = VuoImageRenderer_render(shader,
657  forcePixelsWide ? forcePixelsWide : image->pixelsWide,
658  forcePixelsHigh ? forcePixelsHigh : image->pixelsHigh,
659  VuoImage_getColorDepth(image));
660 
661  VuoRelease(shader);
662 
663  return img;
664 }
665 
672 {
673  VuoShader frag = VuoShader_makeUnlitImageShader(image, 1);
674  VuoRetain(frag);
675 
676  GLuint textureName = VuoImageRenderer_draw_internal(frag, image->pixelsWide, image->pixelsHigh, VuoImage_getColorDepth(image), false, true, 0, NULL);
677 
678  VuoImage img = VuoImage_make_internal(textureName, image->glInternalFormat, image->pixelsWide, image->pixelsHigh, NULL, NULL);
679  img->glTextureTarget = GL_TEXTURE_RECTANGLE_ARB;
680 
681  VuoRelease(frag);
682 
683  return img;
684 }
685 
689 bool VuoImage_areEqualWithinTolerance(const VuoImage a, const VuoImage b, const unsigned char tolerance)
690 {
691  if (!a && !b)
692  return true;
693  if (!a || !b)
694  return false;
695 
696  if (a->pixelsWide != b->pixelsWide
697  || a->pixelsHigh != b->pixelsHigh)
698  return false;
699 
700  if (a->glTextureName == b->glTextureName)
701  return true;
702 
703  const unsigned char *aPixels = VuoImage_getBuffer(a, GL_BGRA);
704  const unsigned char *bPixels = VuoImage_getBuffer(b, GL_BGRA);
705 
706  unsigned char aChannels = VuoGlTexture_getChannelCount(a->glInternalFormat);
707  unsigned char bChannels = VuoGlTexture_getChannelCount(b->glInternalFormat);
708  if (aChannels == 4 && bChannels == 1)
709  {
710  // Treat 1-channel red images as equal to opaque greyscale BGRA images.
711  for (unsigned int i = 0; i < a->pixelsWide * a->pixelsHigh; ++i)
712  if (abs(aPixels[i*4+0] - bPixels[i*4+2]) > tolerance
713  || abs(aPixels[i*4+1] - bPixels[i*4+2]) > tolerance
714  || abs(aPixels[i*4+2] - bPixels[i*4+2]) > tolerance
715  || abs(aPixels[i*4+3] - bPixels[i*4+3]) > tolerance)
716  {
717  VDebugLog("Difference found at pixel coordinate (%ld,%ld): RGBA %d,%d,%d,%d vs %d,%d,%d,%d",
718  i%a->pixelsWide, i/a->pixelsWide,
719  aPixels[i*4+2],aPixels[i*4+1],aPixels[i*4+0],aPixels[i*4+3],
720  bPixels[i*4+2],bPixels[i*4+2],bPixels[i*4+2],bPixels[i*4+3]);
721  return false;
722  }
723  return true;
724  }
725  else if (aChannels == 1 && bChannels == 4)
726  {
727  // Treat 1-channel red images as equal to opaque greyscale BGRA images.
728  for (unsigned int i = 0; i < a->pixelsWide * a->pixelsHigh; ++i)
729  if (abs(aPixels[i*4+2] - bPixels[i*4+0]) > tolerance
730  || abs(aPixels[i*4+2] - bPixels[i*4+1]) > tolerance
731  || abs(aPixels[i*4+2] - bPixels[i*4+2]) > tolerance
732  || abs(aPixels[i*4+3] - bPixels[i*4+3]) > tolerance)
733  {
734  VDebugLog("Difference found at pixel coordinate (%ld,%ld): RGBA %d,%d,%d,%d vs %d,%d,%d,%d",
735  i%a->pixelsWide, i/a->pixelsWide,
736  aPixels[i*4+2],aPixels[i*4+2],aPixels[i*4+2],aPixels[i*4+3],
737  bPixels[i*4+2],bPixels[i*4+1],bPixels[i*4+0],bPixels[i*4+3]);
738  return false;
739  }
740  return true;
741  }
742 
743  for (unsigned int i = 0; i < a->pixelsWide * a->pixelsHigh * 4; ++i)
744  if (abs(aPixels[i] - bPixels[i]) > tolerance)
745  {
746  unsigned int p = (i/4)*4; // Round down to the start of this 32bit pixel.
747  VDebugLog("Difference found at pixel coordinate (%ld,%ld): abs(%d - %d) > %d (RGBA %d,%d,%d,%d vs %d,%d,%d,%d)",
748  i%a->pixelsWide, i/a->pixelsWide,
749  aPixels[i], bPixels[i], tolerance,
750  aPixels[p+2],aPixels[p+1],aPixels[p+0],aPixels[p+3],
751  bPixels[p+2],bPixels[p+1],bPixels[p+0],bPixels[p+3]);
752  return false;
753  }
754 
755  return true;
756 }
757 
769 bool VuoImage_areEqual(const VuoImage a, const VuoImage b)
770 {
771  return VuoImage_areEqualWithinTolerance(a,b,0);
772 }
773 
779 bool VuoImage_isLessThan(const VuoImage a, const VuoImage b)
780 {
781  // Treat null images as greater than non-null images,
782  // so the more useful non-null images sort to the beginning of the list.
783  if (!a || !b)
784  return a && !b;
785 
786  if (a->pixelsWide < b->pixelsWide) return true;
787  if (b->pixelsWide < a->pixelsWide) return false;
788 
789  if (a->pixelsHigh < b->pixelsHigh) return true;
790  /*if (b->pixelsHigh < a->pixelsHigh)*/ return false;
791 }
792 
798 bool VuoImage_isEmpty(const VuoImage image)
799 {
800  if (!image || image->pixelsWide == 0 || image->pixelsHigh == 0)
801  return true;
802 
803  const unsigned char *pixels = VuoImage_getBuffer(image, GL_BGRA);
804  bool foundSubstantialPixel = false;
805  for (unsigned int p = 3; p < image->pixelsWide * image->pixelsHigh * 4; p += 4)
806  if (pixels[p])
807  {
808  foundSubstantialPixel = true;
809  break;
810  }
811  return !foundSubstantialPixel;
812 }
813 
820 bool VuoImage_isBlackOrTransparent(const VuoImage image, const unsigned char tolerance)
821 {
822  if (!image || image->pixelsWide == 0 || image->pixelsHigh == 0)
823  return false;
824 
825  const unsigned char *pixels = VuoImage_getBuffer(image, GL_LUMINANCE);
826  bool foundNonBlackPixel = false;
827  for (unsigned int p = 0; p < image->pixelsWide * image->pixelsHigh; ++p)
828  if (pixels[p] > tolerance)
829  {
830  foundNonBlackPixel = true;
831  break;
832  }
833  return !foundNonBlackPixel;
834 }
835 
842 {
843  return (image && image->pixelsWide && image->pixelsHigh);
844 }
845 
850 {
851  return VuoRectangle_make(0, 0, 2, 2. * image->pixelsHigh / image->pixelsWide);
852 }
853 
858 {
859  if (!image)
860  return VuoImageColorDepth_8;
861 
862  if (image->glInternalFormat == GL_LUMINANCE8
863  || image->glInternalFormat == GL_LUMINANCE8_ALPHA8
864  || image->glInternalFormat == GL_RGB
865  || image->glInternalFormat == GL_RGBA
866  || image->glInternalFormat == GL_RGBA8
867  || image->glInternalFormat == GL_BGRA
868  || image->glInternalFormat == GL_COMPRESSED_RGB_S3TC_DXT1_EXT
869  || image->glInternalFormat == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT)
870  return VuoImageColorDepth_8;
871  else if (image->glInternalFormat == GL_LUMINANCE16F_ARB
872  || image->glInternalFormat == GL_LUMINANCE_ALPHA16F_ARB
873  || image->glInternalFormat == GL_DEPTH_COMPONENT
874  || image->glInternalFormat == GL_RGB16
875  || image->glInternalFormat == GL_RGBA16
876  || image->glInternalFormat == GL_RGB16F_ARB
877  || image->glInternalFormat == GL_RGBA16F_ARB)
878  return VuoImageColorDepth_16;
879  else if (image->glInternalFormat == GL_LUMINANCE32F_ARB
880  || image->glInternalFormat == GL_LUMINANCE_ALPHA32F_ARB
881  || image->glInternalFormat == GL_RGB32F_ARB
882  || image->glInternalFormat == GL_RGBA32F_ARB)
883  return VuoImageColorDepth_32;
884 
885  char *formatString = VuoGl_stringForConstant(image->glInternalFormat);
886  VUserLog("Error: Unknown glInternalFormat %x (%s)", image->glInternalFormat, formatString);
887  free(formatString);
888  return VuoImageColorDepth_8;
889 }
890 
929 {
930  return VuoImage_makeFromJsonWithDimensions(js, 0, 0);
931 }
932 
937 {
938  VuoGlTexturePool_disuse(VuoGlTexturePool_AllocateIOSurface, i->glTextureTarget, i->glInternalFormat, i->pixelsWide, i->pixelsHigh, i->glTextureName);
939 }
940 
952 VuoImage VuoImage_makeFromJsonWithDimensions(struct json_object *js, unsigned int requestedPixelsWide, unsigned int requestedPixelsHigh)
953 {
954  if (!js)
955  return NULL;
956 
957  {
958  json_object * o;
959  if (json_object_object_get_ex(js, "pointer", &o))
960  {
961  VuoImage im = (VuoImage)json_object_get_int64(o);
962  if ((requestedPixelsWide == 0 && requestedPixelsHigh == 0)
963  || (im->pixelsWide == requestedPixelsWide && im->pixelsHigh == requestedPixelsHigh))
964  return im;
965  else
966  {
967  VuoImage outputImage = VuoImage_make(im->glTextureName, im->glInternalFormat, requestedPixelsWide, requestedPixelsHigh);
968  outputImage->glTextureTarget = im->glTextureTarget;
969  outputImage->scaleFactor = im->scaleFactor;
970  return outputImage;
971  }
972  }
973  }
974 
975  __block unsigned int glInternalFormat = 0;
976  unsigned long int pixelsWide;
977  unsigned long int pixelsHigh;
978  float scaleFactor = 1;
979 
980  {
981  json_object * o;
982  if (json_object_object_get_ex(js, "pixelsWide", &o))
983  pixelsWide = json_object_get_int64(o);
984  else
985  return NULL;
986  }
987  {
988  json_object * o;
989  if (json_object_object_get_ex(js, "pixelsHigh", &o))
990  pixelsHigh = json_object_get_int64(o);
991  else
992  return NULL;
993  }
994  if (pixelsWide == 0 || pixelsHigh == 0)
995  return NULL;
996 
997  {
998  json_object * o;
999  if (json_object_object_get_ex(js, "scaleFactor", &o))
1000  scaleFactor = json_object_get_double(o);
1001  }
1002 
1003  {
1004  json_object * o;
1005  if (json_object_object_get_ex(js, "color", &o))
1006  return VuoImage_makeColorImage(VuoColor_makeFromJson(o), pixelsWide, pixelsHigh);
1007  }
1008 
1009  {
1010  json_object * o;
1011  if (json_object_object_get_ex(js, "ioSurface", &o))
1012  {
1013  IOSurfaceID surfID = json_object_get_int(o);
1014 // VLog("Converting IOSurfaceID %d",surfID);
1015 
1016  // Read the IOSurface into a GL_TEXTURE_RECTANGLE_ARB (the only texture type IOSurface supports).
1017  __block IOSurfaceRef surf = NULL;
1018  __block GLuint textureRect;
1019  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
1020  glInternalFormat = GL_RGBA;
1021  surf = IOSurfaceLookup(surfID);
1022  if (!surf)
1023  {
1024  VUserLog("Error: IOSurfaceLookup(%d) failed.", surfID);
1025  return;
1026  }
1027  textureRect = VuoGlTexturePool_use(cgl_ctx, VuoGlTexturePool_AllocateIOSurface, GL_TEXTURE_RECTANGLE_ARB, glInternalFormat, pixelsWide, pixelsHigh, GL_BGRA, surf);
1028  glFlushRenderAPPLE();
1029  });
1030  if (!surf)
1031  return NULL;
1032 
1033  // Convert the GL_TEXTURE_RECTANGLE_ARB into GL_TEXTURE_2D.
1034  VuoImage image2d;
1035  {
1036  VuoImage imageRect = VuoImage_makeClientOwnedGlTextureRectangle(textureRect, glInternalFormat, pixelsWide, pixelsHigh, VuoImage_IOSurfaceTextureFree, NULL);
1037 
1038  imageRect->glTextureTarget = GL_TEXTURE_RECTANGLE_ARB;
1039  VuoLocal(imageRect);
1040 
1041  VuoShader shader = VuoShader_makeGlTextureRectangleShader(imageRect, 1);
1042  VuoLocal(shader);
1043 
1044  image2d = VuoImageRenderer_render(shader,
1045  requestedPixelsWide ? requestedPixelsWide : pixelsWide,
1046  requestedPixelsHigh ? requestedPixelsHigh : pixelsHigh,
1047  VuoImage_getColorDepth(imageRect));
1048  }
1049 
1051  CFRelease(surf);
1052 
1053  image2d->scaleFactor = scaleFactor;
1054 
1055  return image2d;
1056  }
1057  }
1058 
1059  return NULL;
1060 }
1061 
1082 GLuint VuoImage_resolveInterprocessJsonUsingTextureProvider(struct json_object *js, GLuint (^provider)(unsigned int pixelsWide, unsigned int pixelsHigh), unsigned int *outputPixelsWide, unsigned int *outputPixelsHigh, void *outputIOSurface)
1083 {
1084  json_object *o;
1085 
1086  if (!json_object_object_get_ex(js, "pixelsWide", &o))
1087  return 0;
1088  *outputPixelsWide = json_object_get_int64(o);
1089 
1090  if (!json_object_object_get_ex(js, "pixelsHigh", &o))
1091  return 0;
1092  *outputPixelsHigh = json_object_get_int64(o);
1093 
1094  if (!json_object_object_get_ex(js, "ioSurface", &o))
1095  return 0;
1096  IOSurfaceID surfID = json_object_get_int(o);
1097 
1098  GLuint textureRect = provider(*outputPixelsWide, *outputPixelsHigh);
1099  if (!textureRect)
1100  return 0;
1101 
1102  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
1103  glBindTexture(GL_TEXTURE_RECTANGLE_ARB, textureRect);
1104 
1105  IOSurfaceRef *surf = outputIOSurface;
1106  *surf = IOSurfaceLookup(surfID);
1107  if (!*surf)
1108  {
1109  VUserLog("Error: IOSurfaceLookup(%d) failed.", surfID);
1110  return;
1111  }
1112 
1113  CGLError err = CGLTexImageIOSurface2D(cgl_ctx, GL_TEXTURE_RECTANGLE_ARB, GL_RGBA, (GLsizei)*outputPixelsWide, (GLsizei)*outputPixelsHigh, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, *surf, 0);
1114  if(err != kCGLNoError)
1115  {
1116  VUserLog("Error in CGLTexImageIOSurface2D(): %s", CGLErrorString(err));
1117  return;
1118  }
1119  glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
1120  glFlushRenderAPPLE();
1121  });
1122 
1123  return textureRect;
1124 }
1125 
1126 static bool VuoImage_resolveInterprocessJsonOntoFramebufferInternal(IOSurfaceRef surf, VuoGlContext context, GLsizei pixelsWide, GLsizei pixelsHigh, bool flip, bool stretch);
1127 
1149 bool VuoImage_resolveInterprocessJsonUsingClientTexture(struct json_object *js, GLuint clientTextureName, unsigned int pixelsWide, unsigned int pixelsHigh, void *outputIOSurface)
1150 {
1151  if (!clientTextureName)
1152  return false;
1153 
1154  json_object *o;
1155 
1156  if (!json_object_object_get_ex(js, "pixelsWide", &o))
1157  return false;
1158  unsigned long inputPixelsWide = json_object_get_int64(o);
1159 
1160  if (!json_object_object_get_ex(js, "pixelsHigh", &o))
1161  return false;
1162  unsigned long inputPixelsHigh = json_object_get_int64(o);
1163 
1164  if (!json_object_object_get_ex(js, "ioSurface", &o))
1165  return false;
1166  IOSurfaceID surfID = json_object_get_int(o);
1167 
1168  __block bool success = true;
1169  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
1170  IOSurfaceRef *surf = outputIOSurface;
1171  *surf = IOSurfaceLookup(surfID);
1172  if (!*surf)
1173  {
1174  VUserLog("Error: IOSurfaceLookup(%d) failed.", surfID);
1175  success = false;
1176  return;
1177  }
1178 
1179  bool shouldResize = (inputPixelsWide != pixelsWide
1180  || inputPixelsHigh != pixelsHigh);
1181  if (shouldResize)
1182  {
1183  VuoShader_resetContext(cgl_ctx);
1184 
1185  glBindTexture(GL_TEXTURE_RECTANGLE_ARB, clientTextureName);
1186  glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8, pixelsWide, pixelsHigh, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
1187  glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
1188 
1189  GLuint outputFramebuffer;
1190  glGenFramebuffers(1, &outputFramebuffer);
1191  glBindFramebuffer(GL_FRAMEBUFFER, outputFramebuffer);
1192  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, clientTextureName, 0);
1193 
1194  glViewport(0, 0, pixelsWide, pixelsHigh);
1195 
1196  success = VuoImage_resolveInterprocessJsonOntoFramebufferInternal(*surf, cgl_ctx, inputPixelsWide, inputPixelsHigh, false, true);
1197 
1198  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, 0, 0);
1199  glBindFramebuffer(GL_FRAMEBUFFER, 0);
1200  glDeleteFramebuffers(1, &outputFramebuffer);
1201  }
1202  else
1203  {
1204  glBindTexture(GL_TEXTURE_RECTANGLE_ARB, clientTextureName);
1205 
1206  CGLError err = CGLTexImageIOSurface2D(cgl_ctx, GL_TEXTURE_RECTANGLE_ARB, GL_RGBA, (GLsizei)inputPixelsWide, (GLsizei)inputPixelsHigh, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, *surf, 0);
1207  if (err != kCGLNoError)
1208  {
1209  VUserLog("Error in CGLTexImageIOSurface2D(): %s", CGLErrorString(err));
1210  success = false;
1211  return;
1212  }
1213 
1214  glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
1215  }
1216 
1217  glFlushRenderAPPLE();
1218  });
1219 
1220  return success;
1221 }
1222 
1239 {
1240  json_object *o;
1241  if (!json_object_object_get_ex(js, "pixelsWide", &o))
1242  return false;
1243  GLsizei pixelsWide = json_object_get_int64(o);
1244 
1245  if (!json_object_object_get_ex(js, "pixelsHigh", &o))
1246  return false;
1247  GLsizei pixelsHigh = json_object_get_int64(o);
1248 
1249  if (!json_object_object_get_ex(js, "ioSurface", &o))
1250  return false;
1251  IOSurfaceID surfID = json_object_get_int(o);
1252  IOSurfaceRef surf = IOSurfaceLookup(surfID);
1253  if (!surf)
1254  {
1255  VUserLog("Error: IOSurfaceLookup(%d) failed.", surfID);
1256  return false;
1257  }
1258 
1259  bool ret = VuoImage_resolveInterprocessJsonOntoFramebufferInternal(surf, context, pixelsWide, pixelsHigh, flip, stretch);
1260 
1262  CFRelease(surf);
1263  return ret;
1264 }
1265 
1269 static GLuint CompileShader(CGLContextObj cgl_ctx, GLenum type, const char *source)
1270 {
1271  GLint length = (GLint)strlen(source);
1272  GLuint shader = glCreateShader(type);
1273  glShaderSource(shader, 1, (const GLchar**)&source, &length);
1274  glCompileShader(shader);
1275 
1276  int infologLength = 0;
1277  glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infologLength);
1278  if (infologLength > 0)
1279  {
1280  char *infoLog = (char *)malloc(infologLength);
1281  int charsWritten = 0;
1282  glGetShaderInfoLog(shader, infologLength, &charsWritten, infoLog);
1283  VUserLog("%s", infoLog);
1284  free(infoLog);
1285  }
1286  return shader;
1287 }
1288 
1291 
1295 static bool VuoImage_resolveInterprocessJsonOntoFramebufferInternal(IOSurfaceRef surf, VuoGlContext context, GLsizei pixelsWide, GLsizei pixelsHigh, bool flip, bool stretch)
1296 {
1297  CGLContextObj cgl_ctx = (CGLContextObj)context;
1298 
1299  static bool openGL32Core;
1300  static GLuint vertexArray;
1301  static GLuint program;
1302  static GLuint receiveTextureOffsetAndSizeUniform;
1304  openGL32Core = VuoGlContext_isOpenGL32Core((VuoGlContext)cgl_ctx);
1305 
1306  char *vertexShaderSource;
1307  char *fragmentShaderSource;
1308  if (openGL32Core)
1309  {
1310  // The following 2 `gl*VertexArrays` calls use the thread-local context (not CGLMacro).
1311  CGLSetCurrentContext(cgl_ctx);
1312 
1313  glGenVertexArrays(1, &vertexArray);
1314  glBindVertexArray(vertexArray);
1315 
1316  vertexShaderSource = VUOSHADER_GLSL_SOURCE(150,
1317  in vec2 position;
1318  in vec2 textureCoordinate;
1319  out vec2 fragmentTextureCoordinate;
1320  void main()
1321  {
1322  fragmentTextureCoordinate = textureCoordinate;
1323  gl_Position = vec4(position.x, position.y, 0., 1.);
1324  }
1325  );
1326  fragmentShaderSource = VUOSHADER_GLSL_SOURCE(150,
1327  uniform sampler2DRect receiveTexture;
1328  uniform vec4 receiveTextureOffsetAndSize;
1329  in vec2 fragmentTextureCoordinate;
1330  out vec4 FragColor;
1331  void main()
1332  {
1333  FragColor = texture(receiveTexture, receiveTextureOffsetAndSize.xy + fragmentTextureCoordinate * receiveTextureOffsetAndSize.zw);
1334  }
1335  );
1336  }
1337  else
1338  {
1339  // OpenGL 2.1 context.
1340 
1341  glGenVertexArraysAPPLE(1, &vertexArray);
1342  glBindVertexArrayAPPLE(vertexArray);
1343 
1344  vertexShaderSource = VUOSHADER_GLSL_SOURCE(120,
1345  attribute vec2 position;
1346  attribute vec2 textureCoordinate;
1347  varying vec2 fragmentTextureCoordinate;
1348  void main()
1349  {
1350  fragmentTextureCoordinate = textureCoordinate;
1351  gl_Position = vec4(position.x, position.y, 0., 1.);
1352  }
1353  );
1354  fragmentShaderSource = VUOSHADER_GLSL_SOURCE(120,
1355  uniform sampler2DRect receiveTexture;
1356  uniform vec4 receiveTextureOffsetAndSize;
1357  varying vec2 fragmentTextureCoordinate;
1358  void main()
1359  {
1360  gl_FragColor = texture2DRect(receiveTexture, receiveTextureOffsetAndSize.xy + fragmentTextureCoordinate * receiveTextureOffsetAndSize.zw);
1361  }
1362  );
1363  }
1364 
1365 
1366  const GLfloat quadPositionsAndTextureCoordinates[] = {
1367  // X Y U V
1368  -1, -1, 0, 0,
1369  1, -1, 1, 0,
1370  -1, 1, 0, 1,
1371 
1372  1, 1, 1, 1,
1373  -1, 1, 0, 1,
1374  1, -1, 1, 0,
1375  };
1376  GLuint quadPTCBuffer;
1377  glGenBuffers(1, &quadPTCBuffer);
1378  glBindBuffer(GL_ARRAY_BUFFER, quadPTCBuffer);
1379  glBufferData(GL_ARRAY_BUFFER, sizeof(quadPositionsAndTextureCoordinates), quadPositionsAndTextureCoordinates, GL_STATIC_DRAW);
1380  VuoGlPool_logVRAMAllocated(sizeof(quadPositionsAndTextureCoordinates));
1381 
1382  GLuint vertexShader = CompileShader(context, GL_VERTEX_SHADER, vertexShaderSource);
1383  GLuint fragmentShader = CompileShader(context, GL_FRAGMENT_SHADER, fragmentShaderSource);
1384  program = glCreateProgram();
1385  glAttachShader(program, vertexShader);
1386  glAttachShader(program, fragmentShader);
1387  glLinkProgram(program);
1388  GLuint positionAttribute = glGetAttribLocation(program, "position");
1389  GLuint textureCoordinateAttribute = glGetAttribLocation(program, "textureCoordinate");
1390  GLuint receiveTextureUniform = glGetUniformLocation(program, "receiveTexture");
1391  receiveTextureOffsetAndSizeUniform = glGetUniformLocation(program, "receiveTextureOffsetAndSize");
1392 
1393  glUseProgram(program);
1394 
1395  glVertexAttribPointer(positionAttribute, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*4, (void*)0);
1396  glEnableVertexAttribArray(positionAttribute);
1397 
1398  glVertexAttribPointer(textureCoordinateAttribute, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*4, (void*)(sizeof(GLfloat)*2));
1399  glEnableVertexAttribArray(textureCoordinateAttribute);
1400 
1401  glUniform1i(receiveTextureUniform, 0);
1402  });
1403 
1404 
1405  if (openGL32Core)
1406  {
1407  CGLSetCurrentContext(cgl_ctx);
1408  glBindVertexArray(vertexArray);
1409  }
1410  else
1411  glBindVertexArrayAPPLE(vertexArray);
1412 
1413  GLuint textureRect = VuoGlTexturePool_use(cgl_ctx, VuoGlTexturePool_AllocateIOSurface, GL_TEXTURE_RECTANGLE_ARB, GL_RGBA, pixelsWide, pixelsHigh, GL_BGRA, surf);
1414  if (!textureRect)
1415  {
1416  VUserLog("Error: Couldn't allocate texture.");
1417  VGL();
1418  return false;
1419  }
1420  VuoGlTexture_retain(textureRect, NULL, NULL);
1421 
1422  glActiveTexture(GL_TEXTURE0);
1423  glBindTexture(GL_TEXTURE_RECTANGLE_ARB, textureRect);
1424 
1425  glUseProgram(program);
1426 
1427  if (stretch)
1428  glUniform4f(receiveTextureOffsetAndSizeUniform, 0, flip ? pixelsHigh : 0, pixelsWide, pixelsHigh * (flip ? -1 : 1));
1429  else
1430  {
1431  // Center the image in the viewport.
1432  GLint viewport[4];
1433  glGetIntegerv(GL_VIEWPORT, viewport);
1434 // VLog("Resolving %dx%d image onto a %dx%d viewport.", pixelsWide, pixelsHigh, viewport[2], viewport[3]);
1435  glUniform4f(receiveTextureOffsetAndSizeUniform,
1436  ((float)pixelsWide - viewport[2]) / 2,
1437  flip ? viewport[3] : 0,
1438  viewport[2],
1439  viewport[3] * (flip ? -1 : 1));
1440  }
1441 
1442  glDrawArrays(GL_TRIANGLES, 0, 6);
1443 
1444  glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
1445  VuoGlTexture_release(VuoGlTexturePool_AllocateIOSurface, GL_TEXTURE_RECTANGLE_ARB, GL_RGBA, pixelsWide, pixelsHigh, textureRect);
1446  glUseProgram(0);
1447  if (openGL32Core)
1448  glBindVertexArray(0);
1449  else
1450  glBindVertexArrayAPPLE(0);
1451 
1452  glBindBuffer(GL_ARRAY_BUFFER, 0);
1453 
1454  return true;
1455 }
1456 
1464 {
1465  if (!value)
1466  return NULL;
1467 
1468  json_object *js = json_object_new_object();
1469  json_object_object_add(js, "pointer", json_object_new_int64((int64_t)value));
1470  return js;
1471 }
1472 
1482 {
1483  if (!value)
1484  return NULL;
1485 
1486  __block VuoShader shader = NULL;
1487  __block IOSurfaceID surfID = 0;
1488  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
1489 // VLog("Creating an IOSurface from glTextureName %d on target %lu",value->glTextureName,value->glTextureTarget);
1490 
1491  if (value->glTextureTarget == GL_TEXTURE_2D)
1492  shader = VuoShader_makeUnlitAlphaPassthruImageShader(value, false);
1493  else if (value->glTextureTarget == GL_TEXTURE_RECTANGLE_ARB)
1495  VuoRetain(shader);
1496 
1497  surfID = VuoImageRenderer_draw_internal(shader, value->pixelsWide, value->pixelsHigh, VuoImage_getColorDepth(value), true, true, 0, NULL);
1498 // VLog("Created IOSurfaceID %d",surfID);
1499 
1500  // Ensure the command queue gets executed before we return,
1501  // since the IOSurface might immediately be used on another context.
1502  glFlushRenderAPPLE();
1503  });
1504  if (!surfID)
1505  return NULL;
1506 
1507  json_object * js = json_object_new_object();
1508  {
1509  json_object * o = json_object_new_int(surfID);
1510  json_object_object_add(js, "ioSurface", o);
1511  }
1512 
1513  {
1514  json_object * o = json_object_new_int64(value->pixelsWide);
1515  json_object_object_add(js, "pixelsWide", o);
1516  }
1517  {
1518  json_object * o = json_object_new_int64(value->pixelsHigh);
1519  json_object_object_add(js, "pixelsHigh", o);
1520  }
1521  {
1522  json_object *o = json_object_new_double(value->scaleFactor);
1523  json_object_object_add(js, "scaleFactor", o);
1524  }
1525  // VuoShader_makeUnlitImageShader retains the image; VuoRelease(shader) then releases it.
1526  // So don't release the shader until we're done with the image, in case this release is its last.
1527  VuoRelease(shader);
1528 
1529  return js;
1530 }
1531 
1541 char * VuoImage_getSummary(const VuoImage value)
1542 {
1543  if (!value)
1544  return strdup("No image");
1545 
1546  const char *type;
1547  switch (value->glInternalFormat)
1548  {
1549  case GL_RGB: type = "RGB, each channel 8-bit unsigned integer"; break;
1550  case GL_RGB16F_ARB: type = "RGB, each channel 16-bit signed float"; break;
1551  case GL_RGB32F_ARB: type = "RGB, each channel 32-bit signed float"; break;
1552  case GL_RGBA: type = "RGBA, each channel 8-bit unsigned integer"; break;
1553  case GL_RGBA16F_ARB: type = "RGBA, each channel 16-bit signed float"; break;
1554  case GL_RGBA32F_ARB: type = "RGBA, each channel 32-bit signed float"; break;
1555  case GL_LUMINANCE8: type = "intensity, 8-bit unsigned integer"; break;
1556  case GL_LUMINANCE16F_ARB: type = "intensity, 16-bit signed float"; break;
1557  case GL_LUMINANCE32F_ARB: type = "intensity, 32-bit signed float"; break;
1558  case GL_LUMINANCE8_ALPHA8: type = "intensity+alpha, each channel 8-bit unsigned integer"; break;
1559  case GL_LUMINANCE_ALPHA16F_ARB: type = "intensity+alpha, each channel 16-bit signed float"; break;
1560  case GL_LUMINANCE_ALPHA32F_ARB: type = "intensity+alpha, each channel 32-bit signed float"; break;
1561  case GL_DEPTH_COMPONENT: type = "intensity, 16-bit signed float"; break;
1562  default: type = "(unknown)";
1563  }
1564 
1565  char *target = VuoGl_stringForConstant(value->glTextureTarget);
1566  char *internalformat = VuoGl_stringForConstant(value->glInternalFormat);
1567 
1568  char *summary = VuoText_format("<div>%luĂ—%lu pixels @ %gx</div><div>%s</div><div>OpenGL: %s, %s, ID %u</div>",
1569  value->pixelsWide, value->pixelsHigh,
1570  value->scaleFactor,
1571  type,
1572  target,
1573  internalformat,
1574  value->glTextureName);
1575 
1576  free(internalformat);
1577 
1578  return summary;
1579 }