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