glwin32: don't activate on XP
[vlc.git] / modules / video_output / caopengllayer.m
blob1b9c2724b9cec66187b31c2eb25a5ee73ae2c488
1 /*****************************************************************************
2  * caopengllayer.m: CAOpenGLLayer (Mac OS X) video output
3  *****************************************************************************
4  * Copyright (C) 2014-2017 VLC authors and VideoLAN
5  * $Id$
6  *
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>
10  *
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.
15  *
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.
20  *
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 /*****************************************************************************
27  * Preamble
28  *****************************************************************************/
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
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 /*****************************************************************************
49  * Vout interface
50  *****************************************************************************/
51 static int  Open   (vlc_object_t *);
52 static void Close  (vlc_object_t *);
54 vlc_module_begin()
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)
60 vlc_module_end()
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                              vlc_tick_t date);
65 static void PictureDisplay  (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture);
66 static int Control          (vout_display_t *vd, int query, va_list ap);
68 static void *OurGetProcAddress (vlc_gl_t *gl, const char *name);
69 static int OpenglLock         (vlc_gl_t *gl);
70 static void OpenglUnlock       (vlc_gl_t *gl);
71 static void OpenglSwap         (vlc_gl_t *gl);
73 @protocol VLCCoreAnimationVideoLayerEmbedding <NSObject>
74 - (void)addVoutLayer:(CALayer *)aLayer;
75 - (void)removeVoutLayer:(CALayer *)aLayer;
76 - (CGSize)currentOutputSize;
77 @end
79 @interface VLCCAOpenGLLayer : CAOpenGLLayer
81 @property (nonatomic, readwrite) vout_display_t* voutDisplay;
82 @property (nonatomic, readwrite) CGLContextObj glContext;
84 @end
87 struct vout_display_sys_t {
89     picture_pool_t *pool;
90     picture_resource_t resource;
92     CALayer <VLCCoreAnimationVideoLayerEmbedding> *container;
93     vout_window_t *embed;
94     VLCCAOpenGLLayer *cgLayer;
96     vlc_gl_t *gl;
97     vout_display_opengl_t *vgl;
99     vout_display_place_t place;
101     bool  b_frame_available;
104 struct gl_sys
106     CGLContextObj locked_ctx;
107     VLCCAOpenGLLayer *cgLayer;
110 /*****************************************************************************
111  * Open: This function allocates and initializes the OpenGL vout method.
112  *****************************************************************************/
113 static int Open (vlc_object_t *p_this)
115     vout_display_t *vd = (vout_display_t *)p_this;
116     vout_display_sys_t *sys;
118     /* Allocate structure */
119     vd->sys = sys = calloc(1, sizeof(vout_display_sys_t));
120     if (sys == NULL)
121         return VLC_EGENERIC;
123     @autoreleasepool {
124         id container = var_CreateGetAddress(vd, "drawable-nsobject");
125         if (!container) {
126             sys->embed = vout_display_NewWindow(vd, VOUT_WINDOW_TYPE_NSOBJECT);
127             if (sys->embed)
128                 container = sys->embed->handle.nsobject;
130             if (!container) {
131                 msg_Err(vd, "No drawable-nsobject found!");
132                 goto bailout;
133             }
134         }
136         /* store for later, released in Close() */
137         sys->container = [container retain];
139         [CATransaction begin];
140         sys->cgLayer = [[VLCCAOpenGLLayer alloc] init];
141         [sys->cgLayer setVoutDisplay:vd];
143         [sys->cgLayer performSelectorOnMainThread:@selector(display)
144                                        withObject:nil
145                                     waitUntilDone:YES];
147         if ([container respondsToSelector:@selector(addVoutLayer:)]) {
148             msg_Dbg(vd, "container implements implicit protocol");
149             [container addVoutLayer:sys->cgLayer];
150         } else if ([container respondsToSelector:@selector(addSublayer:)] ||
151                    [container isKindOfClass:[CALayer class]]) {
152             msg_Dbg(vd, "container doesn't implement implicit protocol, fallback mode used");
153             [container addSublayer:sys->cgLayer];
154         } else {
155             msg_Err(vd, "Provided NSObject container isn't compatible");
156             [sys->cgLayer release];
157             sys->cgLayer = nil;
158             [CATransaction commit];
159             goto bailout;
160         }
161         [CATransaction commit];
163         if (!sys->cgLayer)
164             goto bailout;
166         if (![sys->cgLayer glContext])
167             msg_Warn(vd, "we might not have an OpenGL context yet");
169         /* Initialize common OpenGL video display */
170         sys->gl = vlc_object_create(vd, sizeof(*sys->gl));
171         if (unlikely(!sys->gl))
172             goto bailout;
173         sys->gl->makeCurrent = OpenglLock;
174         sys->gl->releaseCurrent = OpenglUnlock;
175         sys->gl->swap = OpenglSwap;
176         sys->gl->getProcAddress = OurGetProcAddress;
178         struct gl_sys *glsys = sys->gl->sys = malloc(sizeof(*glsys));
179         if (!sys->gl->sys)
180             goto bailout;
181         glsys->locked_ctx = NULL;
182         glsys->cgLayer = sys->cgLayer;
184         const vlc_fourcc_t *subpicture_chromas;
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);
189         } else
190             sys->vgl = NULL;
191         if (!sys->vgl) {
192             msg_Err(vd, "Error while initializing opengl display.");
193             goto bailout;
194         }
196         /* setup vout display */
197         vout_display_info_t info = vd->info;
198         info.subpicture_chromas = subpicture_chromas;
199         vd->info = info;
201         vd->pool    = Pool;
202         vd->prepare = PictureRender;
203         vd->display = PictureDisplay;
204         vd->control = Control;
206         if (OSX_SIERRA_AND_HIGHER) {
207             /* request our screen's HDR mode (introduced in OS X 10.11, but correctly supported in 10.12 only) */
208             if ([sys->cgLayer respondsToSelector:@selector(setWantsExtendedDynamicRangeContent:)]) {
209                 [sys->cgLayer setWantsExtendedDynamicRangeContent:YES];
210             }
211         }
213         /* setup initial state */
214         CGSize outputSize;
215         if ([container respondsToSelector:@selector(currentOutputSize)])
216             outputSize = [container currentOutputSize];
217         else
218             outputSize = [sys->container visibleRect].size;
219         vout_display_SendEventDisplaySize(vd, (int)outputSize.width, (int)outputSize.height);
221         return VLC_SUCCESS;
223     bailout:
224         Close(p_this);
225         return VLC_EGENERIC;
226     }
229 static void Close (vlc_object_t *p_this)
231     vout_display_t *vd = (vout_display_t *)p_this;
232     vout_display_sys_t *sys = vd->sys;
234     if (sys->cgLayer) {
235         if ([sys->container respondsToSelector:@selector(removeVoutLayer:)])
236             [sys->container removeVoutLayer:sys->cgLayer];
237         else
238             [sys->cgLayer removeFromSuperlayer];
240         if ([sys->cgLayer glContext])
241             CGLReleaseContext([sys->cgLayer glContext]);
243         [sys->cgLayer release];
244     }
246     if (sys->container)
247         [sys->container release];
249     if (sys->vgl != NULL && !OpenglLock(sys->gl)) {
250         vout_display_opengl_Delete(sys->vgl);
251         OpenglUnlock(sys->gl);
252     }
254     if (sys->gl != NULL)
255     {
256         if (sys->gl->sys != NULL)
257         {
258             assert(((struct gl_sys *)sys->gl->sys)->locked_ctx == NULL);
259             free(sys->gl->sys);
260         }
261         vlc_object_release(sys->gl);
262     }
264     free(sys);
267 static picture_pool_t *Pool (vout_display_t *vd, unsigned count)
269     vout_display_sys_t *sys = vd->sys;
271     if (!sys->pool && !OpenglLock(sys->gl)) {
272         sys->pool = vout_display_opengl_GetPool(sys->vgl, count);
273         OpenglUnlock(sys->gl);
274         assert(sys->pool);
275     }
276     return sys->pool;
279 static void PictureRender (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture,
280                            vlc_tick_t date)
282     VLC_UNUSED(date);
283     vout_display_sys_t *sys = vd->sys;
285     if (pic == NULL) {
286         msg_Warn(vd, "invalid pic, skipping frame");
287         return;
288     }
290     @synchronized (sys->cgLayer) {
291         if (!OpenglLock(sys->gl)) {
292             vout_display_opengl_Prepare(sys->vgl, pic, subpicture);
293             OpenglUnlock(sys->gl);
294         }
295     }
298 static void PictureDisplay (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
300     vout_display_sys_t *sys = vd->sys;
302     @synchronized (sys->cgLayer) {
303         sys->b_frame_available = YES;
305         /* Calling display on the non-main thread is not officially supported, but
306          * its suggested at several places and works fine here. Flush is thread-safe
307          * and makes sure the picture is actually displayed. */
308         [sys->cgLayer display];
309         [CATransaction flush];
310     }
312     picture_Release(pic);
314     if (subpicture)
315         subpicture_Delete(subpicture);
318 static int Control (vout_display_t *vd, int query, va_list ap)
320     vout_display_sys_t *sys = vd->sys;
322     if (!vd->sys)
323         return VLC_EGENERIC;
325     switch (query)
326     {
327         case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
328         case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
329         case VOUT_DISPLAY_CHANGE_ZOOM:
330         case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
331         case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
332         {
333             const vout_display_cfg_t *cfg;
335             if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT || query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
336                 cfg = vd->cfg;
337             } else {
338                 cfg = (const vout_display_cfg_t*)va_arg (ap, const vout_display_cfg_t *);
339             }
341             /* we always use our current frame here */
342             vout_display_cfg_t cfg_tmp = *cfg;
343             [CATransaction lock];
344             CGRect bounds = [sys->cgLayer visibleRect];
345             [CATransaction unlock];
346             cfg_tmp.display.width = bounds.size.width;
347             cfg_tmp.display.height = bounds.size.height;
349             /* Reverse vertical alignment as the GL tex are Y inverted */
350             if (cfg_tmp.align.vertical == VOUT_DISPLAY_ALIGN_TOP)
351                 cfg_tmp.align.vertical = VOUT_DISPLAY_ALIGN_BOTTOM;
352             else if (cfg_tmp.align.vertical == VOUT_DISPLAY_ALIGN_BOTTOM)
353                 cfg_tmp.align.vertical = VOUT_DISPLAY_ALIGN_TOP;
355             vout_display_place_t place;
356             vout_display_PlacePicture (&place, &vd->source, &cfg_tmp, false);
357             if (OpenglLock(sys->gl))
358                 return VLC_EGENERIC;
360             vout_display_opengl_SetWindowAspectRatio(sys->vgl, (float)place.width / place.height);
361             OpenglUnlock(sys->gl);
363             sys->place = place;
365             return VLC_SUCCESS;
366         }
368         case VOUT_DISPLAY_CHANGE_VIEWPOINT:
369         {
370             int ret;
372             if (OpenglLock(sys->gl))
373                 return VLC_EGENERIC;
375             ret = vout_display_opengl_SetViewpoint(sys->vgl,
376                 &va_arg (ap, const vout_display_cfg_t* )->viewpoint);
377             OpenglUnlock(sys->gl);
378             return ret;
379         }
381         case VOUT_DISPLAY_RESET_PICTURES:
382             vlc_assert_unreachable ();
383         default:
384             msg_Err (vd, "Unhandled request %d", query);
385             return VLC_EGENERIC;
386     }
388     return VLC_SUCCESS;
391 #pragma mark -
392 #pragma mark OpenGL callbacks
394 static int OpenglLock (vlc_gl_t *gl)
396     struct gl_sys *sys = gl->sys;
397     assert(sys->locked_ctx == NULL);
399     CGLContextObj ctx = [sys->cgLayer glContext];
400     if(!ctx) {
401         return 1;
402     }
404     CGLError err = CGLLockContext(ctx);
405     if (kCGLNoError == err) {
406         sys->locked_ctx = ctx;
407         CGLSetCurrentContext(ctx);
408         return 0;
409     }
410     return 1;
413 static void OpenglUnlock (vlc_gl_t *gl)
415     struct gl_sys *sys = gl->sys;
416     CGLUnlockContext(sys->locked_ctx);
417     sys->locked_ctx = NULL;
420 static void OpenglSwap (vlc_gl_t *gl)
422     glFlush();
425 static void *OurGetProcAddress (vlc_gl_t *gl, const char *name)
427     VLC_UNUSED(gl);
429     return dlsym(RTLD_DEFAULT, name);
432 #pragma mark -
433 #pragma mark CA layer
435 /*****************************************************************************
436  * @implementation VLCCAOpenGLLayer
437  *****************************************************************************/
438 @implementation VLCCAOpenGLLayer
440 - (id)init {
442     self = [super init];
443     if (self) {
444         [CATransaction lock];
445         self.needsDisplayOnBoundsChange = YES;
446         self.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
447         self.asynchronous = NO;
448         [CATransaction unlock];
449     }
451     return self;
454 - (void)setVoutDisplay:(vout_display_t *)aVd
456     _voutDisplay = aVd;
459 - (void)resizeWithOldSuperlayerSize:(CGSize)size
461     [super resizeWithOldSuperlayerSize: size];
463     CGSize boundsSize = self.visibleRect.size;
465     if (_voutDisplay)
466         vout_display_SendEventDisplaySize(_voutDisplay, boundsSize.width, boundsSize.height);
469 - (BOOL)canDrawInCGLContext:(CGLContextObj)glContext pixelFormat:(CGLPixelFormatObj)pixelFormat forLayerTime:(CFTimeInterval)timeInterval displayTime:(const CVTimeStamp *)timeStamp
471     /* Only draw the frame if we have a frame that was previously rendered */
472     if (!_voutDisplay)
473         return false;
475     return _voutDisplay->sys->b_frame_available;
478 - (void)drawInCGLContext:(CGLContextObj)glContext pixelFormat:(CGLPixelFormatObj)pixelFormat forLayerTime:(CFTimeInterval)timeInterval displayTime:(const CVTimeStamp *)timeStamp
480     if (!_voutDisplay)
481         return;
482     vout_display_sys_t *sys = _voutDisplay->sys;
484     if (!sys->vgl)
485         return;
487     CGRect bounds = [self visibleRect];
489     // x / y are top left corner, but we need the lower left one
490     vout_display_opengl_Viewport(sys->vgl, sys->place.x,
491                                  bounds.size.height - (sys->place.y + sys->place.height),
492                                  sys->place.width, sys->place.height);
494     // flush is also done by this method, no need to call super
495     vout_display_opengl_Display (sys->vgl, &_voutDisplay->source);
496     sys->b_frame_available = NO;
499 -(CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask
501     // The default is fine for this demonstration.
502     return [super copyCGLPixelFormatForDisplayMask:mask];
505 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat
507     // Only one opengl context is allowed for the module lifetime
508     if(_glContext) {
509         msg_Dbg(_voutDisplay, "Return existing context: %p", _glContext);
510         return _glContext;
511     }
513     CGLContextObj context = [super copyCGLContextForPixelFormat:pixelFormat];
515     // Swap buffers only during the vertical retrace of the monitor.
516     //http://developer.apple.com/documentation/GraphicsImaging/
517     //Conceptual/OpenGL/chap5/chapter_5_section_44.html /
519     GLint params = 1;
520     CGLSetParameter( CGLGetCurrentContext(), kCGLCPSwapInterval,
521                      &params );
523     @synchronized (self) {
524         _glContext = context;
525     }
527     return context;
530 - (void)releaseCGLContext:(CGLContextObj)glContext
532     // do not release anything here, we do that when closing the module
535 - (void)mouseButtonDown:(int)buttonNumber
537     @synchronized (self) {
538         if (_voutDisplay) {
539             if (buttonNumber == 0)
540                 vout_display_SendEventMousePressed (_voutDisplay, MOUSE_BUTTON_LEFT);
541             else if (buttonNumber == 1)
542                 vout_display_SendEventMousePressed (_voutDisplay, MOUSE_BUTTON_RIGHT);
543             else
544                 vout_display_SendEventMousePressed (_voutDisplay, MOUSE_BUTTON_CENTER);
545         }
546     }
549 - (void)mouseButtonUp:(int)buttonNumber
551     @synchronized (self) {
552         if (_voutDisplay) {
553             if (buttonNumber == 0)
554                 vout_display_SendEventMouseReleased (_voutDisplay, MOUSE_BUTTON_LEFT);
555             else if (buttonNumber == 1)
556                 vout_display_SendEventMouseReleased (_voutDisplay, MOUSE_BUTTON_RIGHT);
557             else
558                 vout_display_SendEventMouseReleased (_voutDisplay, MOUSE_BUTTON_CENTER);
559         }
560     }
563 - (void)mouseMovedToX:(double)xValue Y:(double)yValue
565     @synchronized (self) {
566         if (_voutDisplay) {
567             vout_display_SendMouseMovedDisplayCoordinates (_voutDisplay,
568                                                            ORIENT_NORMAL,
569                                                            xValue,
570                                                            yValue,
571                                                            &_voutDisplay->sys->place);
572         }
573     }
576 @end