Vuo 2.4.4
Loading...
Searching...
No Matches
VuoGraphicsLayer.m
Go to the documentation of this file.
1
10#import "VuoGraphicsLayer.h"
11
12#import "VuoApp.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
46static 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;
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;
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 VUserLog("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