1 /*****************************************************************************
2 * macosx.m: MacOS X OpenGL provider
3 *****************************************************************************
4 * Copyright (C) 2001-2013 VLC authors and VideoLAN
7 * Authors: Derk-Jan Hartman <hartman at videolan dot org>
8 * Eric Petit <titer@m0k.org>
9 * Benjamin Pracht <bigben at videolan dot org>
10 * Damien Fouilleul <damienf at videolan dot org>
11 * Pierre d'Herbemont <pdherbemont at videolan dot org>
12 * Felix Paul Kühne <fkuehne at videolan dot org>
13 * David Fuhrmann <david dot fuhrmann at googlemail dot com>
15 * Juho Vähä-Herttua <juhovh at iki dot fi>
16 * Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
18 * This program is free software; you can redistribute it and/or modify it
19 * under the terms of the GNU Lesser General Public License as published by
20 * the Free Software Foundation; either version 2.1 of the License, or
21 * (at your option) any later version.
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU Lesser General Public License for more details.
28 * You should have received a copy of the GNU Lesser General Public License
29 * along with this program; if not, write to the Free Software Foundation,
30 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
31 *****************************************************************************/
33 /*****************************************************************************
35 *****************************************************************************/
37 #import <Cocoa/Cocoa.h>
38 #import <OpenGL/OpenGL.h>
45 #include <vlc_common.h>
46 #include <vlc_plugin.h>
47 #include <vlc_vout_display.h>
48 #include <vlc_opengl.h>
49 #include <vlc_dialog.h>
50 #include "opengl/vout_helper.h"
52 #define OSX_EL_CAPITAN (NSAppKitVersionNumber >= 1404)
54 #if MAC_OS_X_VERSION_MIN_ALLOWED <= MAC_OS_X_VERSION_10_11
55 const CFStringRef kCGColorSpaceDCIP3 = CFSTR("kCGColorSpaceDCIP3");
56 const CFStringRef kCGColorSpaceITUR_709 = CFSTR("kCGColorSpaceITUR_709");
57 const CFStringRef kCGColorSpaceITUR_2020 = CFSTR("kCGColorSpaceITUR_2020");
61 * Forward declarations
63 static int Open (vlc_object_t *);
64 static void Close (vlc_object_t *);
66 static picture_pool_t *Pool (vout_display_t *vd, unsigned requested_count);
67 static void PictureRender (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture);
68 static void PictureDisplay (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture);
69 static int Control (vout_display_t *vd, int query, va_list ap);
71 static void *OurGetProcAddress(vlc_gl_t *, const char *);
73 static int OpenglLock (vlc_gl_t *gl);
74 static void OpenglUnlock (vlc_gl_t *gl);
75 static void OpenglSwap (vlc_gl_t *gl);
81 /* Will be loaded even without interface module. see voutgl.m */
82 set_shortname ("Mac OS X")
83 set_description (N_("Mac OS X OpenGL video output"))
84 set_category (CAT_VIDEO)
85 set_subcategory (SUBCAT_VIDEO_VOUT)
86 set_capability ("vout display", 300)
87 set_callbacks (Open, Close)
89 add_shortcut ("macosx", "vout_macosx")
93 * Obj-C protocol declaration that drawable-nsobject should follow
95 @protocol VLCOpenGLVideoViewEmbedding <NSObject>
96 - (void)addVoutSubview:(NSView *)view;
97 - (void)removeVoutSubview:(NSView *)view;
100 @interface VLCOpenGLVideoView : NSOpenGLView
103 BOOL _hasPendingReshape;
105 - (void)setVoutDisplay:(vout_display_t *)vd;
106 - (void)setVoutFlushing:(BOOL)flushing;
110 struct vout_display_sys_t
112 VLCOpenGLVideoView *glView;
113 id<VLCOpenGLVideoViewEmbedding> container;
115 CGColorSpaceRef cgColorSpace;
116 NSColorSpace *nsColorSpace;
118 vout_window_t *embed;
120 vout_display_opengl_t *vgl;
122 picture_pool_t *pool;
124 bool has_first_frame;
126 vout_display_place_t place;
130 static void *OurGetProcAddress(vlc_gl_t *gl, const char *name)
134 return dlsym(RTLD_DEFAULT, name);
137 static int Open (vlc_object_t *this)
139 vout_display_t *vd = (vout_display_t *)this;
140 vout_display_sys_t *sys = calloc (1, sizeof(*sys));
146 if (!CGDisplayUsesOpenGLAcceleration (kCGDirectMainDisplay))
147 msg_Err (this, "no OpenGL hardware acceleration found. this can lead to slow output and unexpected results");
155 /* Get the drawable object */
156 id container = var_CreateGetAddress (vd, "drawable-nsobject");
158 vout_display_DeleteWindow (vd, NULL);
160 sys->embed = vout_display_NewWindow (vd, VOUT_WINDOW_TYPE_NSOBJECT);
162 container = sys->embed->handle.nsobject;
165 msg_Err(vd, "No drawable-nsobject nor vout_window_t found, passing over.");
170 /* This will be released in Close(), on
171 * main thread, after we are done using it. */
172 sys->container = [container retain];
174 /* support for BT.709 and BT.2020 color spaces was introduced with OS X 10.11
175 * on older OS versions, we can't show correct colors, so we fallback on linear RGB */
176 if (OSX_EL_CAPITAN) {
177 switch (vd->fmt.primaries) {
178 case COLOR_PRIMARIES_BT601_525:
179 case COLOR_PRIMARIES_BT601_625:
181 msg_Dbg(vd, "Using BT.601 color space");
182 sys->cgColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
185 case COLOR_PRIMARIES_BT709:
187 msg_Dbg(vd, "Using BT.709 color space");
188 sys->cgColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_709);
191 case COLOR_PRIMARIES_BT2020:
193 msg_Dbg(vd, "Using BT.2020 color space");
194 sys->cgColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_2020);
197 case COLOR_PRIMARIES_DCI_P3:
199 msg_Dbg(vd, "Using DCI P3 color space");
200 sys->cgColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceDCIP3);
205 msg_Dbg(vd, "Guessing color space based on video dimensions (%ix%i)", vd->fmt.i_visible_width, vd->fmt.i_visible_height);
206 if (vd->fmt.i_visible_height >= 2000 || vd->fmt.i_visible_width >= 3800) {
207 msg_Dbg(vd, "Using BT.2020 color space");
208 sys->cgColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_2020);
209 } else if (vd->fmt.i_height > 576) {
210 msg_Dbg(vd, "Using BT.709 color space");
211 sys->cgColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_709);
213 msg_Dbg(vd, "SD content, using linear RGB color space");
214 sys->cgColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
220 msg_Dbg(vd, "OS does not support BT.709 or BT.2020 color spaces, output may vary");
221 sys->cgColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
223 sys->nsColorSpace = [[NSColorSpace alloc] initWithCGColorSpace:sys->cgColorSpace];
225 /* Get our main view*/
226 [VLCOpenGLVideoView performSelectorOnMainThread:@selector(getNewView:)
227 withObject:[NSValue valueWithPointer:&sys->glView]
230 msg_Err(vd, "Initialization of open gl view failed");
234 [sys->glView setVoutDisplay:vd];
236 /* We don't wait, that means that we'll have to be careful about releasing
238 * That's why we'll release on main thread in Close(). */
239 if ([(id)container respondsToSelector:@selector(addVoutSubview:)])
240 [(id)container performSelectorOnMainThread:@selector(addVoutSubview:)
241 withObject:sys->glView
243 else if ([container isKindOfClass:[NSView class]]) {
244 NSView *parentView = container;
245 [parentView performSelectorOnMainThread:@selector(addSubview:)
246 withObject:sys->glView
248 [sys->glView performSelectorOnMainThread:@selector(setFrameToBoundsOfView:)
249 withObject:[NSValue valueWithPointer:parentView]
252 msg_Err(vd, "Invalid drawable-nsobject object. drawable-nsobject must either be an NSView or comply to the @protocol VLCOpenGLVideoViewEmbedding.");
256 /* Initialize common OpenGL video display */
257 sys->gl = vlc_object_create(this, sizeof(*sys->gl));
259 if( unlikely( !sys->gl ) )
262 sys->gl->makeCurrent = OpenglLock;
263 sys->gl->releaseCurrent = OpenglUnlock;
264 sys->gl->swap = OpenglSwap;
265 sys->gl->getProcAddress = OurGetProcAddress;
268 const vlc_fourcc_t *subpicture_chromas;
270 sys->vgl = vout_display_opengl_New (&vd->fmt, &subpicture_chromas, sys->gl,
271 &vd->cfg->viewpoint);
273 msg_Err(vd, "Error while initializing opengl display.");
278 vout_display_info_t info = vd->info;
279 info.has_pictures_invalid = false;
280 info.subpicture_chromas = subpicture_chromas;
281 info.has_hide_mouse = true;
283 /* Setup vout_display_t once everything is fine */
287 vd->prepare = PictureRender;
288 vd->display = PictureDisplay;
289 vd->control = Control;
292 vout_display_SendEventDisplaySize (vd, vd->fmt.i_visible_width, vd->fmt.i_visible_height);
302 void Close (vlc_object_t *this)
304 vout_display_t *vd = (vout_display_t *)this;
305 vout_display_sys_t *sys = vd->sys;
308 [sys->glView setVoutDisplay:nil];
310 var_Destroy (vd, "drawable-nsobject");
311 if ([(id)sys->container respondsToSelector:@selector(removeVoutSubview:)])
312 /* This will retain sys->glView */
313 [(id)sys->container performSelectorOnMainThread:@selector(removeVoutSubview:)
314 withObject:sys->glView
317 /* release on main thread as explained in Open() */
318 [(id)sys->container performSelectorOnMainThread:@selector(release)
321 [sys->glView performSelectorOnMainThread:@selector(removeFromSuperview)
325 if (sys->vgl != NULL)
326 vout_display_opengl_Delete (sys->vgl);
329 vlc_object_release(sys->gl);
331 [sys->glView release];
333 if (sys->cgColorSpace != nil)
334 CGColorSpaceRelease(sys->cgColorSpace);
336 if (sys->nsColorSpace != nil)
337 [sys->nsColorSpace release];
340 vout_display_DeleteWindow (vd, sys->embed);
345 /*****************************************************************************
346 * vout display callbacks
347 *****************************************************************************/
349 static picture_pool_t *Pool (vout_display_t *vd, unsigned requested_count)
351 vout_display_sys_t *sys = vd->sys;
354 sys->pool = vout_display_opengl_GetPool (sys->vgl, requested_count);
359 static void PictureRender (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
362 vout_display_sys_t *sys = vd->sys;
364 vout_display_opengl_Prepare (sys->vgl, pic, subpicture);
367 static void PictureDisplay (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
369 vout_display_sys_t *sys = vd->sys;
370 [sys->glView setVoutFlushing:YES];
371 vout_display_opengl_Display (sys->vgl, &vd->source);
372 [sys->glView setVoutFlushing:NO];
373 picture_Release (pic);
374 sys->has_first_frame = true;
377 subpicture_Delete(subpicture);
380 static int Control (vout_display_t *vd, int query, va_list ap)
382 vout_display_sys_t *sys = vd->sys;
393 case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
394 case VOUT_DISPLAY_CHANGE_ZOOM:
395 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
396 case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
397 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
400 id o_window = [sys->glView window];
402 return VLC_SUCCESS; // this is okay, since the event will occur again when we have a window
405 NSSize windowMinSize = [o_window minSize];
407 const vout_display_cfg_t *cfg;
408 const video_format_t *source;
410 if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT || query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
411 source = (const video_format_t *)va_arg (ap, const video_format_t *);
414 source = &vd->source;
415 cfg = (const vout_display_cfg_t*)va_arg (ap, const vout_display_cfg_t *);
418 /* we always use our current frame here, because we have some size constraints
419 in the ui vout provider */
420 vout_display_cfg_t cfg_tmp = *cfg;
421 /* on HiDPI displays, the point bounds don't equal the actual pixel based bounds */
422 NSRect bounds = [sys->glView convertRectToBacking:[sys->glView bounds]];
423 cfg_tmp.display.width = bounds.size.width;
424 cfg_tmp.display.height = bounds.size.height;
426 vout_display_place_t place;
427 vout_display_PlacePicture (&place, source, &cfg_tmp, false);
428 @synchronized (sys->glView) {
432 vout_display_opengl_SetWindowAspectRatio(sys->vgl, (float)place.width / place.height);
434 /* For resize, we call glViewport in reshape and not here.
435 This has the positive side effect that we avoid erratic sizing as we animate every resize. */
436 if (query != VOUT_DISPLAY_CHANGE_DISPLAY_SIZE)
437 // x / y are top left corner, but we need the lower left one
438 glViewport (place.x, cfg_tmp.display.height - (place.y + place.height), place.width, place.height);
443 case VOUT_DISPLAY_HIDE_MOUSE:
445 [NSCursor setHiddenUntilMouseMoves: YES];
449 case VOUT_DISPLAY_CHANGE_VIEWPOINT:
450 return vout_display_opengl_SetViewpoint (sys->vgl,
451 &va_arg (ap, const vout_display_cfg_t* )->viewpoint);
453 case VOUT_DISPLAY_RESET_PICTURES:
454 vlc_assert_unreachable ();
456 msg_Err (vd, "Unknown request in Mac OS X vout display");
462 /*****************************************************************************
463 * vout opengl callbacks
464 *****************************************************************************/
465 static int OpenglLock (vlc_gl_t *gl)
467 vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
468 if (!sys->glView || ![sys->glView respondsToSelector:@selector(openGLContext)])
471 NSOpenGLContext *context = [sys->glView openGLContext];
472 CGLError err = CGLLockContext ([context CGLContextObj]);
473 if (kCGLNoError == err) {
474 [context makeCurrentContext];
480 static void OpenglUnlock (vlc_gl_t *gl)
482 vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
483 CGLUnlockContext ([[sys->glView openGLContext] CGLContextObj]);
486 static void OpenglSwap (vlc_gl_t *gl)
488 vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
489 [[sys->glView openGLContext] flushBuffer];
492 /*****************************************************************************
494 *****************************************************************************/
495 @implementation VLCOpenGLVideoView
497 #define VLCAssertMainThread() assert([[NSThread currentThread] isMainThread])
500 + (void)getNewView:(NSValue *)value
502 id *ret = [value pointerValue];
503 *ret = [[self alloc] init];
507 * Gets called by the Open() method.
511 VLCAssertMainThread();
513 /* Warning - this may be called on non main thread */
515 NSOpenGLPixelFormatAttribute attribs[] =
517 NSOpenGLPFADoubleBuffer,
518 NSOpenGLPFAAccelerated,
519 NSOpenGLPFANoRecovery,
520 NSOpenGLPFAColorSize, 24,
521 NSOpenGLPFAAlphaSize, 8,
522 NSOpenGLPFADepthSize, 24,
524 NSOpenGLPFAAllowOfflineRenderers,
528 NSOpenGLPixelFormat *fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs];
533 self = [super initWithFrame:NSMakeRect(0,0,10,10) pixelFormat:fmt];
539 /* enable HiDPI support */
540 [self setWantsBestResolutionOpenGLSurface:YES];
542 /* request our screen's HDR mode (introduced in OS X 10.11) */
543 #pragma clang diagnostic push
544 #pragma clang diagnostic ignored "-Wpartial-availability"
545 if ([self respondsToSelector:@selector(setWantsExtendedDynamicRangeOpenGLSurface:)]) {
546 [self setWantsExtendedDynamicRangeOpenGLSurface:YES];
548 #pragma clang diagnostic pop
550 /* Swap buffers only during the vertical retrace of the monitor.
551 http://developer.apple.com/documentation/GraphicsImaging/
552 Conceptual/OpenGL/chap5/chapter_5_section_44.html */
553 GLint params[] = { 1 };
554 CGLSetParameter ([[self openGLContext] CGLContextObj], kCGLCPSwapInterval, params);
556 [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationDidChangeScreenParametersNotification
557 object:[NSApplication sharedApplication]
559 usingBlock:^(NSNotification *notification) {
560 [self performSelectorOnMainThread:@selector(reshape)
565 [self setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
571 [[NSNotificationCenter defaultCenter] removeObserver:self];
576 * Gets called by the Open() method.
578 - (void)setFrameToBoundsOfView:(NSValue *)value
580 NSView *parentView = [value pointerValue];
581 [self setFrame:[parentView bounds]];
585 * Gets called by the Close and Open methods.
588 - (void)setVoutDisplay:(vout_display_t *)aVd
590 @synchronized(self) {
596 * Gets called when the vout will aquire the lock and flush.
599 - (void)setVoutFlushing:(BOOL)flushing
603 @synchronized(self) {
604 _hasPendingReshape = NO;
609 * Can -drawRect skip rendering?.
611 - (BOOL)canSkipRendering
613 VLCAssertMainThread();
615 @synchronized(self) {
616 BOOL hasFirstFrame = vd && vd->sys->has_first_frame;
617 return !_hasPendingReshape && hasFirstFrame;
623 * Local method that locks the gl context.
627 VLCAssertMainThread();
628 NSOpenGLContext *context = [self openGLContext];
629 CGLError err = CGLLockContext ([context CGLContextObj]);
630 if (err == kCGLNoError)
631 [context makeCurrentContext];
632 return err == kCGLNoError;
636 * Local method that unlocks the gl context.
640 VLCAssertMainThread();
641 CGLUnlockContext ([[self openGLContext] CGLContextObj]);
645 * Local method that force a rendering of a frame.
646 * This will get called if Cocoa forces us to redraw (via -drawRect).
650 VLCAssertMainThread();
652 // We may have taken some times to take the opengl Lock.
653 // Check here to see if we can just skip the frame as well.
654 if ([self canSkipRendering])
658 @synchronized(self) { // vd can be accessed from multiple threads
659 hasFirstFrame = vd && vd->sys->has_first_frame;
663 // This will lock gl.
664 vout_display_opengl_Display (vd->sys->vgl, &vd->source);
666 glClear (GL_COLOR_BUFFER_BIT);
670 * Method called by Cocoa when the view is resized.
674 VLCAssertMainThread();
676 /* on HiDPI displays, the point bounds don't equal the actual pixel based bounds */
677 NSRect bounds = [self convertRectToBacking:[self bounds]];
678 vout_display_place_t place;
680 @synchronized(self) {
682 vout_display_cfg_t cfg_tmp = *(vd->cfg);
683 cfg_tmp.display.width = bounds.size.width;
684 cfg_tmp.display.height = bounds.size.height;
686 vout_display_PlacePicture (&place, &vd->source, &cfg_tmp, false);
687 vd->sys->place = place;
688 vout_display_SendEventDisplaySize (vd, bounds.size.width, bounds.size.height);
693 // x / y are top left corner, but we need the lower left one
694 glViewport (place.x, bounds.size.height - (place.y + place.height), place.width, place.height);
696 @synchronized(self) {
697 // This may be cleared before -drawRect is being called,
698 // in this case we'll skip the rendering.
699 // This will save us for rendering two frames (or more) for nothing
700 // (one by the vout, one (or more) by drawRect)
701 _hasPendingReshape = YES;
711 * Method called by Cocoa when the view is resized or the location has changed.
712 * We just need to make sure we are locking here.
716 VLCAssertMainThread();
717 BOOL success = [self lockgl];
727 * Method called by Cocoa to force redraw.
729 - (void)drawRect:(NSRect) rect
731 VLCAssertMainThread();
733 if ([self canSkipRendering])
736 BOOL success = [self lockgl];
747 NSWindow *window = [self window];
749 // Remove flashes with splitter view.
750 if ([window respondsToSelector:@selector(disableScreenUpdatesUntilFlush)])
751 [window disableScreenUpdatesUntilFlush];
762 #pragma mark Mouse handling
764 - (void)mouseDown:(NSEvent *)o_event
766 @synchronized (self) {
768 if ([o_event type] == NSLeftMouseDown && !([o_event modifierFlags] & NSControlKeyMask)) {
769 if ([o_event clickCount] <= 1)
770 vout_display_SendEventMousePressed (vd, MOUSE_BUTTON_LEFT);
775 [super mouseDown:o_event];
778 - (void)otherMouseDown:(NSEvent *)o_event
780 @synchronized (self) {
782 vout_display_SendEventMousePressed (vd, MOUSE_BUTTON_CENTER);
785 [super otherMouseDown: o_event];
788 - (void)mouseUp:(NSEvent *)o_event
790 @synchronized (self) {
792 if ([o_event type] == NSLeftMouseUp)
793 vout_display_SendEventMouseReleased (vd, MOUSE_BUTTON_LEFT);
797 [super mouseUp: o_event];
800 - (void)otherMouseUp:(NSEvent *)o_event
802 @synchronized (self) {
804 vout_display_SendEventMouseReleased (vd, MOUSE_BUTTON_CENTER);
807 [super otherMouseUp: o_event];
810 - (void)mouseMoved:(NSEvent *)o_event
812 /* on HiDPI displays, the point bounds don't equal the actual pixel based bounds */
813 NSPoint ml = [self convertPoint: [o_event locationInWindow] fromView: nil];
814 NSRect videoRect = [self bounds];
815 BOOL b_inside = [self mouse: ml inRect: videoRect];
817 ml = [self convertPointToBacking: ml];
818 videoRect = [self convertRectToBacking: videoRect];
821 @synchronized (self) {
823 vout_display_SendMouseMovedDisplayCoordinates(vd, ORIENT_NORMAL,
824 (int)ml.x, videoRect.size.height - (int)ml.y,
830 [super mouseMoved: o_event];
833 - (void)mouseDragged:(NSEvent *)o_event
835 [self mouseMoved: o_event];
836 [super mouseDragged: o_event];
839 - (void)otherMouseDragged:(NSEvent *)o_event
841 [self mouseMoved: o_event];
842 [super otherMouseDragged: o_event];
845 - (void)rightMouseDragged:(NSEvent *)o_event
847 [self mouseMoved: o_event];
848 [super rightMouseDragged: o_event];
851 - (BOOL)acceptsFirstResponder
856 - (BOOL)mouseDownCanMoveWindow
861 - (void)viewWillMoveToWindow:(nullable NSWindow *)newWindow
863 [super viewWillMoveToWindow:newWindow];
865 if (newWindow == nil)
868 @synchronized (self) {
870 if (vd) [newWindow setColorSpace:vd->sys->nsColorSpace];
872 @catch (NSException *exception) {
873 msg_Warn(vd, "Setting the window color space failed due to an Obj-C exception (%s, %s", [exception.name UTF8String], [exception.reason UTF8String]);