Vuo  2.0.2
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  // Get the window's current size in pixels.
322  NSRect contentRect = [_window contentRectForFrameRect:_window.frame];
323  NSSize oldContentRectPixelSize = NSMakeSize(contentRect.size.width * _window.backingScaleFactorCached,
324  contentRect.size.height * _window.backingScaleFactorCached);
325 
326  _window.backingScaleFactorCached = backingScaleFactor;
327  self.contentsScale = backingScaleFactor;
328 
329  _updateBackingCallback(_userData, backingScaleFactor);
330 
331  if (_window.maintainsPixelSizeWhenBackingChanges)
332  {
333  // Resize the window to maintain its pixel size.
334 
335  NSSize newContentRectPointSize = NSMakeSize(oldContentRectPixelSize.width / _window.backingScaleFactorCached,
336  oldContentRectPixelSize.height / _window.backingScaleFactorCached);
337 
338  NSRect contentRect = [_window contentRectForFrameRect:_window.frame];
339 
340  // Adjust the y position by the change in height, so that the window appears to be anchored in its top-left corner
341  // (instead of its bottom-left corner as the system does by default).
342  contentRect.origin.y += contentRect.size.height - newContentRectPointSize.height;
343 
344  contentRect.size = newContentRectPointSize;
345  [_window setFrame:[_window frameRectForContentRect:contentRect] display:YES animate:NO];
346  }
347  }
348 }
349 
354 {
355  VuoGraphicsWindow *gw = self.window;
356  VuoDisplayRefresh_enableTriggers(_displayRefresh, gw.requestedFrame, NULL);
357 }
358 
363 {
364  VuoDisplayRefresh_disableTriggers(_displayRefresh);
365 }
366 
370 - (void)setBounds:(CGRect)bounds
371 {
372  [super setBounds:bounds];
373 }
374 
380 - (void)draw
381 {
382  double now = VuoLogGetElapsedTime();
383  if (now - _renderScheduled > 0.4)
384  {
385  _renderScheduled = now;
386  dispatch_async(dispatch_get_main_queue(), ^{
387  [self setNeedsDisplay];
388  });
389  }
390 }
391 
398 - (void)drawInCGLContext:(CGLContextObj)context pixelFormat:(CGLPixelFormatObj)pixelFormat forLayerTime:(CFTimeInterval)timeInterval displayTime:(const CVTimeStamp *)timeStamp
399 {
400  _renderScheduled = -INFINITY;
401 
402  if (_closed)
403  return;
404 
405  dispatch_sync(_drawQueue, ^{
407  if (!_ioSurface)
408  return;
409 
410  NSRect frameInPoints = self.frame;
411  double s = _window.backingScaleFactor;
412  NSRect frameInPixels = NSMakeRect(frameInPoints.origin.x * s, frameInPoints.origin.y * s, frameInPoints.size.width * s, frameInPoints.size.height * s);
413  size_t ioSurfaceWidth = VuoIoSurfacePool_getWidth(_ioSurface);
414  size_t ioSurfaceHeight = VuoIoSurfacePool_getHeight(_ioSurface);
415 
416  VuoGraphicsView *gv = (VuoGraphicsView *)_window.contentView;
417  NSRect viewport = gv.viewport;
418 
419  // Prepare a texture to receive the IOSurface onto the local OpenGL context.
420  GLuint receiveTexture;
421  GLuint receiveTextureBytes;
422  {
423  glGenTextures(1, &receiveTexture); // This can't participate in the VuoGlTexturePool since it's not on Vuo's shared context.
424  glActiveTexture(GL_TEXTURE0);
425  glBindTexture(GL_TEXTURE_RECTANGLE_EXT, receiveTexture);
426  glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
427  glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
428  glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
429 // glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
430  glUniform1i(_receiveTextureUniform, 0);
431 
432  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);
433  if(err != kCGLNoError)
434  {
435  VUserLog("Error in CGLTexImageIOSurface2D() 2: %s", CGLErrorString(err));
436  return;
437  }
438 
439  receiveTextureBytes = ioSurfaceWidth * ioSurfaceHeight * 4;
440  VuoGlPool_logVRAMAllocated(receiveTextureBytes);
441 
442  glUniform2f(_receiveTextureOffsetUniform, viewport.origin.x, viewport.origin.y);
443  glViewport(viewport.origin.x, viewport.origin.y, viewport.size.width, viewport.size.height);
444  }
445 
446 
447  // Draw the active IOSurface onto the window, using the local OpenGL context.
448  {
449  if (!NSEqualRects(viewport, frameInPixels))
450  {
451  // Clear the parts of the view that we aren't rendering over.
452  glClearColor(0,0,0,1);
453  glClear(GL_COLOR_BUFFER_BIT);
454  }
455 
456  glBindTexture(GL_TEXTURE_RECTANGLE_EXT, receiveTexture);
457  glDrawArrays(GL_TRIANGLES, 0, 3);
458 
459  glBindTexture(GL_TEXTURE_RECTANGLE_EXT, 0);
460  glDeleteTextures(1, &receiveTexture);
461  VuoGlPool_logVRAMFreed(receiveTextureBytes);
462 
463  glFlush();
464  }
465  });
466 
467  if (VuoIsDebugEnabled())
468  {
469  ++_framesRenderedSinceProfileLogged;
470  double t = VuoLogGetTime();
471  const double profileSeconds = 10;
472  if (t > _lastProfileLoggedTime + profileSeconds)
473  {
474  VDebugLog("%4.1f fps", _framesRenderedSinceProfileLogged / profileSeconds);
475  _lastProfileLoggedTime = t;
476  _framesRenderedSinceProfileLogged = 0;
477  }
478  }
479 }
480 
481 
485 - (void)close
486 {
487  _closed = YES;
488  VuoRelease(_displayRefresh);
489 }
490 
491 #pragma clang diagnostic push
492 #pragma clang diagnostic ignored "-Wobjc-missing-super-calls"
493 
496 - (void)dealloc
497 {
498  // Dynamically changing the layer's contentsScale causes dealloc to be called
499  // even though the layer has a nonzero retain count (!),
500  // so we can't do anything here.
501 
502 // dispatch_release(_drawQueue);
503 // if (_ioSurface)
504 // VuoIoSurfacePool_disuse(_ioSurface);
505 // [super dealloc];
506 }
507 #pragma clang diagnostic pop
508 
509 @end