Vuo  2.1.1
VuoGraphicsLayer.m
Go to the documentation of this file.
1 
10 #import "VuoGraphicsLayer.h"
11 
12 #import "VuoApp.h"
13 #import "VuoCompositionState.h"
14 #import "VuoCglPixelFormat.h"
15 #import "VuoEventLoop.h"
16 #import "VuoGraphicsView.h"
17 
18 #import <OpenGL/gl.h>
20 #define glGenVertexArrays glGenVertexArraysAPPLE
21 #define glBindVertexArray glBindVertexArrayAPPLE
22 #define glDeleteVertexArrays glDeleteVertexArraysAPPLE
23 
25 #import <IOSurface/IOSurface.h>
26 
27 #ifdef VUO_COMPILER
29  "title" : "VuoGraphicsLayer",
30  "dependencies" : [
31  "VuoCglPixelFormat",
32  "VuoDisplayRefresh",
33  "VuoGraphicsView",
34  "OpenGL.framework",
35  "QuartzCore.framework",
36  ]
37 });
38 #endif
39 
43 static GLuint CompileShader(GLenum type, const char *source)
44 {
45  GLint length = (GLint)strlen(source);
46  GLuint shader = glCreateShader(type);
47  glShaderSource(shader, 1, (const GLchar**)&source, &length);
48  glCompileShader(shader);
49 
50  int infologLength = 0;
51  glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infologLength);
52  if (infologLength > 0)
53  {
54  char *infoLog = (char *)malloc(infologLength);
55  int charsWritten = 0;
56  glGetShaderInfoLog(shader, infologLength, &charsWritten, infoLog);
57  VUserLog("%s", infoLog);
58  free(infoLog);
59  }
60  return shader;
61 }
62 
66 @interface VuoGraphicsLayer ()
68 @property VuoGraphicsWindowInitCallback initCallback;
72 @property void *userData;
74 
75 @property dispatch_queue_t drawQueue;
76 
77 @property double renderScheduled;
78 
80 
81 @property GLuint receiveTextureUniform;
82 @property GLuint receiveTextureOffsetUniform;
83 
84 @property bool firstFrame;
85 @property bool closed;
86 
88 @property double lastProfileLoggedTime;
89 @end
90 
92 
99 {
100  if (l.ioSurface)
101  {
103  VuoIoSurfacePool_disuse(l.ioSurface, false); // Guaranteed to have finished blitting to the window by this point, so no need to quarantine.
104  l.ioSurface = NULL;
105  }
106 
107  void *callerData = l.userData;
108  VuoGraphicsWindow *gw = l.window;
109  VuoGraphicsView *gv = (VuoGraphicsView *)gw.contentView;
110 
111  NSRect frame = l.frame;
112  frame.origin.x *= gw.backingScaleFactor;
113  frame.origin.y *= gw.backingScaleFactor;
114  frame.size.width *= gw.backingScaleFactor;
115  frame.size.height *= gw.backingScaleFactor;
116 
117  {
118  // Dimensions, in pixels (not points).
119  int x = 0;
120  int y = 0;
121  int width = frame.size.width;
122  int height = frame.size.height;
123 
124  // When fullscreen and aspect-locked, letterbox the scene (render to a centered rectangle that fits the size of the screen and matches the aspect of the now-hidden window).
125  NSSize desiredAspectSize = gw.contentAspectRatio;
126  bool isAspectLocked = !NSEqualSizes(desiredAspectSize, NSMakeSize(0,0));
127  if (gw.isFullScreen && isAspectLocked)
128  {
129  float desiredAspect = desiredAspectSize.width / desiredAspectSize.height;
130  float screenAspect = frame.size.width / frame.size.height;
131 
132  if (desiredAspect > screenAspect)
133  {
134  // Match the screen's width.
135  width = frame.size.width;
136  height = frame.size.width / desiredAspect;
137  x = 0;
138  y = frame.size.height/2 - height/2;
139  }
140  else
141  {
142  // Match the screen's height.
143  width = frame.size.height * desiredAspect;
144  height = frame.size.height;
145  x = frame.size.width/2 - width/2;
146  y = 0;
147  }
148  }
149 
150  // When fullscreen and non-resizable, windowbox the scene (render to a centered rectangle matching the size of the now-hidden window).
151  bool isWindowResizable = (gw.isFullScreen ? gw.styleMaskWhenWindowed : gw.styleMask) & NSResizableWindowMask;
152  if (gw.isFullScreen && !isWindowResizable)
153  {
154  NSRect contentRectWhenWindowed = [gv convertRectToBacking:gw.contentRectWhenWindowed];
155  width = contentRectWhenWindowed.size.width;
156  height = contentRectWhenWindowed.size.height;
157  x = frame.size.width/2 - width/2;
158  y = frame.size.height/2 - height/2;
159  }
160 
161 
162  bool backingScaleFactorChanged = (gw.backingScaleFactorCached != gw.backingScaleFactor);
163  NSRect newViewport = NSMakeRect(x, y, width, height);
164  if (!NSEqualRects(gv.viewport, newViewport) || l.firstFrame || backingScaleFactorChanged)
165  {
166  // When moving a window between Retina and non-Retina displays,
167  // viewDidChangeBackingProperties might be invoked either before or after the next
168  // VuoGraphicsLayer_drawOnIOSurface execution. Ensure that, if the backing has changed,
169  // we call backingChangedCallback, then resizeCallback, then drawCallback
170  // (since backingChangedCallback creates a new VuoSceneRenderer, without a size).
171  // https://b33p.net/kosada/node/12273
172  if (backingScaleFactorChanged)
174 
175  l.resizeCallback(callerData, width, height);
176  gv.viewport = newViewport;
177  l.firstFrame = NO;
178  }
179  }
180 
181 
182  // Draw onto the IOSurface FBO.
183  VuoIoSurface vis = l.drawCallback(callerData);
184  if (!vis)
185  return;
186 
187  [gw.recorder saveImage:vis];
188 
189  l.ioSurface = vis;
190 }
191 
192 @implementation VuoGraphicsLayer
193 
199 - (instancetype)initWithWindow:(VuoGraphicsWindow *)window
200  initCallback:(VuoGraphicsWindowInitCallback)initCallback
201  updateBackingCallback:(VuoGraphicsWindowUpdateBackingCallback)updateBackingCallback
202  backingScaleFactor:(float)backingScaleFactor
203  resizeCallback:(VuoGraphicsWindowResizeCallback)resizeCallback
204  drawCallback:(VuoGraphicsWindowDrawCallback)drawCallback
205  userData:(void *)userData
206 {
207  if (self = [super init])
208  {
209  _window = window;
210  _initCallback = initCallback;
211  _updateBackingCallback = updateBackingCallback;
212  _resizeCallback = resizeCallback;
213  _drawCallback = drawCallback;
214  _userData = userData;
215 
216  if ([self respondsToSelector:@selector(setColorspace:)])
217  [self performSelector:@selector(setColorspace:) withObject:(id)CGColorSpaceCreateWithName(kCGColorSpaceSRGB)];
218 
219  self.needsDisplayOnBoundsChange = YES;
220 
222  _drawQueue = dispatch_queue_create("org.vuo.VuoGraphicsLayer", VuoEventLoop_getDispatchInteractiveAttribute());
223 
224  _renderScheduled = VuoLogGetElapsedTime();
225 
226  _firstFrame = YES;
227 
228  _initCallback(_userData, backingScaleFactor);
229 
230  _displayRefresh = VuoDisplayRefresh_make(self);
231  VuoRetain(_displayRefresh);
232 
233  _lastProfileLoggedTime = VuoLogGetTime();
234  }
235  return self;
236 }
237 
241 - (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask
242 {
243  return VuoGlContext_makePlatformPixelFormat(false, false, -1);
244 }
245 
251 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat
252 {
253  CGLContextObj context = [super copyCGLContextForPixelFormat:pixelFormat];
254 
255  // Initialize stuff for rendering the IOSurface to the window.
256  {
257  CGLSetCurrentContext(context);
258 
259  GLuint vertexArray;
260  glGenVertexArrays(1, &vertexArray);
261  glBindVertexArray(vertexArray);
262 
263  const GLfloat trianglePositions[] = {
264  -1, -1,
265  3, -1,
266  -1, 3,
267  };
268  GLuint trianglePositionBuffer;
269  glGenBuffers(1, &trianglePositionBuffer);
270  glBindBuffer(GL_ARRAY_BUFFER, trianglePositionBuffer);
271  glBufferData(GL_ARRAY_BUFFER, sizeof(trianglePositions), trianglePositions, GL_STATIC_DRAW);
272  VuoGlPool_logVRAMAllocated(sizeof(trianglePositions));
273 
274  const char *vertexShaderSource = VUOSHADER_GLSL_SOURCE(120,
275  attribute vec2 position;
276  void main()
277  {
278  gl_Position = vec4(position.x, position.y, 0., 1.);
279  }
280  );
281  const char *fragmentShaderSource = VUOSHADER_GLSL_SOURCE(120,
282  uniform sampler2DRect t;
283  uniform vec2 textureOrigin;
284  void main()
285  {
286  gl_FragColor = texture2DRect(t, gl_FragCoord.xy - textureOrigin);
287  }
288  );
289  GLuint vertexShader = CompileShader(GL_VERTEX_SHADER, vertexShaderSource);
290  GLuint fragmentShader = CompileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
291  GLuint program = glCreateProgram();
292  glAttachShader(program, vertexShader);
293  glAttachShader(program, fragmentShader);
294  glLinkProgram(program);
295 
296  GLuint positionAttribute = glGetAttribLocation(program, "position");
297 
298  _receiveTextureUniform = glGetUniformLocation(program, "t");
299  _receiveTextureOffsetUniform = glGetUniformLocation(program, "textureOrigin");
300  glUseProgram(program);
301  glVertexAttribPointer(positionAttribute, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*2, (void*)0);
302  glEnableVertexAttribArray(positionAttribute);
303 
304  CGLSetCurrentContext(NULL);
305  }
306 
307  return context;
308 }
309 
316 {
317  float backingScaleFactor = _window.backingScaleFactor;
318  if (backingScaleFactor != _window.backingScaleFactorCached)
319  {
320  VDebugLog("backingScaleFactor changed from %g to %g", _window.backingScaleFactorCached, backingScaleFactor);
321  float oldBackingScaleFactor = _window.backingScaleFactorCached;
322  NSSize contentSize = [_window contentRectForFrameRect:_window.frame].size;
323 
324  _window.backingScaleFactorCached = backingScaleFactor;
325  self.contentsScale = backingScaleFactor;
326 
327  _updateBackingCallback(_userData, backingScaleFactor);
328 
329  if (_window.maintainsPixelSizeWhenBackingChanges)
330  {
331  float backingScaleRatio = oldBackingScaleFactor / backingScaleFactor;
332  [_window scheduleResize:NSMakeSize(contentSize.width * backingScaleRatio,
333  contentSize.height * backingScaleRatio)];
334  }
335 
336  // _updateBackingCallback destroys and recreates the VuoSceneRenderer;
337  // ensure the new one knows its size.
338  NSSize newContentSize = [_window contentRectForFrameRect:_window.frame].size;
339  _resizeCallback(_userData, newContentSize.width * _window.backingScaleFactorCached,
340  newContentSize.height * _window.backingScaleFactorCached);
341  }
342 }
343 
347 - (void)enableTriggers
348 {
349  VuoGraphicsWindow *gw = self.window;
350  VuoDisplayRefresh_enableTriggers(_displayRefresh, gw.requestedFrame, NULL);
351 }
352 
356 - (void)disableTriggers
357 {
358  VuoDisplayRefresh_disableTriggers(_displayRefresh);
359 }
360 
364 - (void)setBounds:(CGRect)bounds
365 {
366  [super setBounds:bounds];
367 }
368 
374 - (void)draw
375 {
376  double now = VuoLogGetElapsedTime();
377  if (now - _renderScheduled > 0.4)
378  {
379  _renderScheduled = now;
380  dispatch_async(dispatch_get_main_queue(), ^{
381  [self setNeedsDisplay];
382  });
383  }
384 }
385 
392 - (void)drawInCGLContext:(CGLContextObj)context pixelFormat:(CGLPixelFormatObj)pixelFormat forLayerTime:(CFTimeInterval)timeInterval displayTime:(const CVTimeStamp *)timeStamp
393 {
394  _renderScheduled = -INFINITY;
395 
396  if (_closed)
397  return;
398 
399  dispatch_sync(_drawQueue, ^{
401  if (!_ioSurface)
402  return;
403 
404  NSRect frameInPoints = self.frame;
405  double s = _window.backingScaleFactor;
406  NSRect frameInPixels = NSMakeRect(frameInPoints.origin.x * s, frameInPoints.origin.y * s, frameInPoints.size.width * s, frameInPoints.size.height * s);
407  size_t ioSurfaceWidth = VuoIoSurfacePool_getWidth(_ioSurface);
408  size_t ioSurfaceHeight = VuoIoSurfacePool_getHeight(_ioSurface);
409 
410  VuoGraphicsView *gv = (VuoGraphicsView *)_window.contentView;
411  NSRect viewport = gv.viewport;
412 
413  // Prepare a texture to receive the IOSurface onto the local OpenGL context.
414  GLuint receiveTexture;
415  GLuint receiveTextureBytes;
416  {
417  glGenTextures(1, &receiveTexture); // This can't participate in the VuoGlTexturePool since it's not on Vuo's shared context.
418  glActiveTexture(GL_TEXTURE0);
419  glBindTexture(GL_TEXTURE_RECTANGLE_EXT, receiveTexture);
420  glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
421  glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
422  glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
423 // glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
424  glUniform1i(_receiveTextureUniform, 0);
425 
426  CGLError err = CGLTexImageIOSurface2D(context, GL_TEXTURE_RECTANGLE_EXT, GL_RGB, ioSurfaceWidth, ioSurfaceHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, VuoIoSurfacePool_getIOSurfaceRef(_ioSurface), 0);
427  if(err != kCGLNoError)
428  {
429  VUserLog("Error in CGLTexImageIOSurface2D(context, GL_TEXTURE_RECTANGLE_EXT, GL_RGB, %zu, %zu, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, %d, 0) 2: %s", ioSurfaceWidth, ioSurfaceHeight, VuoIoSurfacePool_getId(_ioSurface), CGLErrorString(err));
430  return;
431  }
432 
433  receiveTextureBytes = ioSurfaceWidth * ioSurfaceHeight * 4;
434  VuoGlPool_logVRAMAllocated(receiveTextureBytes);
435 
436  glUniform2f(_receiveTextureOffsetUniform, viewport.origin.x, viewport.origin.y);
437  glViewport(viewport.origin.x, viewport.origin.y, viewport.size.width, viewport.size.height);
438  }
439 
440 
441  // Draw the active IOSurface onto the window, using the local OpenGL context.
442  {
443  if (!NSEqualRects(viewport, frameInPixels))
444  {
445  // Clear the parts of the view that we aren't rendering over.
446  glClearColor(0,0,0,1);
447  glClear(GL_COLOR_BUFFER_BIT);
448  }
449 
450  glBindTexture(GL_TEXTURE_RECTANGLE_EXT, receiveTexture);
451  glDrawArrays(GL_TRIANGLES, 0, 3);
452 
453  glBindTexture(GL_TEXTURE_RECTANGLE_EXT, 0);
454  glDeleteTextures(1, &receiveTexture);
455  VuoGlPool_logVRAMFreed(receiveTextureBytes);
456 
457  glFlush();
458  }
459  });
460 
461  if (VuoIsDebugEnabled())
462  {
463  ++_framesRenderedSinceProfileLogged;
464  double t = VuoLogGetTime();
465  const double profileSeconds = 10;
466  if (t > _lastProfileLoggedTime + profileSeconds)
467  {
468  VDebugLog("%4.1f fps", _framesRenderedSinceProfileLogged / profileSeconds);
469  _lastProfileLoggedTime = t;
470  _framesRenderedSinceProfileLogged = 0;
471  }
472  }
473 }
474 
475 
479 - (void)close
480 {
481  _closed = YES;
482  VuoRelease(_displayRefresh);
483 }
484 
485 #pragma clang diagnostic push
486 #pragma clang diagnostic ignored "-Wobjc-missing-super-calls"
487 
490 - (void)dealloc
491 {
492  // Dynamically changing the layer's contentsScale causes dealloc to be called
493  // even though the layer has a nonzero retain count (!),
494  // so we can't do anything here.
495 
496 // dispatch_release(_drawQueue);
497 // if (_ioSurface)
498 // VuoIoSurfacePool_disuse(_ioSurface);
499 // [super dealloc];
500 }
501 #pragma clang diagnostic pop
502 
503 @end