d3d11: handle VLC_CODEC_D3D11_OPAQUE_10B upload/download
[vlc.git] / modules / video_output / caopengllayer.m
bloba8cffadb617c8d2845c66ce6c3db7434ff14fd46
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 static void PictureDisplay  (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture);
65 static int Control          (vout_display_t *vd, int query, va_list ap);
67 static void *OurGetProcAddress (vlc_gl_t *gl, const char *name);
68 static int OpenglLock         (vlc_gl_t *gl);
69 static void OpenglUnlock       (vlc_gl_t *gl);
70 static void OpenglSwap         (vlc_gl_t *gl);
72 @protocol VLCCoreAnimationVideoLayerEmbedding <NSObject>
73 - (void)addVoutLayer:(CALayer *)aLayer;
74 - (void)removeVoutLayer:(CALayer *)aLayer;
75 - (CGSize)currentOutputSize;
76 @end
78 @interface VLCCAOpenGLLayer : CAOpenGLLayer
80 @property (nonatomic, readwrite) vout_display_t* voutDisplay;
81 @property (nonatomic, readwrite) CGLContextObj glContext;
83 @end
86 struct vout_display_sys_t {
88     picture_pool_t *pool;
89     picture_resource_t resource;
91     CALayer <VLCCoreAnimationVideoLayerEmbedding> *container;
92     vout_window_t *embed;
93     VLCCAOpenGLLayer *cgLayer;
95     vlc_gl_t *gl;
96     vout_display_opengl_t *vgl;
98     vout_display_place_t place;
100     bool  b_frame_available;
103 struct gl_sys
105     CGLContextObj locked_ctx;
106     VLCCAOpenGLLayer *cgLayer;
109 /*****************************************************************************
110  * Open: This function allocates and initializes the OpenGL vout method.
111  *****************************************************************************/
112 static int Open (vlc_object_t *p_this)
114     vout_display_t *vd = (vout_display_t *)p_this;
115     vout_display_sys_t *sys;
117     /* Allocate structure */
118     vd->sys = sys = calloc(1, sizeof(vout_display_sys_t));
119     if (sys == NULL)
120         return VLC_EGENERIC;
122     @autoreleasepool {
123         id container = var_CreateGetAddress(vd, "drawable-nsobject");
124         if (container)
125             vout_display_DeleteWindow(vd, NULL);
126         else {
127             sys->embed = vout_display_NewWindow(vd, VOUT_WINDOW_TYPE_NSOBJECT);
128             if (sys->embed)
129                 container = sys->embed->handle.nsobject;
131             if (!container) {
132                 msg_Err(vd, "No drawable-nsobject found!");
133                 goto bailout;
134             }
135         }
137         /* store for later, released in Close() */
138         sys->container = [container retain];
140         [CATransaction begin];
141         sys->cgLayer = [[VLCCAOpenGLLayer alloc] init];
142         [sys->cgLayer setVoutDisplay:vd];
144         [sys->cgLayer performSelectorOnMainThread:@selector(display)
145                                        withObject:nil
146                                     waitUntilDone:YES];
148         if ([container respondsToSelector:@selector(addVoutLayer:)]) {
149             msg_Dbg(vd, "container implements implicit protocol");
150             [container addVoutLayer:sys->cgLayer];
151         } else if ([container respondsToSelector:@selector(addSublayer:)] ||
152                    [container isKindOfClass:[CALayer class]]) {
153             msg_Dbg(vd, "container doesn't implement implicit protocol, fallback mode used");
154             [container addSublayer:sys->cgLayer];
155         } else {
156             msg_Err(vd, "Provided NSObject container isn't compatible");
157             [sys->cgLayer release];
158             sys->cgLayer = nil;
159             [CATransaction commit];
160             goto bailout;
161         }
162         [CATransaction commit];
164         if (!sys->cgLayer)
165             goto bailout;
167         if (![sys->cgLayer glContext])
168             msg_Warn(vd, "we might not have an OpenGL context yet");
170         /* Initialize common OpenGL video display */
171         sys->gl = vlc_object_create(vd, sizeof(*sys->gl));
172         if (unlikely(!sys->gl))
173             goto bailout;
174         sys->gl->makeCurrent = OpenglLock;
175         sys->gl->releaseCurrent = OpenglUnlock;
176         sys->gl->swap = OpenglSwap;
177         sys->gl->getProcAddress = OurGetProcAddress;
179         struct gl_sys *glsys = sys->gl->sys = malloc(sizeof(*glsys));
180         if (!sys->gl->sys)
181             goto bailout;
182         glsys->locked_ctx = NULL;
183         glsys->cgLayer = sys->cgLayer;
185         const vlc_fourcc_t *subpicture_chromas;
186         video_format_t fmt = vd->fmt;
187         if (!OpenglLock(sys->gl)) {
188             sys->vgl = vout_display_opengl_New(&vd->fmt, &subpicture_chromas,
189                                                sys->gl, &vd->cfg->viewpoint);
190             OpenglUnlock(sys->gl);
191         } else
192             sys->vgl = NULL;
193         if (!sys->vgl) {
194             msg_Err(vd, "Error while initializing opengl display.");
195             goto bailout;
196         }
198         /* setup vout display */
199         vout_display_info_t info = vd->info;
200         info.subpicture_chromas = subpicture_chromas;
201         vd->info = info;
203         vd->pool    = Pool;
204         vd->prepare = PictureRender;
205         vd->display = PictureDisplay;
206         vd->control = Control;
208         if (OSX_SIERRA_AND_HIGHER) {
209             /* request our screen's HDR mode (introduced in OS X 10.11, but correctly supported in 10.12 only) */
210             if ([sys->cgLayer respondsToSelector:@selector(setWantsExtendedDynamicRangeContent:)]) {
211                 [sys->cgLayer setWantsExtendedDynamicRangeContent:YES];
212             }
213         }
215         /* setup initial state */
216         CGSize outputSize;
217         if ([container respondsToSelector:@selector(currentOutputSize)])
218             outputSize = [container currentOutputSize];
219         else
220             outputSize = [sys->container visibleRect].size;
221         vout_display_SendEventDisplaySize(vd, (int)outputSize.width, (int)outputSize.height);
223         return VLC_SUCCESS;
225     bailout:
226         Close(p_this);
227         return VLC_EGENERIC;
228     }
231 static void Close (vlc_object_t *p_this)
233     vout_display_t *vd = (vout_display_t *)p_this;
234     vout_display_sys_t *sys = vd->sys;
236     if (sys->cgLayer) {
237         if ([sys->container respondsToSelector:@selector(removeVoutLayer:)])
238             [sys->container removeVoutLayer:sys->cgLayer];
239         else
240             [sys->cgLayer removeFromSuperlayer];
242         if ([sys->cgLayer glContext])
243             CGLReleaseContext([sys->cgLayer glContext]);
245         [sys->cgLayer release];
246     }
248     if (sys->container)
249         [sys->container release];
251     if (sys->embed)
252         vout_display_DeleteWindow(vd, sys->embed);
254     if (sys->vgl != NULL && !OpenglLock(sys->gl)) {
255         vout_display_opengl_Delete(sys->vgl);
256         OpenglUnlock(sys->gl);
257     }
259     if (sys->gl != NULL)
260     {
261         if (sys->gl->sys != NULL)
262         {
263             assert(((struct gl_sys *)sys->gl->sys)->locked_ctx == NULL);
264             free(sys->gl->sys);
265         }
266         vlc_object_release(sys->gl);
267     }
269     free(sys);
272 static picture_pool_t *Pool (vout_display_t *vd, unsigned count)
274     vout_display_sys_t *sys = vd->sys;
276     if (!sys->pool && !OpenglLock(sys->gl)) {
277         sys->pool = vout_display_opengl_GetPool(sys->vgl, count);
278         OpenglUnlock(sys->gl);
279         assert(sys->pool);
280     }
281     return sys->pool;
284 static void PictureRender (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
286     vout_display_sys_t *sys = vd->sys;
288     if (pic == NULL) {
289         msg_Warn(vd, "invalid pic, skipping frame");
290         return;
291     }
293     @synchronized (sys->cgLayer) {
294         if (!OpenglLock(sys->gl)) {
295             vout_display_opengl_Prepare(sys->vgl, pic, subpicture);
296             OpenglUnlock(sys->gl);
297         }
298     }
301 static void PictureDisplay (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
303     vout_display_sys_t *sys = vd->sys;
305     @synchronized (sys->cgLayer) {
306         sys->b_frame_available = YES;
308         /* Calling display on the non-main thread is not officially supported, but
309          * its suggested at several places and works fine here. Flush is thread-safe
310          * and makes sure the picture is actually displayed. */
311         [sys->cgLayer display];
312         [CATransaction flush];
313     }
315     picture_Release(pic);
317     if (subpicture)
318         subpicture_Delete(subpicture);
321 static int Control (vout_display_t *vd, int query, va_list ap)
323     vout_display_sys_t *sys = vd->sys;
325     if (!vd->sys)
326         return VLC_EGENERIC;
328     switch (query)
329     {
330         case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
331         case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
332         case VOUT_DISPLAY_CHANGE_ZOOM:
333         case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
334         case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
335         {
336             const vout_display_cfg_t *cfg;
338             if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT || query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
339                 cfg = vd->cfg;
340             } else {
341                 cfg = (const vout_display_cfg_t*)va_arg (ap, const vout_display_cfg_t *);
342             }
344             /* we always use our current frame here */
345             vout_display_cfg_t cfg_tmp = *cfg;
346             [CATransaction lock];
347             CGRect bounds = [sys->cgLayer visibleRect];
348             [CATransaction unlock];
349             cfg_tmp.display.width = bounds.size.width;
350             cfg_tmp.display.height = bounds.size.height;
352             /* Reverse vertical alignment as the GL tex are Y inverted */
353             if (cfg_tmp.align.vertical == VOUT_DISPLAY_ALIGN_TOP)
354                 cfg_tmp.align.vertical = VOUT_DISPLAY_ALIGN_BOTTOM;
355             else if (cfg_tmp.align.vertical == VOUT_DISPLAY_ALIGN_BOTTOM)
356                 cfg_tmp.align.vertical = VOUT_DISPLAY_ALIGN_TOP;
358             vout_display_place_t place;
359             vout_display_PlacePicture (&place, &vd->source, &cfg_tmp, false);
360             if (OpenglLock(sys->gl))
361                 return VLC_EGENERIC;
363             vout_display_opengl_SetWindowAspectRatio(sys->vgl, (float)place.width / place.height);
364             OpenglUnlock(sys->gl);
366             sys->place = place;
368             return VLC_SUCCESS;
369         }
371         case VOUT_DISPLAY_CHANGE_VIEWPOINT:
372         {
373             int ret;
375             if (OpenglLock(sys->gl))
376                 return VLC_EGENERIC;
378             ret = vout_display_opengl_SetViewpoint(sys->vgl,
379                 &va_arg (ap, const vout_display_cfg_t* )->viewpoint);
380             OpenglUnlock(sys->gl);
381             return ret;
382         }
384         case VOUT_DISPLAY_RESET_PICTURES:
385             vlc_assert_unreachable ();
386         default:
387             msg_Err (vd, "Unhandled request %d", query);
388             return VLC_EGENERIC;
389     }
391     return VLC_SUCCESS;
394 #pragma mark -
395 #pragma mark OpenGL callbacks
397 static int OpenglLock (vlc_gl_t *gl)
399     struct gl_sys *sys = gl->sys;
400     assert(sys->locked_ctx == NULL);
402     CGLContextObj ctx = [sys->cgLayer glContext];
403     if(!ctx) {
404         return 1;
405     }
407     CGLError err = CGLLockContext(ctx);
408     if (kCGLNoError == err) {
409         sys->locked_ctx = ctx;
410         CGLSetCurrentContext(ctx);
411         return 0;
412     }
413     return 1;
416 static void OpenglUnlock (vlc_gl_t *gl)
418     struct gl_sys *sys = gl->sys;
419     CGLUnlockContext(sys->locked_ctx);
420     sys->locked_ctx = NULL;
423 static void OpenglSwap (vlc_gl_t *gl)
425     glFlush();
428 static void *OurGetProcAddress (vlc_gl_t *gl, const char *name)
430     VLC_UNUSED(gl);
432     return dlsym(RTLD_DEFAULT, name);
435 #pragma mark -
436 #pragma mark CA layer
438 /*****************************************************************************
439  * @implementation VLCCAOpenGLLayer
440  *****************************************************************************/
441 @implementation VLCCAOpenGLLayer
443 - (id)init {
445     self = [super init];
446     if (self) {
447         [CATransaction lock];
448         self.needsDisplayOnBoundsChange = YES;
449         self.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
450         self.asynchronous = NO;
451         [CATransaction unlock];
452     }
454     return self;
457 - (void)setVoutDisplay:(vout_display_t *)aVd
459     _voutDisplay = aVd;
462 - (void)resizeWithOldSuperlayerSize:(CGSize)size
464     [super resizeWithOldSuperlayerSize: size];
466     CGSize boundsSize = self.visibleRect.size;
468     if (_voutDisplay)
469         vout_display_SendEventDisplaySize(_voutDisplay, boundsSize.width, boundsSize.height);
472 - (BOOL)canDrawInCGLContext:(CGLContextObj)glContext pixelFormat:(CGLPixelFormatObj)pixelFormat forLayerTime:(CFTimeInterval)timeInterval displayTime:(const CVTimeStamp *)timeStamp
474     /* Only draw the frame if we have a frame that was previously rendered */
475     if (!_voutDisplay)
476         return false;
478     return _voutDisplay->sys->b_frame_available;
481 - (void)drawInCGLContext:(CGLContextObj)glContext pixelFormat:(CGLPixelFormatObj)pixelFormat forLayerTime:(CFTimeInterval)timeInterval displayTime:(const CVTimeStamp *)timeStamp
483     if (!_voutDisplay)
484         return;
485     vout_display_sys_t *sys = _voutDisplay->sys;
487     if (!sys->vgl)
488         return;
490     CGRect bounds = [self visibleRect];
492     // x / y are top left corner, but we need the lower left one
493     vout_display_opengl_Viewport(sys->vgl, sys->place.x,
494                                  bounds.size.height - (sys->place.y + sys->place.height),
495                                  sys->place.width, sys->place.height);
497     // flush is also done by this method, no need to call super
498     vout_display_opengl_Display (sys->vgl, &_voutDisplay->source);
499     sys->b_frame_available = NO;
502 -(CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask
504     // The default is fine for this demonstration.
505     return [super copyCGLPixelFormatForDisplayMask:mask];
508 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat
510     // Only one opengl context is allowed for the module lifetime
511     if(_glContext) {
512         msg_Dbg(_voutDisplay, "Return existing context: %p", _glContext);
513         return _glContext;
514     }
516     CGLContextObj context = [super copyCGLContextForPixelFormat:pixelFormat];
518     // Swap buffers only during the vertical retrace of the monitor.
519     //http://developer.apple.com/documentation/GraphicsImaging/
520     //Conceptual/OpenGL/chap5/chapter_5_section_44.html /
522     GLint params = 1;
523     CGLSetParameter( CGLGetCurrentContext(), kCGLCPSwapInterval,
524                      &params );
526     @synchronized (self) {
527         _glContext = context;
528     }
530     return context;
533 - (void)releaseCGLContext:(CGLContextObj)glContext
535     // do not release anything here, we do that when closing the module
538 - (void)mouseButtonDown:(int)buttonNumber
540     @synchronized (self) {
541         if (_voutDisplay) {
542             if (buttonNumber == 0)
543                 vout_display_SendEventMousePressed (_voutDisplay, MOUSE_BUTTON_LEFT);
544             else if (buttonNumber == 1)
545                 vout_display_SendEventMousePressed (_voutDisplay, MOUSE_BUTTON_RIGHT);
546             else
547                 vout_display_SendEventMousePressed (_voutDisplay, MOUSE_BUTTON_CENTER);
548         }
549     }
552 - (void)mouseButtonUp:(int)buttonNumber
554     @synchronized (self) {
555         if (_voutDisplay) {
556             if (buttonNumber == 0)
557                 vout_display_SendEventMouseReleased (_voutDisplay, MOUSE_BUTTON_LEFT);
558             else if (buttonNumber == 1)
559                 vout_display_SendEventMouseReleased (_voutDisplay, MOUSE_BUTTON_RIGHT);
560             else
561                 vout_display_SendEventMouseReleased (_voutDisplay, MOUSE_BUTTON_CENTER);
562         }
563     }
566 - (void)mouseMovedToX:(double)xValue Y:(double)yValue
568     @synchronized (self) {
569         if (_voutDisplay) {
570             vout_display_SendMouseMovedDisplayCoordinates (_voutDisplay,
571                                                            ORIENT_NORMAL,
572                                                            xValue,
573                                                            yValue,
574                                                            &_voutDisplay->sys->place);
575         }
576     }
579 @end