1 /*****************************************************************************
2 * caopengllayer.m: CAOpenGLLayer (Mac OS X) video output
3 *****************************************************************************
4 * Copyright (C) 2014-2016 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_EL_CAPITAN (NSAppKitVersionNumber >= 1404)
48 #if MAC_OS_X_VERSION_MIN_ALLOWED <= MAC_OS_X_VERSION_10_11
49 const CFStringRef kCGColorSpaceDCIP3 = CFSTR("kCGColorSpaceDCIP3");
50 const CFStringRef kCGColorSpaceITUR_709 = CFSTR("kCGColorSpaceITUR_709");
51 const CFStringRef kCGColorSpaceITUR_2020 = CFSTR("kCGColorSpaceITUR_2020");
54 /*****************************************************************************
56 *****************************************************************************/
57 static int Open (vlc_object_t *);
58 static void Close (vlc_object_t *);
61 set_description(N_("Core Animation OpenGL Layer (Mac OS X)"))
62 set_capability("vout display", 0)
63 set_category(CAT_VIDEO)
64 set_subcategory(SUBCAT_VIDEO_VOUT)
65 set_callbacks(Open, Close)
68 static picture_pool_t *Pool (vout_display_t *vd, unsigned requested_count);
69 static void PictureRender (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture);
70 static void PictureDisplay (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture);
71 static int Control (vout_display_t *vd, int query, va_list ap);
73 static void *OurGetProcAddress (vlc_gl_t *gl, const char *name);
74 static int OpenglLock (vlc_gl_t *gl);
75 static void OpenglUnlock (vlc_gl_t *gl);
76 static void OpenglSwap (vlc_gl_t *gl);
78 @protocol VLCCoreAnimationVideoLayerEmbedding <NSObject>
79 - (void)addVoutLayer:(CALayer *)aLayer;
80 - (void)removeVoutLayer:(CALayer *)aLayer;
81 - (CGSize)currentOutputSize;
84 @interface VLCCAOpenGLLayer : CAOpenGLLayer
86 @property (nonatomic, readwrite) vout_display_t* voutDisplay;
91 struct vout_display_sys_t {
94 picture_resource_t resource;
96 CALayer <VLCCoreAnimationVideoLayerEmbedding> *container;
98 VLCCAOpenGLLayer *cgLayer;
100 CGColorSpaceRef cgColorSpace;
102 CGLContextObj glContext;
105 vout_display_opengl_t *vgl;
107 vout_display_place_t place;
109 bool b_frame_available;
112 /*****************************************************************************
113 * Open: This function allocates and initializes the OpenGL vout method.
114 *****************************************************************************/
115 static int Open (vlc_object_t *p_this)
117 vout_display_t *vd = (vout_display_t *)p_this;
118 vout_display_sys_t *sys;
120 /* Allocate structure */
121 vd->sys = sys = calloc(1, sizeof(vout_display_sys_t));
126 id container = var_CreateGetAddress(vd, "drawable-nsobject");
128 vout_display_DeleteWindow(vd, NULL);
130 sys->embed = vout_display_NewWindow(vd, VOUT_WINDOW_TYPE_NSOBJECT);
132 container = sys->embed->handle.nsobject;
135 msg_Err(vd, "No drawable-nsobject found!");
140 /* store for later, released in Close() */
141 sys->container = [container retain];
143 [CATransaction begin];
144 sys->cgLayer = [[VLCCAOpenGLLayer alloc] init];
145 [sys->cgLayer setVoutDisplay:vd];
147 [sys->cgLayer performSelectorOnMainThread:@selector(display)
151 if ([container respondsToSelector:@selector(addVoutLayer:)]) {
152 msg_Dbg(vd, "container implements implicit protocol");
153 [container addVoutLayer:sys->cgLayer];
154 } else if ([container respondsToSelector:@selector(addSublayer:)] ||
155 [container isKindOfClass:[CALayer class]]) {
156 msg_Dbg(vd, "container doesn't implement implicit protocol, fallback mode used");
157 [container addSublayer:sys->cgLayer];
159 msg_Err(vd, "Provided NSObject container isn't compatible");
160 [sys->cgLayer release];
162 [CATransaction commit];
165 [CATransaction commit];
171 msg_Warn(vd, "we might not have an OpenGL context yet");
173 /* Initialize common OpenGL video display */
174 sys->gl = vlc_object_create(vd, sizeof(*sys->gl));
175 if (unlikely(!sys->gl))
177 sys->gl->makeCurrent = OpenglLock;
178 sys->gl->releaseCurrent = OpenglUnlock;
179 sys->gl->swap = OpenglSwap;
180 sys->gl->getProcAddress = OurGetProcAddress;
183 const vlc_fourcc_t *subpicture_chromas;
184 video_format_t fmt = vd->fmt;
185 if (!OpenglLock(sys->gl)) {
186 sys->vgl = vout_display_opengl_New(&vd->fmt, &subpicture_chromas,
187 sys->gl, &vd->cfg->viewpoint);
188 OpenglUnlock(sys->gl);
192 msg_Err(vd, "Error while initializing opengl display.");
196 /* setup vout display */
197 vout_display_info_t info = vd->info;
198 info.subpicture_chromas = subpicture_chromas;
199 info.has_hide_mouse = true;
203 vd->prepare = PictureRender;
204 vd->display = PictureDisplay;
205 vd->control = Control;
207 /* handle color space if supported by the OS */
208 if ([sys->cgLayer respondsToSelector:@selector(setColorspace:)]) {
210 /* support for BT.709 and BT.2020 color spaces was introduced with OS X 10.11
211 * on older OS versions, we can't show correct colors, so we fallback on linear RGB */
212 if (OSX_EL_CAPITAN) {
213 switch (fmt.primaries) {
214 case COLOR_PRIMARIES_BT601_525:
215 case COLOR_PRIMARIES_BT601_625:
217 msg_Dbg(vd, "Using BT.601 color space");
218 sys->cgColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
221 case COLOR_PRIMARIES_BT709:
223 msg_Dbg(vd, "Using BT.709 color space");
224 sys->cgColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_709);
227 case COLOR_PRIMARIES_BT2020:
229 msg_Dbg(vd, "Using BT.2020 color space");
230 sys->cgColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_2020);
233 case COLOR_PRIMARIES_DCI_P3:
235 msg_Dbg(vd, "Using DCI P3 color space");
236 sys->cgColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceDCIP3);
241 msg_Dbg(vd, "Guessing color space based on video dimensions (%ix%i)", fmt.i_visible_width, fmt.i_visible_height);
242 if (fmt.i_visible_height >= 2000 || fmt.i_visible_width >= 3800) {
243 msg_Dbg(vd, "Using BT.2020 color space");
244 sys->cgColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_2020);
245 } else if (fmt.i_height > 576) {
246 msg_Dbg(vd, "Using BT.709 color space");
247 sys->cgColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_709);
249 msg_Dbg(vd, "SD content, using linear RGB color space");
250 sys->cgColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
256 msg_Dbg(vd, "OS does not support BT.709 or BT.2020 color spaces, output may vary");
257 sys->cgColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGBLinear);
260 [sys->cgLayer setColorspace: sys->cgColorSpace];
262 msg_Dbg(vd, "OS does not support custom color spaces, output may be undefined");
265 /* request our screen's HDR mode (introduced in OS X 10.11) */
266 if ([sys->cgLayer respondsToSelector:@selector(setWantsExtendedDynamicRangeContent:)]) {
267 [sys->cgLayer setWantsExtendedDynamicRangeContent:YES];
270 /* setup initial state */
272 if ([container respondsToSelector:@selector(currentOutputSize)])
273 outputSize = [container currentOutputSize];
275 outputSize = [sys->container visibleRect].size;
276 vout_display_SendEventDisplaySize(vd, (int)outputSize.width, (int)outputSize.height);
286 static void Close (vlc_object_t *p_this)
288 vout_display_t *vd = (vout_display_t *)p_this;
289 vout_display_sys_t *sys = vd->sys;
292 if ([sys->container respondsToSelector:@selector(removeVoutLayer:)])
293 [sys->container removeVoutLayer:sys->cgLayer];
295 [sys->cgLayer removeFromSuperlayer];
296 [sys->cgLayer release];
300 [sys->container release];
303 vout_display_DeleteWindow(vd, sys->embed);
305 if (sys->vgl != NULL && !OpenglLock(sys->gl)) {
306 vout_display_opengl_Delete(sys->vgl);
307 OpenglUnlock(sys->gl);
311 vlc_object_release(sys->gl);
314 CGLReleaseContext(sys->glContext);
316 if (sys->cgColorSpace != nil)
317 CGColorSpaceRelease(sys->cgColorSpace);
322 static picture_pool_t *Pool (vout_display_t *vd, unsigned count)
324 vout_display_sys_t *sys = vd->sys;
326 if (!sys->pool && !OpenglLock(sys->gl)) {
327 sys->pool = vout_display_opengl_GetPool(sys->vgl, count);
328 OpenglUnlock(sys->gl);
334 static void PictureRender (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
336 vout_display_sys_t *sys = vd->sys;
339 msg_Warn(vd, "invalid pic, skipping frame");
343 @synchronized (sys->cgLayer) {
344 if (!OpenglLock(sys->gl)) {
345 vout_display_opengl_Prepare(sys->vgl, pic, subpicture);
346 OpenglUnlock(sys->gl);
351 static void PictureDisplay (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
353 vout_display_sys_t *sys = vd->sys;
355 @synchronized (sys->cgLayer) {
356 sys->b_frame_available = YES;
358 /* Calling display on the non-main thread is not officially supported, but
359 * its suggested at several places and works fine here. Flush is thread-safe
360 * and makes sure the picture is actually displayed. */
361 [sys->cgLayer display];
362 [CATransaction flush];
365 picture_Release(pic);
368 subpicture_Delete(subpicture);
371 static int Control (vout_display_t *vd, int query, va_list ap)
373 vout_display_sys_t *sys = vd->sys;
380 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
381 case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
382 case VOUT_DISPLAY_CHANGE_ZOOM:
383 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
384 case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
386 const vout_display_cfg_t *cfg;
387 const video_format_t *source;
389 if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT || query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
390 source = (const video_format_t *)va_arg (ap, const video_format_t *);
393 source = &vd->source;
394 cfg = (const vout_display_cfg_t*)va_arg (ap, const vout_display_cfg_t *);
397 /* we always use our current frame here */
398 vout_display_cfg_t cfg_tmp = *cfg;
399 [CATransaction lock];
400 CGRect bounds = [sys->cgLayer visibleRect];
401 [CATransaction unlock];
402 cfg_tmp.display.width = bounds.size.width;
403 cfg_tmp.display.height = bounds.size.height;
405 vout_display_place_t place;
406 vout_display_PlacePicture (&place, source, &cfg_tmp, false);
407 if (OpenglLock(sys->gl))
410 vout_display_opengl_SetWindowAspectRatio(sys->vgl, (float)place.width / place.height);
411 OpenglUnlock(sys->gl);
418 case VOUT_DISPLAY_HIDE_MOUSE:
420 [NSCursor setHiddenUntilMouseMoves: YES];
424 case VOUT_DISPLAY_CHANGE_VIEWPOINT:
428 if (OpenglLock(sys->gl))
431 ret = vout_display_opengl_SetViewpoint(sys->vgl,
432 &va_arg (ap, const vout_display_cfg_t* )->viewpoint);
433 OpenglUnlock(sys->gl);
437 case VOUT_DISPLAY_RESET_PICTURES:
438 vlc_assert_unreachable ();
440 msg_Err (vd, "Unhandled request %d", query);
441 case VOUT_DISPLAY_CHANGE_FULLSCREEN:
449 #pragma mark OpenGL callbacks
451 static int OpenglLock (vlc_gl_t *gl)
453 vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
455 if(!sys->glContext) {
459 CGLError err = CGLLockContext(sys->glContext);
460 if (kCGLNoError == err) {
461 CGLSetCurrentContext(sys->glContext);
467 static void OpenglUnlock (vlc_gl_t *gl)
469 vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
471 if (!sys->glContext) {
475 CGLUnlockContext(sys->glContext);
478 static void OpenglSwap (vlc_gl_t *gl)
483 static void *OurGetProcAddress (vlc_gl_t *gl, const char *name)
487 return dlsym(RTLD_DEFAULT, name);
491 #pragma mark CA layer
493 /*****************************************************************************
494 * @implementation VLCCAOpenGLLayer
495 *****************************************************************************/
496 @implementation VLCCAOpenGLLayer
502 [CATransaction lock];
503 self.needsDisplayOnBoundsChange = YES;
504 self.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
505 self.asynchronous = NO;
506 [CATransaction unlock];
512 - (void)setVoutDisplay:(vout_display_t *)aVd
517 - (void)resizeWithOldSuperlayerSize:(CGSize)size
519 [super resizeWithOldSuperlayerSize: size];
521 CGSize boundsSize = self.visibleRect.size;
524 vout_display_SendEventDisplaySize(_voutDisplay, boundsSize.width, boundsSize.height);
527 - (BOOL)canDrawInCGLContext:(CGLContextObj)glContext pixelFormat:(CGLPixelFormatObj)pixelFormat forLayerTime:(CFTimeInterval)timeInterval displayTime:(const CVTimeStamp *)timeStamp
529 /* Only draw the frame if we have a frame that was previously rendered */
533 return _voutDisplay->sys->b_frame_available;
536 - (void)drawInCGLContext:(CGLContextObj)glContext pixelFormat:(CGLPixelFormatObj)pixelFormat forLayerTime:(CFTimeInterval)timeInterval displayTime:(const CVTimeStamp *)timeStamp
540 vout_display_sys_t *sys = _voutDisplay->sys;
545 CGRect bounds = [self visibleRect];
547 // x / y are top left corner, but we need the lower left one
548 glViewport (sys->place.x, bounds.size.height - (sys->place.y + sys->place.height), sys->place.width, sys->place.height);
550 // flush is also done by this method, no need to call super
551 vout_display_opengl_Display (sys->vgl, &_voutDisplay->source);
552 sys->b_frame_available = NO;
555 -(CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask
557 // The default is fine for this demonstration.
558 return [super copyCGLPixelFormatForDisplayMask:mask];
561 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat
563 // Only one opengl context is allowed for the module lifetime
564 if(_voutDisplay->sys->glContext) {
565 msg_Dbg(_voutDisplay, "Return existing context: %p", _voutDisplay->sys->glContext);
566 return _voutDisplay->sys->glContext;
569 CGLContextObj context = [super copyCGLContextForPixelFormat:pixelFormat];
571 // Swap buffers only during the vertical retrace of the monitor.
572 //http://developer.apple.com/documentation/GraphicsImaging/
573 //Conceptual/OpenGL/chap5/chapter_5_section_44.html /
576 CGLSetParameter( CGLGetCurrentContext(), kCGLCPSwapInterval,
579 @synchronized (self) {
580 _voutDisplay->sys->glContext = context;
586 - (void)releaseCGLContext:(CGLContextObj)glContext
588 // do not release anything here, we do that when closing the module
591 - (void)mouseButtonDown:(int)buttonNumber
593 @synchronized (self) {
595 if (buttonNumber == 0)
596 vout_display_SendEventMousePressed (_voutDisplay, MOUSE_BUTTON_LEFT);
597 else if (buttonNumber == 1)
598 vout_display_SendEventMousePressed (_voutDisplay, MOUSE_BUTTON_RIGHT);
600 vout_display_SendEventMousePressed (_voutDisplay, MOUSE_BUTTON_CENTER);
605 - (void)mouseButtonUp:(int)buttonNumber
607 @synchronized (self) {
609 if (buttonNumber == 0)
610 vout_display_SendEventMouseReleased (_voutDisplay, MOUSE_BUTTON_LEFT);
611 else if (buttonNumber == 1)
612 vout_display_SendEventMouseReleased (_voutDisplay, MOUSE_BUTTON_RIGHT);
614 vout_display_SendEventMouseReleased (_voutDisplay, MOUSE_BUTTON_CENTER);
619 - (void)mouseMovedToX:(double)xValue Y:(double)yValue
621 @synchronized (self) {
623 vout_display_SendMouseMovedDisplayCoordinates (_voutDisplay,
627 &_voutDisplay->sys->place);