1 /*****************************************************************************
2 * caopengllayer.m: CAOpenGLLayer (Mac OS X) video output
3 *****************************************************************************
4 * Copyright (C) 2014 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 */
46 /*****************************************************************************
48 *****************************************************************************/
49 static int Open (vlc_object_t *);
50 static void Close (vlc_object_t *);
53 set_description(N_("Core Animation OpenGL Layer (Mac OS X)"))
54 set_capability("vout display", 0)
55 set_category(CAT_VIDEO)
56 set_subcategory(SUBCAT_VIDEO_VOUT)
57 set_callbacks(Open, Close)
61 static picture_pool_t *Pool (vout_display_t *vd, unsigned requested_count);
62 static void PictureRender (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture);
63 static void PictureDisplay (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture);
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;
77 @interface VLCCAOpenGLLayer : CAOpenGLLayer {
81 - (void)setVoutDisplay:(vout_display_t *)aVd;
85 struct vout_display_sys_t {
88 picture_resource_t resource;
90 CALayer <VLCCoreAnimationVideoLayerEmbedding> *container;
92 VLCCAOpenGLLayer *cgLayer;
94 CGLContextObj glContext;
97 vout_display_opengl_t *vgl;
99 vout_display_place_t place;
101 bool b_frame_available;
104 /*****************************************************************************
105 * Open: This function allocates and initializes the OpenGL vout method.
106 *****************************************************************************/
107 static int Open (vlc_object_t *p_this)
109 vout_display_t *vd = (vout_display_t *)p_this;
110 vout_display_sys_t *sys;
112 /* Allocate structure */
113 vd->sys = sys = calloc(1, sizeof(vout_display_sys_t));
117 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
119 id container = var_CreateGetAddress(vd, "drawable-nsobject");
121 vout_display_DeleteWindow(vd, NULL);
123 vout_window_cfg_t wnd_cfg;
125 memset(&wnd_cfg, 0, sizeof(wnd_cfg));
126 wnd_cfg.type = VOUT_WINDOW_TYPE_NSOBJECT;
127 wnd_cfg.x = var_InheritInteger(vd, "video-x");
128 wnd_cfg.y = var_InheritInteger(vd, "video-y");
129 wnd_cfg.height = vd->cfg->display.height;
130 wnd_cfg.width = vd->cfg->display.width;
132 sys->embed = vout_display_NewWindow(vd, &wnd_cfg);
134 container = sys->embed->handle.nsobject;
137 msg_Err(vd, "No drawable-nsobject found!");
142 /* store for later, released in Close() */
143 sys->container = [container retain];
145 [CATransaction begin];
146 sys->cgLayer = [[VLCCAOpenGLLayer alloc] init];
147 [sys->cgLayer setVoutDisplay:vd];
149 [sys->cgLayer performSelectorOnMainThread:@selector(display) withObject:nil waitUntilDone:YES];
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:)] || [container isKindOfClass:[CALayer class]]) {
155 msg_Dbg(vd, "container doesn't implement implicit protocol, fallback mode used");
156 [container addSublayer:sys->cgLayer];
158 msg_Err(vd, "Provided NSObject container isn't compatible");
159 [sys->cgLayer release];
161 [CATransaction commit];
164 [CATransaction commit];
170 msg_Warn(vd, "we might not have an OpenGL context yet");
172 /* Initialize common OpenGL video display */
173 sys->gl.lock = OpenglLock;
174 sys->gl.unlock = OpenglUnlock;
175 sys->gl.swap = OpenglSwap;
176 sys->gl.getProcAddress = OurGetProcAddress;
179 const vlc_fourcc_t *subpicture_chromas;
180 video_format_t fmt = vd->fmt;
181 sys->vgl = vout_display_opengl_New(&vd->fmt, &subpicture_chromas, &sys->gl);
183 msg_Err(vd, "Error while initializing opengl display.");
188 /* setup vout display */
189 vout_display_info_t info = vd->info;
190 info.subpicture_chromas = subpicture_chromas;
191 info.has_hide_mouse = true;
195 vd->prepare = PictureRender;
196 vd->display = PictureDisplay;
197 vd->control = Control;
199 /* setup initial state */
201 if ([container respondsToSelector:@selector(currentOutputSize)])
202 outputSize = [container currentOutputSize];
204 outputSize = [sys->container visibleRect].size;
205 vout_display_SendEventFullscreen(vd, false);
206 vout_display_SendEventDisplaySize(vd, (int)outputSize.width, (int)outputSize.height, false);
217 static void Close (vlc_object_t *p_this)
219 vout_display_t *vd = (vout_display_t *)p_this;
220 vout_display_sys_t *sys = vd->sys;
223 if ([sys->container respondsToSelector:@selector(removeVoutLayer:)])
224 [sys->container removeVoutLayer:sys->cgLayer];
226 [sys->cgLayer removeFromSuperlayer];
227 [sys->cgLayer release];
231 [sys->container release];
234 vout_display_DeleteWindow(vd, sys->embed);
236 if (sys->gl.sys != NULL)
237 vout_display_opengl_Delete(sys->vgl);
240 CGLReleaseContext(sys->glContext);
245 static picture_pool_t *Pool (vout_display_t *vd, unsigned count)
247 vout_display_sys_t *sys = vd->sys;
250 sys->pool = vout_display_opengl_GetPool(sys->vgl, count);
255 static void PictureRender (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
257 vout_display_sys_t *sys = vd->sys;
260 msg_Warn(vd, "invalid pic, skipping frame");
264 @synchronized (sys->cgLayer) {
265 vout_display_opengl_Prepare(sys->vgl, pic, subpicture);
269 static void PictureDisplay (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
271 vout_display_sys_t *sys = vd->sys;
273 @synchronized (sys->cgLayer) {
274 sys->b_frame_available = YES;
276 /* Calling display on the non-main thread is not officially supported, but
277 * its suggested at several places and works fine here. Flush is thread-safe
278 * and makes sure the picture is actually displayed. */
279 [sys->cgLayer display];
280 [CATransaction flush];
283 picture_Release(pic);
286 subpicture_Delete(subpicture);
289 static int Control (vout_display_t *vd, int query, va_list ap)
291 vout_display_sys_t *sys = vd->sys;
298 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
299 case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
300 case VOUT_DISPLAY_CHANGE_ZOOM:
301 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
302 case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
303 case VOUT_DISPLAY_CHANGE_FULLSCREEN:
305 const vout_display_cfg_t *cfg;
306 const video_format_t *source;
308 if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT || query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
309 source = (const video_format_t *)va_arg (ap, const video_format_t *);
312 source = &vd->source;
313 cfg = (const vout_display_cfg_t*)va_arg (ap, const vout_display_cfg_t *);
316 /* we always use our current frame here */
317 vout_display_cfg_t cfg_tmp = *cfg;
318 [CATransaction lock];
319 CGRect bounds = [sys->cgLayer bounds];
320 [CATransaction unlock];
321 cfg_tmp.display.width = bounds.size.width;
322 cfg_tmp.display.height = bounds.size.height;
324 vout_display_place_t place;
325 vout_display_PlacePicture (&place, source, &cfg_tmp, false);
331 case VOUT_DISPLAY_HIDE_MOUSE:
333 [NSCursor setHiddenUntilMouseMoves: YES];
337 case VOUT_DISPLAY_GET_OPENGL:
339 vlc_gl_t **gl = va_arg (ap, vlc_gl_t **);
343 case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
348 case VOUT_DISPLAY_RESET_PICTURES:
351 msg_Err (vd, "Unhandled request %d", query);
359 #pragma mark OpenGL callbacks
361 static int OpenglLock (vlc_gl_t *gl)
363 vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
365 if(!sys->glContext) {
369 CGLError err = CGLLockContext(sys->glContext);
370 if (kCGLNoError == err) {
371 CGLSetCurrentContext(sys->glContext);
377 static void OpenglUnlock (vlc_gl_t *gl)
379 vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
381 if (!sys->glContext) {
385 CGLUnlockContext(sys->glContext);
388 static void OpenglSwap (vlc_gl_t *gl)
393 static void *OurGetProcAddress (vlc_gl_t *gl, const char *name)
397 return dlsym(RTLD_DEFAULT, name);
401 #pragma mark CA layer
403 /*****************************************************************************
404 * @implementation VLCCAOpenGLLayer
405 *****************************************************************************/
406 @implementation VLCCAOpenGLLayer
412 [CATransaction lock];
413 [self setAutoresizingMask: kCALayerWidthSizable | kCALayerHeightSizable];
414 self.asynchronous = NO;
415 [CATransaction unlock];
421 - (void)setVoutDisplay:(vout_display_t *)aVd
426 - (void)resizeWithOldSuperlayerSize:(CGSize)size
428 [super resizeWithOldSuperlayerSize: size];
430 CGSize boundsSize = self.bounds.size;
432 vout_display_SendEventDisplaySize(_vd, boundsSize.width, boundsSize.height, _vd->cfg->is_fullscreen);
435 - (BOOL)canDrawInCGLContext:(CGLContextObj)glContext pixelFormat:(CGLPixelFormatObj)pixelFormat forLayerTime:(CFTimeInterval)timeInterval displayTime:(const CVTimeStamp *)timeStamp
437 /* Only draw the frame if we have a frame that was previously rendered */
441 return _vd->sys->b_frame_available;
444 - (void)drawInCGLContext:(CGLContextObj)glContext pixelFormat:(CGLPixelFormatObj)pixelFormat forLayerTime:(CFTimeInterval)timeInterval displayTime:(const CVTimeStamp *)timeStamp
448 vout_display_sys_t *sys = _vd->sys;
453 CGRect bounds = [self bounds];
454 // x / y are top left corner, but we need the lower left one
455 glViewport (sys->place.x, bounds.size.height - (sys->place.y + sys->place.height), sys->place.width, sys->place.height);
457 // flush is also done by this method, no need to call super
458 vout_display_opengl_Display (sys->vgl, &_vd->source);
459 sys->b_frame_available = NO;
462 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat
464 // Only one opengl context is allowed for the module lifetime
465 if(_vd->sys->glContext) {
466 msg_Dbg(_vd, "Return existing context: %p", _vd->sys->glContext);
467 return _vd->sys->glContext;
470 CGLContextObj context = [super copyCGLContextForPixelFormat:pixelFormat];
472 // Swap buffers only during the vertical retrace of the monitor.
473 //http://developer.apple.com/documentation/GraphicsImaging/
474 //Conceptual/OpenGL/chap5/chapter_5_section_44.html /
477 CGLSetParameter( CGLGetCurrentContext(), kCGLCPSwapInterval,
480 @synchronized (self) {
481 _vd->sys->glContext = context;
487 - (void)releaseCGLContext:(CGLContextObj)glContext
489 // do not release anything here, we do that when closing the module
492 - (void)mouseButtonDown:(int)buttonNumber
494 @synchronized (self) {
496 if (buttonNumber == 0)
497 vout_display_SendEventMousePressed (_vd, MOUSE_BUTTON_LEFT);
498 else if (buttonNumber == 1)
499 vout_display_SendEventMousePressed (_vd, MOUSE_BUTTON_RIGHT);
501 vout_display_SendEventMousePressed (_vd, MOUSE_BUTTON_CENTER);
506 - (void)mouseButtonUp:(int)buttonNumber
508 @synchronized (self) {
510 if (buttonNumber == 0)
511 vout_display_SendEventMouseReleased (_vd, MOUSE_BUTTON_LEFT);
512 else if (buttonNumber == 1)
513 vout_display_SendEventMouseReleased (_vd, MOUSE_BUTTON_RIGHT);
515 vout_display_SendEventMouseReleased (_vd, MOUSE_BUTTON_CENTER);
520 - (void)mouseMovedToX:(double)xValue Y:(double)yValue
522 @synchronized (self) {
524 vout_display_SendMouseMovedDisplayCoordinates (_vd,