opengl: factorize program creation
[vlc.git] / modules / video_output / caopengllayer.m
blobafe6d6a3f7b0af1aee0a7e45fb633d0a0ac20d61
1 /*****************************************************************************
2  * caopengllayer.m: CAOpenGLLayer (Mac OS X) video output
3  *****************************************************************************
4  * Copyright (C) 2014-2017 VLC authors and VideoLAN
5  *
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>
9  *
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.
14  *
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.
19  *
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 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
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 /*****************************************************************************
48  * Vout interface
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);
54 vlc_module_begin()
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)
59 vlc_module_end()
61 static void PictureRender   (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture,
62                              vlc_tick_t date);
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;
75 @end
77 @interface VLCCAOpenGLLayer : CAOpenGLLayer
79 @property (nonatomic, readwrite) vout_display_t* voutDisplay;
80 @property (nonatomic, readwrite) CGLContextObj glContext;
82 @end
85 struct vout_display_sys_t {
87     CALayer <VLCCoreAnimationVideoLayerEmbedding> *container;
88     vout_window_t *embed;
89     VLCCAOpenGLLayer *cgLayer;
91     vlc_gl_t *gl;
92     vout_display_opengl_t *vgl;
94     vout_display_place_t place;
96     bool  b_frame_available;
99 struct gl_sys
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)
114         return VLC_EGENERIC;
116     /* Allocate structure */
117     vd->sys = sys = calloc(1, sizeof(vout_display_sys_t));
118     if (sys == NULL)
119         return VLC_EGENERIC;
121     @autoreleasepool {
122         id container = var_CreateGetAddress(vd, "drawable-nsobject");
123         if (!container) {
124             sys->embed = cfg->window;
125             container = sys->embed->handle.nsobject;
127             if (!container) {
128                 msg_Err(vd, "No drawable-nsobject found!");
129                 goto bailout;
130             }
131         }
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)
141                                        withObject:nil
142                                     waitUntilDone:YES];
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];
151         } else {
152             msg_Err(vd, "Provided NSObject container isn't compatible");
153             [sys->cgLayer release];
154             sys->cgLayer = nil;
155             [CATransaction commit];
156             goto bailout;
157         }
158         [CATransaction commit];
160         if (!sys->cgLayer)
161             goto bailout;
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))
169             goto bailout;
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));
176         if (!sys->gl->sys)
177             goto bailout;
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);
186         } else
187             sys->vgl = NULL;
188         if (!sys->vgl) {
189             msg_Err(vd, "Error while initializing opengl display.");
190             goto bailout;
191         }
193         /* setup vout display */
194         vd->info.subpicture_chromas = subpicture_chromas;
196         vd->prepare = PictureRender;
197         vd->display = PictureDisplay;
198         vd->control = Control;
199         vd->close   = Close;
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];
205             }
206         }
208         /* setup initial state */
209         CGSize outputSize;
210         if ([container respondsToSelector:@selector(currentOutputSize)])
211             outputSize = [container currentOutputSize];
212         else
213             outputSize = [sys->container visibleRect].size;
214         vout_window_ReportSize(sys->embed, (int)outputSize.width, (int)outputSize.height);
216         return VLC_SUCCESS;
218     bailout:
219         Close(vd);
220         return VLC_EGENERIC;
221     }
224 static void Close(vout_display_t *vd)
226     vout_display_sys_t *sys = vd->sys;
228     if (sys->cgLayer) {
229         if ([sys->container respondsToSelector:@selector(removeVoutLayer:)])
230             [sys->container removeVoutLayer:sys->cgLayer];
231         else
232             [sys->cgLayer removeFromSuperlayer];
234         if ([sys->cgLayer glContext])
235             CGLReleaseContext([sys->cgLayer glContext]);
237         [sys->cgLayer release];
238     }
240     if (sys->container)
241         [sys->container release];
243     if (sys->vgl != NULL && !OpenglLock(sys->gl)) {
244         vout_display_opengl_Delete(sys->vgl);
245         OpenglUnlock(sys->gl);
246     }
248     if (sys->gl != NULL)
249     {
250         if (sys->gl->sys != NULL)
251         {
252             assert(((struct gl_sys *)sys->gl->sys)->locked_ctx == NULL);
253             free(sys->gl->sys);
254         }
255         vlc_object_delete(sys->gl);
256     }
258     free(sys);
261 static void PictureRender (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture,
262                            vlc_tick_t date)
264     VLC_UNUSED(date);
265     vout_display_sys_t *sys = vd->sys;
267     if (pic == NULL) {
268         msg_Warn(vd, "invalid pic, skipping frame");
269         return;
270     }
272     @synchronized (sys->cgLayer) {
273         if (!OpenglLock(sys->gl)) {
274             vout_display_opengl_Prepare(sys->vgl, pic, subpicture);
275             OpenglUnlock(sys->gl);
276         }
277     }
280 static void PictureDisplay (vout_display_t *vd, picture_t *pic)
282     vout_display_sys_t *sys = vd->sys;
283     VLC_UNUSED(pic);
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];
293     }
296 static int Control (vout_display_t *vd, int query, va_list ap)
298     vout_display_sys_t *sys = vd->sys;
300     if (!vd->sys)
301         return VLC_EGENERIC;
303     switch (query)
304     {
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:
310         {
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
332                 return VLC_SUCCESS;
334             vout_display_opengl_SetWindowAspectRatio(sys->vgl, (float)place.width / place.height);
335             OpenglUnlock(sys->gl);
337             sys->place = place;
339             return VLC_SUCCESS;
340         }
342         case VOUT_DISPLAY_CHANGE_VIEWPOINT:
343         {
344             int ret;
346             if (OpenglLock(sys->gl))
347                 return VLC_EGENERIC;
349             ret = vout_display_opengl_SetViewpoint(sys->vgl,
350                 &va_arg (ap, const vout_display_cfg_t* )->viewpoint);
351             OpenglUnlock(sys->gl);
352             return ret;
353         }
355         case VOUT_DISPLAY_RESET_PICTURES:
356             vlc_assert_unreachable ();
357         default:
358             msg_Err (vd, "Unhandled request %d", query);
359             return VLC_EGENERIC;
360     }
362     return VLC_SUCCESS;
365 #pragma mark -
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];
374     if(!ctx) {
375         return 1;
376     }
378     CGLError err = CGLLockContext(ctx);
379     if (kCGLNoError == err) {
380         sys->locked_ctx = ctx;
381         CGLSetCurrentContext(ctx);
382         return 0;
383     }
384     return 1;
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)
396     glFlush();
399 static void *OurGetProcAddress (vlc_gl_t *gl, const char *name)
401     VLC_UNUSED(gl);
403     return dlsym(RTLD_DEFAULT, name);
406 #pragma mark -
407 #pragma mark CA layer
409 /*****************************************************************************
410  * @implementation VLCCAOpenGLLayer
411  *****************************************************************************/
412 @implementation VLCCAOpenGLLayer
414 - (id)init {
416     self = [super init];
417     if (self) {
418         [CATransaction lock];
419         self.needsDisplayOnBoundsChange = YES;
420         self.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
421         self.asynchronous = NO;
422         [CATransaction unlock];
423     }
425     return self;
428 - (void)setVoutDisplay:(vout_display_t *)aVd
430     _voutDisplay = aVd;
433 - (void)resizeWithOldSuperlayerSize:(CGSize)size
435     [super resizeWithOldSuperlayerSize: size];
437     CGSize boundsSize = self.visibleRect.size;
439     if (_voutDisplay)
440     {
441         vout_display_sys_t *sys = _voutDisplay->sys;
442         vout_window_ReportSize(sys->embed, boundsSize.width, boundsSize.height);
443     }
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 */
449     if (!_voutDisplay)
450         return false;
452     return _voutDisplay->sys->b_frame_available;
455 - (void)drawInCGLContext:(CGLContextObj)glContext pixelFormat:(CGLPixelFormatObj)pixelFormat forLayerTime:(CFTimeInterval)timeInterval displayTime:(const CVTimeStamp *)timeStamp
457     if (!_voutDisplay)
458         return;
459     vout_display_sys_t *sys = _voutDisplay->sys;
461     if (!sys->vgl)
462         return;
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
485     if(_glContext) {
486         msg_Dbg(_voutDisplay, "Return existing context: %p", _glContext);
487         return _glContext;
488     }
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 /
496     GLint params = 1;
497     CGLSetParameter( CGLGetCurrentContext(), kCGLCPSwapInterval,
498                      &params );
500     @synchronized (self) {
501         _glContext = context;
502     }
504     return 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) {
515         if (_voutDisplay) {
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);
520             else
521                 vout_display_SendEventMousePressed (_voutDisplay, MOUSE_BUTTON_CENTER);
522         }
523     }
526 - (void)mouseButtonUp:(int)buttonNumber
528     @synchronized (self) {
529         if (_voutDisplay) {
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);
534             else
535                 vout_display_SendEventMouseReleased (_voutDisplay, MOUSE_BUTTON_CENTER);
536         }
537     }
540 - (void)mouseMovedToX:(double)xValue Y:(double)yValue
542     @synchronized (self) {
543         if (_voutDisplay) {
544             vout_display_SendMouseMovedDisplayCoordinates (_voutDisplay,
545                                                            xValue,
546                                                            yValue);
547         }
548     }
551 @end