1 /*****************************************************************************
2 * caopengllayer.m: CAOpenGLLayer (Mac OS X) video output
3 *****************************************************************************
4 * Copyright (C) 2014-2017 VLC authors and VideoLAN
7 * Authors: David Fuhrmann <david dot fuhrmann at googlemail dot com>
8 * Felix Paul Kühne <fkuehne at videolan dot org>
9 * Pierre d'Herbemont <pdherbemont at videolan dot org>
11 * This program is free software; you can redistribute it and/or modify it
12 * under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation; either version 2.1 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public License
22 * along with this program; if not, write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24 *****************************************************************************/
26 /*****************************************************************************
28 *****************************************************************************/
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include <vlc_vout_display.h>
37 #include <vlc_opengl.h>
39 #import <QuartzCore/QuartzCore.h>
40 #import <Cocoa/Cocoa.h>
41 #import <OpenGL/OpenGL.h>
42 #import <dlfcn.h> /* dlsym */
44 #include "opengl/vout_helper.h"
46 #define OSX_SIERRA_AND_HIGHER (NSAppKitVersionNumber >= 1485)
48 /*****************************************************************************
50 *****************************************************************************/
51 static int Open (vlc_object_t *);
52 static void Close (vlc_object_t *);
55 set_description(N_("Core Animation OpenGL Layer (Mac OS X)"))
56 set_capability("vout display", 0)
57 set_category(CAT_VIDEO)
58 set_subcategory(SUBCAT_VIDEO_VOUT)
59 set_callbacks(Open, Close)
62 static picture_pool_t *Pool (vout_display_t *vd, unsigned requested_count);
63 static void PictureRender (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture);
64 static void PictureDisplay (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture);
65 static int Control (vout_display_t *vd, int query, va_list ap);
67 static void *OurGetProcAddress (vlc_gl_t *gl, const char *name);
68 static int OpenglLock (vlc_gl_t *gl);
69 static void OpenglUnlock (vlc_gl_t *gl);
70 static void OpenglSwap (vlc_gl_t *gl);
72 @protocol VLCCoreAnimationVideoLayerEmbedding <NSObject>
73 - (void)addVoutLayer:(CALayer *)aLayer;
74 - (void)removeVoutLayer:(CALayer *)aLayer;
75 - (CGSize)currentOutputSize;
78 @interface VLCCAOpenGLLayer : CAOpenGLLayer
80 @property (nonatomic, readwrite) vout_display_t* voutDisplay;
81 @property (nonatomic, readwrite) CGLContextObj glContext;
86 struct vout_display_sys_t {
89 picture_resource_t resource;
91 CALayer <VLCCoreAnimationVideoLayerEmbedding> *container;
93 VLCCAOpenGLLayer *cgLayer;
96 vout_display_opengl_t *vgl;
98 vout_display_place_t place;
100 bool b_frame_available;
105 CGLContextObj locked_ctx;
106 VLCCAOpenGLLayer *cgLayer;
109 /*****************************************************************************
110 * Open: This function allocates and initializes the OpenGL vout method.
111 *****************************************************************************/
112 static int Open (vlc_object_t *p_this)
114 vout_display_t *vd = (vout_display_t *)p_this;
115 vout_display_sys_t *sys;
117 /* Allocate structure */
118 vd->sys = sys = calloc(1, sizeof(vout_display_sys_t));
123 id container = var_CreateGetAddress(vd, "drawable-nsobject");
125 vout_display_DeleteWindow(vd, NULL);
127 sys->embed = vout_display_NewWindow(vd, VOUT_WINDOW_TYPE_NSOBJECT);
129 container = sys->embed->handle.nsobject;
132 msg_Err(vd, "No drawable-nsobject found!");
137 /* store for later, released in Close() */
138 sys->container = [container retain];
140 [CATransaction begin];
141 sys->cgLayer = [[VLCCAOpenGLLayer alloc] init];
142 [sys->cgLayer setVoutDisplay:vd];
144 [sys->cgLayer performSelectorOnMainThread:@selector(display)
148 if ([container respondsToSelector:@selector(addVoutLayer:)]) {
149 msg_Dbg(vd, "container implements implicit protocol");
150 [container addVoutLayer:sys->cgLayer];
151 } else if ([container respondsToSelector:@selector(addSublayer:)] ||
152 [container isKindOfClass:[CALayer class]]) {
153 msg_Dbg(vd, "container doesn't implement implicit protocol, fallback mode used");
154 [container addSublayer:sys->cgLayer];
156 msg_Err(vd, "Provided NSObject container isn't compatible");
157 [sys->cgLayer release];
159 [CATransaction commit];
162 [CATransaction commit];
167 if (![sys->cgLayer glContext])
168 msg_Warn(vd, "we might not have an OpenGL context yet");
170 /* Initialize common OpenGL video display */
171 sys->gl = vlc_object_create(vd, sizeof(*sys->gl));
172 if (unlikely(!sys->gl))
174 sys->gl->makeCurrent = OpenglLock;
175 sys->gl->releaseCurrent = OpenglUnlock;
176 sys->gl->swap = OpenglSwap;
177 sys->gl->getProcAddress = OurGetProcAddress;
179 struct gl_sys *glsys = sys->gl->sys = malloc(sizeof(*glsys));
182 glsys->locked_ctx = NULL;
183 glsys->cgLayer = sys->cgLayer;
185 const vlc_fourcc_t *subpicture_chromas;
186 video_format_t fmt = vd->fmt;
187 if (!OpenglLock(sys->gl)) {
188 sys->vgl = vout_display_opengl_New(&vd->fmt, &subpicture_chromas,
189 sys->gl, &vd->cfg->viewpoint);
190 OpenglUnlock(sys->gl);
194 msg_Err(vd, "Error while initializing opengl display.");
198 /* setup vout display */
199 vout_display_info_t info = vd->info;
200 info.subpicture_chromas = subpicture_chromas;
204 vd->prepare = PictureRender;
205 vd->display = PictureDisplay;
206 vd->control = Control;
208 if (OSX_SIERRA_AND_HIGHER) {
209 /* request our screen's HDR mode (introduced in OS X 10.11, but correctly supported in 10.12 only) */
210 if ([sys->cgLayer respondsToSelector:@selector(setWantsExtendedDynamicRangeContent:)]) {
211 [sys->cgLayer setWantsExtendedDynamicRangeContent:YES];
215 /* setup initial state */
217 if ([container respondsToSelector:@selector(currentOutputSize)])
218 outputSize = [container currentOutputSize];
220 outputSize = [sys->container visibleRect].size;
221 vout_display_SendEventDisplaySize(vd, (int)outputSize.width, (int)outputSize.height);
231 static void Close (vlc_object_t *p_this)
233 vout_display_t *vd = (vout_display_t *)p_this;
234 vout_display_sys_t *sys = vd->sys;
237 if ([sys->container respondsToSelector:@selector(removeVoutLayer:)])
238 [sys->container removeVoutLayer:sys->cgLayer];
240 [sys->cgLayer removeFromSuperlayer];
242 if ([sys->cgLayer glContext])
243 CGLReleaseContext([sys->cgLayer glContext]);
245 [sys->cgLayer release];
249 [sys->container release];
252 vout_display_DeleteWindow(vd, sys->embed);
254 if (sys->vgl != NULL && !OpenglLock(sys->gl)) {
255 vout_display_opengl_Delete(sys->vgl);
256 OpenglUnlock(sys->gl);
261 if (sys->gl->sys != NULL)
263 assert(((struct gl_sys *)sys->gl->sys)->locked_ctx == NULL);
266 vlc_object_release(sys->gl);
272 static picture_pool_t *Pool (vout_display_t *vd, unsigned count)
274 vout_display_sys_t *sys = vd->sys;
276 if (!sys->pool && !OpenglLock(sys->gl)) {
277 sys->pool = vout_display_opengl_GetPool(sys->vgl, count);
278 OpenglUnlock(sys->gl);
284 static void PictureRender (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
286 vout_display_sys_t *sys = vd->sys;
289 msg_Warn(vd, "invalid pic, skipping frame");
293 @synchronized (sys->cgLayer) {
294 if (!OpenglLock(sys->gl)) {
295 vout_display_opengl_Prepare(sys->vgl, pic, subpicture);
296 OpenglUnlock(sys->gl);
301 static void PictureDisplay (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
303 vout_display_sys_t *sys = vd->sys;
305 @synchronized (sys->cgLayer) {
306 sys->b_frame_available = YES;
308 /* Calling display on the non-main thread is not officially supported, but
309 * its suggested at several places and works fine here. Flush is thread-safe
310 * and makes sure the picture is actually displayed. */
311 [sys->cgLayer display];
312 [CATransaction flush];
315 picture_Release(pic);
318 subpicture_Delete(subpicture);
321 static int Control (vout_display_t *vd, int query, va_list ap)
323 vout_display_sys_t *sys = vd->sys;
330 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
331 case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
332 case VOUT_DISPLAY_CHANGE_ZOOM:
333 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
334 case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
336 const vout_display_cfg_t *cfg;
338 if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT || query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
341 cfg = (const vout_display_cfg_t*)va_arg (ap, const vout_display_cfg_t *);
344 /* we always use our current frame here */
345 vout_display_cfg_t cfg_tmp = *cfg;
346 [CATransaction lock];
347 CGRect bounds = [sys->cgLayer visibleRect];
348 [CATransaction unlock];
349 cfg_tmp.display.width = bounds.size.width;
350 cfg_tmp.display.height = bounds.size.height;
352 /* Reverse vertical alignment as the GL tex are Y inverted */
353 if (cfg_tmp.align.vertical == VOUT_DISPLAY_ALIGN_TOP)
354 cfg_tmp.align.vertical = VOUT_DISPLAY_ALIGN_BOTTOM;
355 else if (cfg_tmp.align.vertical == VOUT_DISPLAY_ALIGN_BOTTOM)
356 cfg_tmp.align.vertical = VOUT_DISPLAY_ALIGN_TOP;
358 vout_display_place_t place;
359 vout_display_PlacePicture (&place, &vd->source, &cfg_tmp, false);
360 if (OpenglLock(sys->gl))
363 vout_display_opengl_SetWindowAspectRatio(sys->vgl, (float)place.width / place.height);
364 OpenglUnlock(sys->gl);
371 case VOUT_DISPLAY_CHANGE_VIEWPOINT:
375 if (OpenglLock(sys->gl))
378 ret = vout_display_opengl_SetViewpoint(sys->vgl,
379 &va_arg (ap, const vout_display_cfg_t* )->viewpoint);
380 OpenglUnlock(sys->gl);
384 case VOUT_DISPLAY_RESET_PICTURES:
385 vlc_assert_unreachable ();
387 msg_Err (vd, "Unhandled request %d", query);
395 #pragma mark OpenGL callbacks
397 static int OpenglLock (vlc_gl_t *gl)
399 struct gl_sys *sys = gl->sys;
400 assert(sys->locked_ctx == NULL);
402 CGLContextObj ctx = [sys->cgLayer glContext];
407 CGLError err = CGLLockContext(ctx);
408 if (kCGLNoError == err) {
409 sys->locked_ctx = ctx;
410 CGLSetCurrentContext(ctx);
416 static void OpenglUnlock (vlc_gl_t *gl)
418 struct gl_sys *sys = gl->sys;
419 CGLUnlockContext(sys->locked_ctx);
420 sys->locked_ctx = NULL;
423 static void OpenglSwap (vlc_gl_t *gl)
428 static void *OurGetProcAddress (vlc_gl_t *gl, const char *name)
432 return dlsym(RTLD_DEFAULT, name);
436 #pragma mark CA layer
438 /*****************************************************************************
439 * @implementation VLCCAOpenGLLayer
440 *****************************************************************************/
441 @implementation VLCCAOpenGLLayer
447 [CATransaction lock];
448 self.needsDisplayOnBoundsChange = YES;
449 self.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
450 self.asynchronous = NO;
451 [CATransaction unlock];
457 - (void)setVoutDisplay:(vout_display_t *)aVd
462 - (void)resizeWithOldSuperlayerSize:(CGSize)size
464 [super resizeWithOldSuperlayerSize: size];
466 CGSize boundsSize = self.visibleRect.size;
469 vout_display_SendEventDisplaySize(_voutDisplay, boundsSize.width, boundsSize.height);
472 - (BOOL)canDrawInCGLContext:(CGLContextObj)glContext pixelFormat:(CGLPixelFormatObj)pixelFormat forLayerTime:(CFTimeInterval)timeInterval displayTime:(const CVTimeStamp *)timeStamp
474 /* Only draw the frame if we have a frame that was previously rendered */
478 return _voutDisplay->sys->b_frame_available;
481 - (void)drawInCGLContext:(CGLContextObj)glContext pixelFormat:(CGLPixelFormatObj)pixelFormat forLayerTime:(CFTimeInterval)timeInterval displayTime:(const CVTimeStamp *)timeStamp
485 vout_display_sys_t *sys = _voutDisplay->sys;
490 CGRect bounds = [self visibleRect];
492 // x / y are top left corner, but we need the lower left one
493 vout_display_opengl_Viewport(sys->vgl, sys->place.x,
494 bounds.size.height - (sys->place.y + sys->place.height),
495 sys->place.width, sys->place.height);
497 // flush is also done by this method, no need to call super
498 vout_display_opengl_Display (sys->vgl, &_voutDisplay->source);
499 sys->b_frame_available = NO;
502 -(CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask
504 // The default is fine for this demonstration.
505 return [super copyCGLPixelFormatForDisplayMask:mask];
508 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat
510 // Only one opengl context is allowed for the module lifetime
512 msg_Dbg(_voutDisplay, "Return existing context: %p", _glContext);
516 CGLContextObj context = [super copyCGLContextForPixelFormat:pixelFormat];
518 // Swap buffers only during the vertical retrace of the monitor.
519 //http://developer.apple.com/documentation/GraphicsImaging/
520 //Conceptual/OpenGL/chap5/chapter_5_section_44.html /
523 CGLSetParameter( CGLGetCurrentContext(), kCGLCPSwapInterval,
526 @synchronized (self) {
527 _glContext = context;
533 - (void)releaseCGLContext:(CGLContextObj)glContext
535 // do not release anything here, we do that when closing the module
538 - (void)mouseButtonDown:(int)buttonNumber
540 @synchronized (self) {
542 if (buttonNumber == 0)
543 vout_display_SendEventMousePressed (_voutDisplay, MOUSE_BUTTON_LEFT);
544 else if (buttonNumber == 1)
545 vout_display_SendEventMousePressed (_voutDisplay, MOUSE_BUTTON_RIGHT);
547 vout_display_SendEventMousePressed (_voutDisplay, MOUSE_BUTTON_CENTER);
552 - (void)mouseButtonUp:(int)buttonNumber
554 @synchronized (self) {
556 if (buttonNumber == 0)
557 vout_display_SendEventMouseReleased (_voutDisplay, MOUSE_BUTTON_LEFT);
558 else if (buttonNumber == 1)
559 vout_display_SendEventMouseReleased (_voutDisplay, MOUSE_BUTTON_RIGHT);
561 vout_display_SendEventMouseReleased (_voutDisplay, MOUSE_BUTTON_CENTER);
566 - (void)mouseMovedToX:(double)xValue Y:(double)yValue
568 @synchronized (self) {
570 vout_display_SendMouseMovedDisplayCoordinates (_voutDisplay,
574 &_voutDisplay->sys->place);