]> git.sesse.net Git - vlc/blob - modules/video_output/macosx.m
Define HAVE_V4L2 conditional
[vlc] / modules / video_output / macosx.m
1 /*****************************************************************************
2  * voutgl.m: MacOS X OpenGL provider
3  *****************************************************************************
4  * Copyright (C) 2001-2011 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Colin Delacroix <colin@zoy.org>
8  *          Florian G. Pflug <fgp@phlo.org>
9  *          Jon Lech Johansen <jon-vl@nanocrew.net>
10  *          Derk-Jan Hartman <hartman at videolan dot org>
11  *          Eric Petit <titer@m0k.org>
12  *          Benjamin Pracht <bigben at videolan dot org>
13  *          Damien Fouilleul <damienf at videolan dot org>
14  *          Pierre d'Herbemont <pdherbemont at videolan dot org>
15  *          Felix Paul Kühne <fkuehne at videolan dot org>
16  *
17  * This program is free software; you can redistribute it and/or modify
18  * it under the terms of the GNU General Public License as published by
19  * the Free Software Foundation; either version 2 of the License, or
20  * (at your option) any later version.
21  *
22  * This program is distributed in the hope that it will be useful,
23  * but WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25  * GNU General Public License for more details.
26  *
27  * You should have received a copy of the GNU General Public License
28  * along with this program; if not, write to the Free Software
29  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
30  *****************************************************************************/
31
32 /*****************************************************************************
33  * Preamble
34  *****************************************************************************/
35
36 #import <Cocoa/Cocoa.h>
37 #import <OpenGL/OpenGL.h>
38
39 #ifdef HAVE_CONFIG_H
40 # include "config.h"
41 #endif
42
43 #include <vlc_common.h>
44 #include <vlc_plugin.h>
45 #include <vlc_vout_display.h>
46 #include <vlc_opengl.h>
47 #include <vlc_dialog.h>
48 #include "opengl.h"
49
50 #ifndef MAC_OS_X_VERSION_10_7
51 enum {
52     NSApplicationPresentationFullScreen                 = (1 << 10),
53     NSApplicationPresentationAutoHideToolbar            = (1 << 11)
54 };
55 #endif
56
57 /**
58  * Forward declarations
59  */
60 static int Open(vlc_object_t *);
61 static void Close(vlc_object_t *);
62
63 static picture_pool_t *Pool(vout_display_t *vd, unsigned requested_count);
64 static void PictureRender(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture);
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);
67
68 static int OpenglLock(vlc_gl_t *gl);
69 static void OpenglUnlock(vlc_gl_t *gl);
70 static void OpenglSwap(vlc_gl_t *gl);
71
72 /**
73  * Module declaration
74  */
75 vlc_module_begin ()
76     /* Will be loaded even without interface module. see voutgl.m */
77     set_shortname("Mac OS X")
78     set_description( N_("Mac OS X OpenGL video output (requires drawable-nsobject)"))
79     set_category(CAT_VIDEO)
80     set_subcategory(SUBCAT_VIDEO_VOUT )
81     set_capability("vout display", 300)
82     set_callbacks(Open, Close)
83
84     add_shortcut("macosx", "vout_macosx")
85 vlc_module_end ()
86
87 /**
88  * Obj-C protocol declaration that drawable-nsobject should follow
89  */
90 @protocol VLCOpenGLVideoViewEmbedding <NSObject>
91 - (void)addVoutSubview:(NSView *)view;
92 - (void)removeVoutSubview:(NSView *)view;
93 @end
94
95 @interface VLCOpenGLVideoView : NSOpenGLView
96 {
97     vout_display_t *vd;
98     BOOL _hasPendingReshape;
99 }
100 - (void)setVoutDisplay:(vout_display_t *)vd;
101 - (void)setVoutFlushing:(BOOL)flushing;
102 @end
103
104
105 struct vout_display_sys_t
106 {
107     VLCOpenGLVideoView *glView;
108     id<VLCOpenGLVideoViewEmbedding> container;
109
110     vout_window_t *embed;
111     vlc_gl_t gl;
112     vout_display_opengl_t *vgl;
113
114     picture_pool_t *pool;
115     picture_t *current;
116     bool has_first_frame;
117 };
118
119 static int Open(vlc_object_t *this)
120 {
121     vout_display_t *vd = (vout_display_t *)this;
122     vout_display_sys_t *sys = calloc(1, sizeof(*sys));
123     NSAutoreleasePool *nsPool = nil;
124
125     if (!sys)
126         return VLC_ENOMEM;
127
128     if( !CGDisplayUsesOpenGLAcceleration( kCGDirectMainDisplay ) )
129     {
130         msg_Err( this, "no OpenGL hardware acceleration found, video output will fail" );
131         dialog_Fatal( this, _("Video output is not supported"), _("Your Mac lacks Quartz Extreme acceleration, which is required for video output.") );
132         return VLC_EGENERIC;
133     }
134     else
135         msg_Dbg( this, "Quartz Extreme acceleration is active" );
136
137     vd->sys = sys;
138     sys->pool = NULL;
139     sys->gl.sys = NULL;
140     sys->embed = NULL;
141
142     /* Get the drawable object */
143     id container = var_CreateGetAddress(vd, "drawable-nsobject");
144     if (container)
145     {
146         vout_display_DeleteWindow(vd, NULL);
147     }
148     else
149     {
150         vout_window_cfg_t wnd_cfg;
151
152         memset (&wnd_cfg, 0, sizeof (wnd_cfg));
153         wnd_cfg.type = VOUT_WINDOW_TYPE_NSOBJECT;
154         wnd_cfg.x = var_InheritInteger (vd, "video-x");
155         wnd_cfg.y = var_InheritInteger (vd, "video-y");
156         wnd_cfg.width  = vd->cfg->display.width;
157         wnd_cfg.height = vd->cfg->display.height;
158
159         sys->embed = vout_display_NewWindow (vd, &wnd_cfg);
160         if (sys->embed)
161             container = sys->embed->handle.nsobject;
162
163         if (!container)
164         {
165             msg_Dbg(vd, "No drawable-nsobject nor vout_window_t found, passing over.");
166             goto error;
167         }
168     }
169
170     /* This will be released in Close(), on
171      * main thread, after we are done using it. */
172     sys->container = [container retain];
173
174     /* Get our main view*/
175     nsPool = [[NSAutoreleasePool alloc] init];
176
177     [VLCOpenGLVideoView performSelectorOnMainThread:@selector(getNewView:) withObject:[NSValue valueWithPointer:&sys->glView] waitUntilDone:YES];
178     if (!sys->glView)
179         goto error;
180
181     [sys->glView setVoutDisplay:vd];
182
183     /* We don't wait, that means that we'll have to be careful about releasing
184      * container.
185      * That's why we'll release on main thread in Close(). */
186     if ([(id)container respondsToSelector:@selector(addVoutSubview:)])
187         [(id)container performSelectorOnMainThread:@selector(addVoutSubview:) withObject:sys->glView waitUntilDone:NO];
188     else if ([container isKindOfClass:[NSView class]])
189     {
190         NSView *parentView = container;
191         [parentView performSelectorOnMainThread:@selector(addSubview:) withObject:sys->glView waitUntilDone:NO];
192         [sys->glView performSelectorOnMainThread:@selector(setFrameWithValue:) withObject:[NSValue valueWithRect:[parentView bounds]] waitUntilDone:NO];
193     }
194     else
195     {
196         msg_Err(vd, "Invalid drawable-nsobject object. drawable-nsobject must either be an NSView or comply to the @protocol VLCOpenGLVideoViewEmbedding.");
197         goto error;
198     }
199
200
201     [nsPool release];
202     nsPool = nil;
203
204     /* Initialize common OpenGL video display */
205     sys->gl.lock = OpenglLock;
206     sys->gl.unlock = OpenglUnlock;
207     sys->gl.swap = OpenglSwap;
208     sys->gl.getProcAddress = NULL;
209     sys->gl.sys = sys;
210
211         sys->vgl = vout_display_opengl_New(&vd->fmt, NULL, &sys->gl);
212         if (!sys->vgl)
213     {
214         sys->gl.sys = NULL;
215         goto error;
216     }
217
218     /* */
219     vout_display_info_t info = vd->info;
220     info.has_pictures_invalid = false;
221
222     /* Setup vout_display_t once everything is fine */
223     vd->info = info;
224
225     vd->pool = Pool;
226     vd->prepare = PictureRender;
227     vd->display = PictureDisplay;
228     vd->control = Control;
229
230     /* */
231     vout_display_SendEventFullscreen (vd, false);
232     vout_display_SendEventDisplaySize (vd, vd->source.i_visible_width, vd->source.i_visible_height, false);
233
234     return VLC_SUCCESS;
235
236 error:
237     [nsPool release];
238     Close(this);
239     return VLC_EGENERIC;
240 }
241
242 void Close(vlc_object_t *this)
243 {
244     vout_display_t *vd = (vout_display_t *)this;
245     vout_display_sys_t *sys = vd->sys;
246
247     [sys->glView setVoutDisplay:nil];
248
249     var_Destroy(vd, "drawable-nsobject");
250     if ([(id)sys->container respondsToSelector:@selector(removeVoutSubview:)])
251     {
252         /* This will retain sys->glView */
253         [(id)sys->container performSelectorOnMainThread:@selector(removeVoutSubview:) withObject:sys->glView waitUntilDone:NO];
254     }
255     /* release on main thread as explained in Open() */
256     [(id)sys->container performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO];
257     [sys->glView performSelectorOnMainThread:@selector(removeFromSuperview) withObject:nil waitUntilDone:NO];
258
259     [sys->glView release];
260
261     if (sys->gl.sys != NULL)
262         vout_display_opengl_Delete(sys->vgl);
263
264     if (sys->embed)
265         vout_display_DeleteWindow(vd, sys->embed);
266     free (sys);
267 }
268
269 /*****************************************************************************
270  * vout display callbacks
271  *****************************************************************************/
272
273 static picture_pool_t *Pool(vout_display_t *vd, unsigned requested_count)
274 {
275     vout_display_sys_t *sys = vd->sys;
276
277     if (!sys->pool)
278         sys->pool = vout_display_opengl_GetPool (sys->vgl, requested_count);
279     assert(sys->pool);
280     return sys->pool;
281 }
282
283 static void PictureRender(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
284 {
285
286     vout_display_sys_t *sys = vd->sys;
287
288     vout_display_opengl_Prepare( sys->vgl, pic, subpicture );
289 }
290
291 static void PictureDisplay(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
292 {
293     vout_display_sys_t *sys = vd->sys;
294     [sys->glView setVoutFlushing:YES];
295     vout_display_opengl_Display(sys->vgl, &vd->fmt );
296     [sys->glView setVoutFlushing:NO];
297     picture_Release (pic);
298     sys->has_first_frame = true;
299         (void)subpicture;
300 }
301
302 static int Control (vout_display_t *vd, int query, va_list ap)
303 {
304     vout_display_sys_t *sys = vd->sys;
305
306     switch (query)
307     {
308         case VOUT_DISPLAY_CHANGE_FULLSCREEN:
309         {
310             /* todo */
311             return VLC_EGENERIC;
312         }
313         case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
314         {
315             unsigned state = va_arg (ap, unsigned);
316             if( (state & VOUT_WINDOW_STATE_ABOVE) != 0)
317                 [[sys->glView window] setLevel: NSStatusWindowLevel];
318             else
319                 [[sys->glView window] setLevel: NSNormalWindowLevel];
320             return VLC_SUCCESS;
321         }
322         case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
323         {
324             [[sys->glView window] performSelectorOnMainThread:@selector(zoom:) withObject: nil waitUntilDone:NO];
325             return VLC_SUCCESS;
326         }
327         case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
328         case VOUT_DISPLAY_CHANGE_ZOOM:
329         case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
330         case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
331         {
332             NSPoint topleftbase;
333             NSPoint topleftscreen;
334             NSRect new_frame;
335             const vout_display_cfg_t *cfg;
336
337             id o_window = [sys->glView window];
338             if (!o_window)
339                 return VLC_SUCCESS; // this is okay, since the event will occur again when we have a window
340             NSRect windowFrame = [o_window frame];
341             NSRect glViewFrame = [sys->glView frame];
342             NSSize windowMinSize = [o_window minSize];
343
344             topleftbase.x = 0;
345             topleftbase.y = windowFrame.size.height;
346             topleftscreen = [o_window convertBaseToScreen: topleftbase];
347             cfg = (const vout_display_cfg_t*)va_arg (ap, const vout_display_cfg_t *);
348             int i_width = cfg->display.width;
349             int i_height = cfg->display.height;
350
351             /* Calculate the window's new size, if it is larger than our minimal size */
352             if (i_width < windowMinSize.width)
353                 i_width = windowMinSize.width;
354             if (i_height < windowMinSize.height)
355                 i_height = windowMinSize.height;
356
357             if( i_height != glViewFrame.size.height || i_width != glViewFrame.size.width )
358             {
359                 new_frame.size.width = windowFrame.size.width - glViewFrame.size.width + i_width;
360                 new_frame.size.height = windowFrame.size.height - glViewFrame.size.height + i_height;
361
362                 new_frame.origin.x = topleftscreen.x;
363                 new_frame.origin.y = topleftscreen.y - new_frame.size.height;
364
365                 [sys->glView performSelectorOnMainThread:@selector(setWindowFrameWithValue:) withObject:[NSValue valueWithRect:new_frame] waitUntilDone:NO];
366             }
367             return VLC_SUCCESS;
368         }
369
370         case VOUT_DISPLAY_HIDE_MOUSE:
371         {
372             [NSCursor setHiddenUntilMouseMoves: YES];
373             return VLC_SUCCESS;
374         }
375
376         case VOUT_DISPLAY_GET_OPENGL:
377         {
378             vlc_gl_t **gl = va_arg (ap, vlc_gl_t **);
379             *gl = &sys->gl;
380             return VLC_SUCCESS;
381         }
382
383         case VOUT_DISPLAY_RESET_PICTURES:
384             assert (0);
385         default:
386             msg_Err (vd, "Unknown request in Mac OS X vout display");
387             return VLC_EGENERIC;
388     }
389 }
390
391 /*****************************************************************************
392  * vout opengl callbacks
393  *****************************************************************************/
394 static int OpenglLock(vlc_gl_t *gl)
395 {
396     vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
397     NSOpenGLContext *context = [sys->glView openGLContext];
398     CGLError err = CGLLockContext([context CGLContextObj]);
399     if (kCGLNoError == err)
400     {
401         [context makeCurrentContext];
402         return 0;
403     }
404     return 1;
405 }
406
407 static void OpenglUnlock(vlc_gl_t *gl)
408 {
409     vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
410     CGLUnlockContext([[sys->glView openGLContext] CGLContextObj]);
411 }
412
413 static void OpenglSwap(vlc_gl_t *gl)
414 {
415     vout_display_sys_t *sys = (vout_display_sys_t *)gl->sys;
416     [[sys->glView openGLContext] flushBuffer];
417 }
418
419 /*****************************************************************************
420  * Our NSView object
421  *****************************************************************************/
422 @implementation VLCOpenGLVideoView
423
424 #define VLCAssertMainThread() assert([[NSThread currentThread] isMainThread])
425
426 + (void)getNewView:(NSValue *)value
427 {
428     id *ret = [value pointerValue];
429     *ret = [[self alloc] init];
430 }
431
432 /**
433  * Gets called by the Open() method.
434  */
435 - (id)init
436 {
437     VLCAssertMainThread();
438
439     /* Warning - this may be called on non main thread */
440
441     NSOpenGLPixelFormatAttribute attribs[] =
442     {
443         NSOpenGLPFADoubleBuffer,
444         NSOpenGLPFAAccelerated,
445         NSOpenGLPFANoRecovery,
446         NSOpenGLPFAColorSize, 24,
447         NSOpenGLPFAAlphaSize, 8,
448         NSOpenGLPFADepthSize, 24,
449         NSOpenGLPFAWindow,
450         0
451     };
452
453     NSOpenGLPixelFormat *fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs];
454
455     if (!fmt)
456         return nil;
457
458     self = [super initWithFrame:NSMakeRect(0,0,10,10) pixelFormat:fmt];
459     [fmt release];
460
461     if (!self)
462         return nil;
463
464     /* Swap buffers only during the vertical retrace of the monitor.
465      http://developer.apple.com/documentation/GraphicsImaging/
466      Conceptual/OpenGL/chap5/chapter_5_section_44.html */
467     GLint params[] = { 1 };
468     CGLSetParameter([[self openGLContext] CGLContextObj], kCGLCPSwapInterval, params);
469
470     [self setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
471     return self;
472 }
473
474 /**
475  * Gets called by the Open() method.
476  */
477 - (void)setFrameWithValue:(NSValue *)value
478 {
479     [self setFrame:[value rectValue]];
480 }
481
482 /**
483  * Gets called by Control() to make sure that we're performing on the main thread
484  */
485 - (void)setWindowFrameWithValue:(NSValue *)value
486 {
487     if (!(NSAppKitVersionNumber >= 1115.2 && [NSApp currentSystemPresentationOptions] == NSApplicationPresentationFullScreen))
488     {
489         NSRect frame = [value rectValue];
490         if (frame.origin.x <= 0.0 && frame.origin.y <= 0.0)
491             [[self window] center];
492         [[self window] setFrame:frame display:YES animate: YES];
493     }
494 }
495
496 /**
497  * Gets called by the Close and Open methods.
498  * (Non main thread).
499  */
500 - (void)setVoutDisplay:(vout_display_t *)aVd
501 {
502     @synchronized(self) {
503         vd = aVd;
504     }
505 }
506
507
508 /**
509  * Gets called when the vout will aquire the lock and flush.
510  * (Non main thread).
511  */
512 - (void)setVoutFlushing:(BOOL)flushing
513 {
514     if (!flushing)
515         return;
516     @synchronized(self) {
517         _hasPendingReshape = NO;
518     }
519 }
520
521 /**
522  * Can -drawRect skip rendering?.
523  */
524 - (BOOL)canSkipRendering
525 {
526     VLCAssertMainThread();
527
528     @synchronized(self) {
529         BOOL hasFirstFrame = vd && vd->sys->has_first_frame;
530         return !_hasPendingReshape && hasFirstFrame;
531     }
532 }
533
534
535 /**
536  * Local method that locks the gl context.
537  */
538 - (BOOL)lockgl
539 {
540     VLCAssertMainThread();
541     NSOpenGLContext *context = [self openGLContext];
542     CGLError err = CGLLockContext([context CGLContextObj]);
543     if (err == kCGLNoError)
544         [context makeCurrentContext];
545     return err == kCGLNoError;
546 }
547
548 /**
549  * Local method that unlocks the gl context.
550  */
551 - (void)unlockgl
552 {
553     VLCAssertMainThread();
554     CGLUnlockContext([[self openGLContext] CGLContextObj]);
555 }
556
557 /**
558  * Local method that force a rendering of a frame.
559  * This will get called if Cocoa forces us to redraw (via -drawRect).
560  */
561 - (void)render
562 {
563     VLCAssertMainThread();
564
565     // We may have taken some times to take the opengl Lock.
566     // Check here to see if we can just skip the frame as well.
567     if ([self canSkipRendering])
568         return;
569
570     BOOL hasFirstFrame;
571     @synchronized(self) { // vd can be accessed from multiple threads
572         hasFirstFrame = vd && vd->sys->has_first_frame;
573     }
574
575     if (hasFirstFrame) {
576         // This will lock gl.
577         vout_display_opengl_Display( vd->sys->vgl, &vd->source );
578     }
579     else
580         glClear(GL_COLOR_BUFFER_BIT);
581 }
582
583 /**
584  * Method called by Cocoa when the view is resized.
585  */
586 - (void)reshape
587 {
588     VLCAssertMainThread();
589
590     NSRect bounds = [self bounds];
591
592     CGFloat height = bounds.size.height;
593     CGFloat width = bounds.size.width;
594
595     GLint x = width, y = height;
596
597     @synchronized(self) {
598         if (vd) {
599             CGFloat videoHeight = vd->source.i_visible_height;
600             CGFloat videoWidth = vd->source.i_visible_width;
601
602             GLint sarNum = vd->source.i_sar_num;
603             GLint sarDen = vd->source.i_sar_den;
604
605             if (height * videoWidth * sarNum < width * videoHeight * sarDen)
606             {
607                 x = (height * videoWidth * sarNum) / (videoHeight * sarDen);
608                 y = height;
609             }
610             else
611             {
612                 x = width;
613                 y = (width * videoHeight * sarDen) / (videoWidth * sarNum);
614             }
615         }
616     }
617
618     if ([self lockgl]) {
619         glViewport((width - x) / 2, (height - y) / 2, x, y);
620
621         @synchronized(self) {
622             // This may be cleared before -drawRect is being called,
623             // in this case we'll skip the rendering.
624             // This will save us for rendering two frames (or more) for nothing
625             // (one by the vout, one (or more) by drawRect)
626             _hasPendingReshape = YES;
627         }
628
629         [self unlockgl];
630
631         [super reshape];
632     }
633 }
634
635 /**
636  * Method called by Cocoa when the view is resized or the location has changed.
637  * We just need to make sure we are locking here.
638  */
639 - (void)update
640 {
641     VLCAssertMainThread();
642     BOOL success = [self lockgl];
643     if (!success)
644         return;
645
646     [super update];
647
648     [self unlockgl];
649 }
650
651 /**
652  * Method called by Cocoa to force redraw.
653  */
654 - (void)drawRect:(NSRect) rect
655 {
656     VLCAssertMainThread();
657
658     if ([self canSkipRendering])
659         return;
660
661     BOOL success = [self lockgl];
662     if (!success)
663         return;
664
665     [self render];
666
667     [self unlockgl];
668 }
669
670 - (void)renewGState
671 {
672     NSWindow *window = [self window];
673
674     // Remove flashes with splitter view.
675         if ([window respondsToSelector:@selector(disableScreenUpdatesUntilFlush)])
676                 [window disableScreenUpdatesUntilFlush];
677
678     [super renewGState];
679 }
680
681 - (BOOL)mouseDownCanMoveWindow
682 {
683     return YES;
684 }
685
686 - (BOOL)isOpaque
687 {
688     return YES;
689 }
690 @end