1 /*****************************************************************************
2 * caopengllayer.m: CAOpenGLLayer (Mac OS X) video output
3 *****************************************************************************
4 * Copyright (C) 2014-2017 VLC authors and VideoLAN
6 * Authors: David Fuhrmann <david dot fuhrmann at googlemail dot com>
7 * Felix Paul Kühne <fkuehne at videolan dot org>
8 * Pierre d'Herbemont <pdherbemont at videolan dot org>
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 2.1 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this program; if not, write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 *****************************************************************************/
25 /*****************************************************************************
27 *****************************************************************************/
33 #include <vlc_common.h>
34 #include <vlc_plugin.h>
35 #include <vlc_vout_display.h>
36 #include <vlc_opengl.h>
38 #import <QuartzCore/QuartzCore.h>
39 #import <Cocoa/Cocoa.h>
40 #import <OpenGL/OpenGL.h>
41 #import <dlfcn.h> /* dlsym */
43 #include "opengl/vout_helper.h"
45 #define OSX_SIERRA_AND_HIGHER (NSAppKitVersionNumber >= 1485)
47 /*****************************************************************************
49 *****************************************************************************/
50 static int Open(vout_display_t *vd, const vout_display_cfg_t *cfg,
51 video_format_t *fmt, vlc_video_context *context);
52 static void Close(vout_display_t *vd);
55 set_description(N_("Core Animation OpenGL Layer (Mac OS X)"))
56 set_category(CAT_VIDEO)
57 set_subcategory(SUBCAT_VIDEO_VOUT)
58 set_callback_display(Open, 0)
61 static void PictureRender (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture,
63 static void PictureDisplay (vout_display_t *vd, picture_t *pic);
64 static int Control (vout_display_t *vd, int query, va_list ap);
66 static void *OurGetProcAddress (vlc_gl_t *gl, const char *name);
67 static int OpenglLock (vlc_gl_t *gl);
68 static void OpenglUnlock (vlc_gl_t *gl);
69 static void OpenglSwap (vlc_gl_t *gl);
71 @protocol VLCCoreAnimationVideoLayerEmbedding <NSObject>
72 - (void)addVoutLayer:(CALayer *)aLayer;
73 - (void)removeVoutLayer:(CALayer *)aLayer;
74 - (CGSize)currentOutputSize;
77 @interface VLCCAOpenGLLayer : CAOpenGLLayer
79 @property (nonatomic, readwrite) vout_display_t* voutDisplay;
80 @property (nonatomic, readwrite) CGLContextObj glContext;
85 struct vout_display_sys_t {
87 CALayer <VLCCoreAnimationVideoLayerEmbedding> *container;
89 VLCCAOpenGLLayer *cgLayer;
92 vout_display_opengl_t *vgl;
94 vout_display_place_t place;
96 bool b_frame_available;
101 CGLContextObj locked_ctx;
102 VLCCAOpenGLLayer *cgLayer;
105 /*****************************************************************************
106 * Open: This function allocates and initializes the OpenGL vout method.
107 *****************************************************************************/
108 static int Open (vout_display_t *vd, const vout_display_cfg_t *cfg,
109 video_format_t *fmt, vlc_video_context *context)
111 vout_display_sys_t *sys;
113 if (cfg->window->type != VOUT_WINDOW_TYPE_NSOBJECT)
116 /* Allocate structure */
117 vd->sys = sys = calloc(1, sizeof(vout_display_sys_t));
122 id container = var_CreateGetAddress(vd, "drawable-nsobject");
124 sys->embed = cfg->window;
125 container = sys->embed->handle.nsobject;
128 msg_Err(vd, "No drawable-nsobject found!");
133 /* store for later, released in Close() */
134 sys->container = [container retain];
136 [CATransaction begin];
137 sys->cgLayer = [[VLCCAOpenGLLayer alloc] init];
138 [sys->cgLayer setVoutDisplay:vd];
140 [sys->cgLayer performSelectorOnMainThread:@selector(display)
144 if ([container respondsToSelector:@selector(addVoutLayer:)]) {
145 msg_Dbg(vd, "container implements implicit protocol");
146 [container addVoutLayer:sys->cgLayer];
147 } else if ([container respondsToSelector:@selector(addSublayer:)] ||
148 [container isKindOfClass:[CALayer class]]) {
149 msg_Dbg(vd, "container doesn't implement implicit protocol, fallback mode used");
150 [container addSublayer:sys->cgLayer];
152 msg_Err(vd, "Provided NSObject container isn't compatible");
153 [sys->cgLayer release];
155 [CATransaction commit];
158 [CATransaction commit];
163 if (![sys->cgLayer glContext])
164 msg_Warn(vd, "we might not have an OpenGL context yet");
166 /* Initialize common OpenGL video display */
167 sys->gl = vlc_object_create(vd, sizeof(*sys->gl));
168 if (unlikely(!sys->gl))
170 sys->gl->makeCurrent = OpenglLock;
171 sys->gl->releaseCurrent = OpenglUnlock;
172 sys->gl->swap = OpenglSwap;
173 sys->gl->getProcAddress = OurGetProcAddress;
175 struct gl_sys *glsys = sys->gl->sys = malloc(sizeof(*glsys));
178 glsys->locked_ctx = NULL;
179 glsys->cgLayer = sys->cgLayer;
181 const vlc_fourcc_t *subpicture_chromas;
182 if (!OpenglLock(sys->gl)) {
183 sys->vgl = vout_display_opengl_New(fmt, &subpicture_chromas,
184 sys->gl, &cfg->viewpoint, context);
185 OpenglUnlock(sys->gl);
189 msg_Err(vd, "Error while initializing opengl display.");
193 /* setup vout display */
194 vd->info.subpicture_chromas = subpicture_chromas;
196 vd->prepare = PictureRender;
197 vd->display = PictureDisplay;
198 vd->control = Control;
201 if (OSX_SIERRA_AND_HIGHER) {
202 /* request our screen's HDR mode (introduced in OS X 10.11, but correctly supported in 10.12 only) */
203 if ([sys->cgLayer respondsToSelector:@selector(setWantsExtendedDynamicRangeContent:)]) {
204 [sys->cgLayer setWantsExtendedDynamicRangeContent:YES];
208 /* setup initial state */
210 if ([container respondsToSelector:@selector(currentOutputSize)])
211 outputSize = [container currentOutputSize];
213 outputSize = [sys->container visibleRect].size;
214 vout_window_ReportSize(sys->embed, (int)outputSize.width, (int)outputSize.height);
224 static void Close(vout_display_t *vd)
226 vout_display_sys_t *sys = vd->sys;
229 if ([sys->container respondsToSelector:@selector(removeVoutLayer:)])
230 [sys->container removeVoutLayer:sys->cgLayer];
232 [sys->cgLayer removeFromSuperlayer];
234 if ([sys->cgLayer glContext])
235 CGLReleaseContext([sys->cgLayer glContext]);
237 [sys->cgLayer release];
241 [sys->container release];
243 if (sys->vgl != NULL && !OpenglLock(sys->gl)) {
244 vout_display_opengl_Delete(sys->vgl);
245 OpenglUnlock(sys->gl);
250 if (sys->gl->sys != NULL)
252 assert(((struct gl_sys *)sys->gl->sys)->locked_ctx == NULL);
255 vlc_object_delete(sys->gl);
261 static void PictureRender (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture,
265 vout_display_sys_t *sys = vd->sys;
268 msg_Warn(vd, "invalid pic, skipping frame");
272 @synchronized (sys->cgLayer) {
273 if (!OpenglLock(sys->gl)) {
274 vout_display_opengl_Prepare(sys->vgl, pic, subpicture);
275 OpenglUnlock(sys->gl);
280 static void PictureDisplay (vout_display_t *vd, picture_t *pic)
282 vout_display_sys_t *sys = vd->sys;
285 @synchronized (sys->cgLayer) {
286 sys->b_frame_available = YES;
288 /* Calling display on the non-main thread is not officially supported, but
289 * its suggested at several places and works fine here. Flush is thread-safe
290 * and makes sure the picture is actually displayed. */
291 [sys->cgLayer display];
292 [CATransaction flush];
296 static int Control (vout_display_t *vd, int query, va_list ap)
298 vout_display_sys_t *sys = vd->sys;
305 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
306 case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
307 case VOUT_DISPLAY_CHANGE_ZOOM:
308 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
309 case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
311 const vout_display_cfg_t *cfg =
312 va_arg (ap, const vout_display_cfg_t *);
314 /* we always use our current frame here */
315 vout_display_cfg_t cfg_tmp = *cfg;
316 [CATransaction lock];
317 CGRect bounds = [sys->cgLayer visibleRect];
318 [CATransaction unlock];
319 cfg_tmp.display.width = bounds.size.width;
320 cfg_tmp.display.height = bounds.size.height;
322 /* Reverse vertical alignment as the GL tex are Y inverted */
323 if (cfg_tmp.align.vertical == VLC_VIDEO_ALIGN_TOP)
324 cfg_tmp.align.vertical = VLC_VIDEO_ALIGN_BOTTOM;
325 else if (cfg_tmp.align.vertical == VLC_VIDEO_ALIGN_BOTTOM)
326 cfg_tmp.align.vertical = VLC_VIDEO_ALIGN_TOP;
328 vout_display_place_t place;
329 vout_display_PlacePicture(&place, &vd->source, &cfg_tmp);
330 if (unlikely(OpenglLock(sys->gl)))
331 // don't return an error or we need to handle VOUT_DISPLAY_RESET_PICTURES
334 vout_display_opengl_SetWindowAspectRatio(sys->vgl, (float)place.width / place.height);
335 OpenglUnlock(sys->gl);
342 case VOUT_DISPLAY_CHANGE_VIEWPOINT:
346 if (OpenglLock(sys->gl))
349 ret = vout_display_opengl_SetViewpoint(sys->vgl,
350 &va_arg (ap, const vout_display_cfg_t* )->viewpoint);
351 OpenglUnlock(sys->gl);
355 case VOUT_DISPLAY_RESET_PICTURES:
356 vlc_assert_unreachable ();
358 msg_Err (vd, "Unhandled request %d", query);
366 #pragma mark OpenGL callbacks
368 static int OpenglLock (vlc_gl_t *gl)
370 struct gl_sys *sys = gl->sys;
371 assert(sys->locked_ctx == NULL);
373 CGLContextObj ctx = [sys->cgLayer glContext];
378 CGLError err = CGLLockContext(ctx);
379 if (kCGLNoError == err) {
380 sys->locked_ctx = ctx;
381 CGLSetCurrentContext(ctx);
387 static void OpenglUnlock (vlc_gl_t *gl)
389 struct gl_sys *sys = gl->sys;
390 CGLUnlockContext(sys->locked_ctx);
391 sys->locked_ctx = NULL;
394 static void OpenglSwap (vlc_gl_t *gl)
399 static void *OurGetProcAddress (vlc_gl_t *gl, const char *name)
403 return dlsym(RTLD_DEFAULT, name);
407 #pragma mark CA layer
409 /*****************************************************************************
410 * @implementation VLCCAOpenGLLayer
411 *****************************************************************************/
412 @implementation VLCCAOpenGLLayer
418 [CATransaction lock];
419 self.needsDisplayOnBoundsChange = YES;
420 self.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
421 self.asynchronous = NO;
422 [CATransaction unlock];
428 - (void)setVoutDisplay:(vout_display_t *)aVd
433 - (void)resizeWithOldSuperlayerSize:(CGSize)size
435 [super resizeWithOldSuperlayerSize: size];
437 CGSize boundsSize = self.visibleRect.size;
441 vout_display_sys_t *sys = _voutDisplay->sys;
442 vout_window_ReportSize(sys->embed, boundsSize.width, boundsSize.height);
446 - (BOOL)canDrawInCGLContext:(CGLContextObj)glContext pixelFormat:(CGLPixelFormatObj)pixelFormat forLayerTime:(CFTimeInterval)timeInterval displayTime:(const CVTimeStamp *)timeStamp
448 /* Only draw the frame if we have a frame that was previously rendered */
452 return _voutDisplay->sys->b_frame_available;
455 - (void)drawInCGLContext:(CGLContextObj)glContext pixelFormat:(CGLPixelFormatObj)pixelFormat forLayerTime:(CFTimeInterval)timeInterval displayTime:(const CVTimeStamp *)timeStamp
459 vout_display_sys_t *sys = _voutDisplay->sys;
464 CGRect bounds = [self visibleRect];
466 // x / y are top left corner, but we need the lower left one
467 vout_display_opengl_Viewport(sys->vgl, sys->place.x,
468 bounds.size.height - (sys->place.y + sys->place.height),
469 sys->place.width, sys->place.height);
471 // flush is also done by this method, no need to call super
472 vout_display_opengl_Display (sys->vgl, &_voutDisplay->source);
473 sys->b_frame_available = NO;
476 -(CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask
478 // The default is fine for this demonstration.
479 return [super copyCGLPixelFormatForDisplayMask:mask];
482 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat
484 // Only one opengl context is allowed for the module lifetime
486 msg_Dbg(_voutDisplay, "Return existing context: %p", _glContext);
490 CGLContextObj context = [super copyCGLContextForPixelFormat:pixelFormat];
492 // Swap buffers only during the vertical retrace of the monitor.
493 //http://developer.apple.com/documentation/GraphicsImaging/
494 //Conceptual/OpenGL/chap5/chapter_5_section_44.html /
497 CGLSetParameter( CGLGetCurrentContext(), kCGLCPSwapInterval,
500 @synchronized (self) {
501 _glContext = context;
507 - (void)releaseCGLContext:(CGLContextObj)glContext
509 // do not release anything here, we do that when closing the module
512 - (void)mouseButtonDown:(int)buttonNumber
514 @synchronized (self) {
516 if (buttonNumber == 0)
517 vout_display_SendEventMousePressed (_voutDisplay, MOUSE_BUTTON_LEFT);
518 else if (buttonNumber == 1)
519 vout_display_SendEventMousePressed (_voutDisplay, MOUSE_BUTTON_RIGHT);
521 vout_display_SendEventMousePressed (_voutDisplay, MOUSE_BUTTON_CENTER);
526 - (void)mouseButtonUp:(int)buttonNumber
528 @synchronized (self) {
530 if (buttonNumber == 0)
531 vout_display_SendEventMouseReleased (_voutDisplay, MOUSE_BUTTON_LEFT);
532 else if (buttonNumber == 1)
533 vout_display_SendEventMouseReleased (_voutDisplay, MOUSE_BUTTON_RIGHT);
535 vout_display_SendEventMouseReleased (_voutDisplay, MOUSE_BUTTON_CENTER);
540 - (void)mouseMovedToX:(double)xValue Y:(double)yValue
542 @synchronized (self) {
544 vout_display_SendMouseMovedDisplayCoordinates (_voutDisplay,