freetype: font fallback for Windows
[vlc.git] / modules / video_output / caopengllayer.m
blobc8dfd652f48487aebe86dc136b719b3312bb3d29
1 /*****************************************************************************
2  * caopengllayer.m: CAOpenGLLayer (Mac OS X) video output
3  *****************************************************************************
4  * Copyright (C) 2014-2015 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.h"
46 /*****************************************************************************
47  * Vout interface
48  *****************************************************************************/
49 static int  Open   (vlc_object_t *);
50 static void Close  (vlc_object_t *);
52 vlc_module_begin()
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)
58 vlc_module_end()
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;
75 @end
77 @interface VLCCAOpenGLLayer : CAOpenGLLayer
79 @property (nonatomic, readwrite) vout_display_t* voutDisplay;
81 @end
84 struct vout_display_sys_t {
86     picture_pool_t *pool;
87     picture_resource_t resource;
89     CALayer <VLCCoreAnimationVideoLayerEmbedding> *container;
90     vout_window_t *embed;
91     VLCCAOpenGLLayer *cgLayer;
93     CGLContextObj glContext;
95     vlc_gl_t gl;
96     vout_display_opengl_t *vgl;
98     vout_display_place_t place;
100     bool  b_frame_available;
103 /*****************************************************************************
104  * Open: This function allocates and initializes the OpenGL vout method.
105  *****************************************************************************/
106 static int Open (vlc_object_t *p_this)
108     vout_display_t *vd = (vout_display_t *)p_this;
109     vout_display_sys_t *sys;
111     /* Allocate structure */
112     vd->sys = sys = calloc(1, sizeof(vout_display_sys_t));
113     if (sys == NULL)
114         return VLC_EGENERIC;
116     @autoreleasepool {
117         id container = var_CreateGetAddress(vd, "drawable-nsobject");
118         if (container)
119             vout_display_DeleteWindow(vd, NULL);
120         else {
121             sys->embed = vout_display_NewWindow(vd, VOUT_WINDOW_TYPE_NSOBJECT);
122             if (sys->embed)
123                 container = sys->embed->handle.nsobject;
125             if (!container) {
126                 msg_Err(vd, "No drawable-nsobject found!");
127                 goto bailout;
128             }
129         }
131         /* store for later, released in Close() */
132         sys->container = [container retain];
134         [CATransaction begin];
135         sys->cgLayer = [[VLCCAOpenGLLayer alloc] init];
136         [sys->cgLayer setVoutDisplay:vd];
138         [sys->cgLayer performSelectorOnMainThread:@selector(display)
139                                        withObject:nil
140                                     waitUntilDone:YES];
142         if ([container respondsToSelector:@selector(addVoutLayer:)]) {
143             msg_Dbg(vd, "container implements implicit protocol");
144             [container addVoutLayer:sys->cgLayer];
145         } else if ([container respondsToSelector:@selector(addSublayer:)] ||
146                    [container isKindOfClass:[CALayer class]]) {
147             msg_Dbg(vd, "container doesn't implement implicit protocol, fallback mode used");
148             [container addSublayer:sys->cgLayer];
149         } else {
150             msg_Err(vd, "Provided NSObject container isn't compatible");
151             [sys->cgLayer release];
152             sys->cgLayer = nil;
153             [CATransaction commit];
154             goto bailout;
155         }
156         [CATransaction commit];
158         if (!sys->cgLayer)
159             goto bailout;
161         if (!sys->glContext)
162             msg_Warn(vd, "we might not have an OpenGL context yet");
164         /* Initialize common OpenGL video display */
165         sys->gl.lock = OpenglLock;
166         sys->gl.unlock = OpenglUnlock;
167         sys->gl.swap = OpenglSwap;
168         sys->gl.getProcAddress = OurGetProcAddress;
169         sys->gl.sys = sys;
171         const vlc_fourcc_t *subpicture_chromas;
172         video_format_t fmt = vd->fmt;
173         sys->vgl = vout_display_opengl_New(&vd->fmt, &subpicture_chromas, &sys->gl);
174         if (!sys->vgl) {
175             msg_Err(vd, "Error while initializing opengl display.");
176             sys->gl.sys = NULL;
177             goto bailout;
178         }
180         /* setup vout display */
181         vout_display_info_t info = vd->info;
182         info.subpicture_chromas = subpicture_chromas;
183         info.has_hide_mouse = true;
184         vd->info = info;
186         vd->pool    = Pool;
187         vd->prepare = PictureRender;
188         vd->display = PictureDisplay;
189         vd->control = Control;
191         /* setup initial state */
192         CGSize outputSize;
193         if ([container respondsToSelector:@selector(currentOutputSize)])
194             outputSize = [container currentOutputSize];
195         else
196             outputSize = [sys->container visibleRect].size;
197         vout_display_SendEventFullscreen(vd, false);
198         vout_display_SendEventDisplaySize(vd, (int)outputSize.width, (int)outputSize.height);
199         
200         return VLC_SUCCESS;
201         
202     bailout:
203         Close(p_this);
204         return VLC_EGENERIC;
205     }
208 static void Close (vlc_object_t *p_this)
210     vout_display_t *vd = (vout_display_t *)p_this;
211     vout_display_sys_t *sys = vd->sys;
213     if (sys->cgLayer) {
214         if ([sys->container respondsToSelector:@selector(removeVoutLayer:)])
215             [sys->container removeVoutLayer:sys->cgLayer];
216         else
217             [sys->cgLayer removeFromSuperlayer];
218         [sys->cgLayer release];
219     }
221     if (sys->container)
222         [sys->container release];
224     if (sys->embed)
225         vout_display_DeleteWindow(vd, sys->embed);
227     if (sys->gl.sys != NULL)
228         vout_display_opengl_Delete(sys->vgl);
230     if (sys->glContext)
231         CGLReleaseContext(sys->glContext);
233     free(sys);
236 static picture_pool_t *Pool (vout_display_t *vd, unsigned count)
238     vout_display_sys_t *sys = vd->sys;
240     if (!sys->pool)
241         sys->pool = vout_display_opengl_GetPool(sys->vgl, count);
242     assert(sys->pool);
243     return sys->pool;
246 static void PictureRender (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
248     vout_display_sys_t *sys = vd->sys;
250     if (pic == NULL) {
251         msg_Warn(vd, "invalid pic, skipping frame");
252         return;
253     }
255     @synchronized (sys->cgLayer) {
256         vout_display_opengl_Prepare(sys->vgl, pic, subpicture);
257     }
260 static void PictureDisplay (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
262     vout_display_sys_t *sys = vd->sys;
264     @synchronized (sys->cgLayer) {
265         sys->b_frame_available = YES;
267         /* Calling display on the non-main thread is not officially supported, but
268          * its suggested at several places and works fine here. Flush is thread-safe
269          * and makes sure the picture is actually displayed. */
270         [sys->cgLayer display];
271         [CATransaction flush];
272     }
274     picture_Release(pic);
276     if (subpicture)
277         subpicture_Delete(subpicture);
280 static int Control (vout_display_t *vd, int query, va_list ap)
282     vout_display_sys_t *sys = vd->sys;
284     if (!vd->sys)
285         return VLC_EGENERIC;
287     switch (query)
288     {
289         case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
290         case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
291         case VOUT_DISPLAY_CHANGE_ZOOM:
292         case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
293         case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
294         {
295             const vout_display_cfg_t *cfg;
296             const video_format_t *source;
298             if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT || query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
299                 source = (const video_format_t *)va_arg (ap, const video_format_t *);
300                 cfg = vd->cfg;
301             } else {
302                 source = &vd->source;
303                 cfg = (const vout_display_cfg_t*)va_arg (ap, const vout_display_cfg_t *);
304             }
306             /* we always use our current frame here */
307             vout_display_cfg_t cfg_tmp = *cfg;
308             [CATransaction lock];
309             CGRect bounds = [sys->cgLayer visibleRect];
310             [CATransaction unlock];
311             cfg_tmp.display.width = bounds.size.width;
312             cfg_tmp.display.height = bounds.size.height;
314             vout_display_place_t place;
315             vout_display_PlacePicture (&place, source, &cfg_tmp, false);
316             sys->place = place;
318             return VLC_SUCCESS;
319         }
321         case VOUT_DISPLAY_HIDE_MOUSE:
322         {
323             [NSCursor setHiddenUntilMouseMoves: YES];
324             return VLC_SUCCESS;
325         }
327         case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
328         {
329             return VLC_SUCCESS;
330         }
332         case VOUT_DISPLAY_RESET_PICTURES:
333             vlc_assert_unreachable ();
334         default:
335             msg_Err (vd, "Unhandled request %d", query);
336         case VOUT_DISPLAY_CHANGE_FULLSCREEN:
337             return VLC_EGENERIC;
338     }
340     return VLC_SUCCESS;
343 #pragma mark -
344 #pragma mark OpenGL callbacks
346 static int OpenglLock (vlc_gl_t *gl)
348     vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
350     if(!sys->glContext) {
351         return 1;
352     }
354     CGLError err = CGLLockContext(sys->glContext);
355     if (kCGLNoError == err) {
356         CGLSetCurrentContext(sys->glContext);
357         return 0;
358     }
359     return 1;
362 static void OpenglUnlock (vlc_gl_t *gl)
364     vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
366     if (!sys->glContext) {
367         return;
368     }
370     CGLUnlockContext(sys->glContext);
373 static void OpenglSwap (vlc_gl_t *gl)
375     glFlush();
378 static void *OurGetProcAddress (vlc_gl_t *gl, const char *name)
380     VLC_UNUSED(gl);
382     return dlsym(RTLD_DEFAULT, name);
385 #pragma mark -
386 #pragma mark CA layer
388 /*****************************************************************************
389  * @implementation VLCCAOpenGLLayer
390  *****************************************************************************/
391 @implementation VLCCAOpenGLLayer
393 - (id)init {
395     self = [super init];
396     if (self) {
397         [CATransaction lock];
398         self.needsDisplayOnBoundsChange = YES;
399         self.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
400         self.asynchronous = NO;
401         [CATransaction unlock];
402     }
404     return self;
407 - (void)setVoutDisplay:(vout_display_t *)aVd
409     _voutDisplay = aVd;
412 - (void)resizeWithOldSuperlayerSize:(CGSize)size
414     [super resizeWithOldSuperlayerSize: size];
416     CGSize boundsSize = self.visibleRect.size;
418     if (_voutDisplay)
419         vout_display_SendEventDisplaySize(_voutDisplay, boundsSize.width, boundsSize.height);
422 - (BOOL)canDrawInCGLContext:(CGLContextObj)glContext pixelFormat:(CGLPixelFormatObj)pixelFormat forLayerTime:(CFTimeInterval)timeInterval displayTime:(const CVTimeStamp *)timeStamp
424     /* Only draw the frame if we have a frame that was previously rendered */
425     if (!_voutDisplay)
426         return false;
428     return _voutDisplay->sys->b_frame_available;
431 - (void)drawInCGLContext:(CGLContextObj)glContext pixelFormat:(CGLPixelFormatObj)pixelFormat forLayerTime:(CFTimeInterval)timeInterval displayTime:(const CVTimeStamp *)timeStamp
433     if (!_voutDisplay)
434         return;
435     vout_display_sys_t *sys = _voutDisplay->sys;
437     if (!sys->vgl)
438         return;
440     CGRect bounds = [self visibleRect];
442     // x / y are top left corner, but we need the lower left one
443     glViewport (sys->place.x, bounds.size.height - (sys->place.y + sys->place.height), sys->place.width, sys->place.height);
445     // flush is also done by this method, no need to call super
446     vout_display_opengl_Display (sys->vgl, &_voutDisplay->source);
447     sys->b_frame_available = NO;
450 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat
452     // Only one opengl context is allowed for the module lifetime
453     if(_voutDisplay->sys->glContext) {
454         msg_Dbg(_voutDisplay, "Return existing context: %p", _voutDisplay->sys->glContext);
455         return _voutDisplay->sys->glContext;
456     }
458     CGLContextObj context = [super copyCGLContextForPixelFormat:pixelFormat];
460     // Swap buffers only during the vertical retrace of the monitor.
461     //http://developer.apple.com/documentation/GraphicsImaging/
462     //Conceptual/OpenGL/chap5/chapter_5_section_44.html /
464     GLint params = 1;
465     CGLSetParameter( CGLGetCurrentContext(), kCGLCPSwapInterval,
466                      &params );
468     @synchronized (self) {
469         _voutDisplay->sys->glContext = context;
470     }
472     return context;
475 - (void)releaseCGLContext:(CGLContextObj)glContext
477     // do not release anything here, we do that when closing the module
480 - (void)mouseButtonDown:(int)buttonNumber
482     @synchronized (self) {
483         if (_voutDisplay) {
484             if (buttonNumber == 0)
485                 vout_display_SendEventMousePressed (_voutDisplay, MOUSE_BUTTON_LEFT);
486             else if (buttonNumber == 1)
487                 vout_display_SendEventMousePressed (_voutDisplay, MOUSE_BUTTON_RIGHT);
488             else
489                 vout_display_SendEventMousePressed (_voutDisplay, MOUSE_BUTTON_CENTER);
490         }
491     }
494 - (void)mouseButtonUp:(int)buttonNumber
496     @synchronized (self) {
497         if (_voutDisplay) {
498             if (buttonNumber == 0)
499                 vout_display_SendEventMouseReleased (_voutDisplay, MOUSE_BUTTON_LEFT);
500             else if (buttonNumber == 1)
501                 vout_display_SendEventMouseReleased (_voutDisplay, MOUSE_BUTTON_RIGHT);
502             else
503                 vout_display_SendEventMouseReleased (_voutDisplay, MOUSE_BUTTON_CENTER);
504         }
505     }
508 - (void)mouseMovedToX:(double)xValue Y:(double)yValue
510     @synchronized (self) {
511         if (_voutDisplay) {
512             vout_display_SendMouseMovedDisplayCoordinates (_voutDisplay,
513                                                            ORIENT_NORMAL,
514                                                            xValue,
515                                                            yValue,
516                                                            &_voutDisplay->sys->place);
517         }
518     }
521 @end