]> git.sesse.net Git - vlc/blob - modules/gui/macosx/voutgl.m
c4830f42e3efcdcc85ff8f626f8d297d7e71b7e0
[vlc] / modules / gui / macosx / voutgl.m
1 /*****************************************************************************
2  * voutgl.m: MacOS X OpenGL provider
3  *****************************************************************************
4  * Copyright (C) 2001-2004 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  *
14  * This program is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program; if not, write to the Free Software
26  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
27  *****************************************************************************/
28
29 /*****************************************************************************
30  * Preamble
31  *****************************************************************************/
32 #include <errno.h>                                                 /* ENOMEM */
33 #include <stdlib.h>                                                /* free() */
34 #include <string.h>                                            /* strerror() */
35
36 #include <vlc_keys.h>
37
38 #include "intf.h"
39 #include "vout.h"
40
41 #include <OpenGL/OpenGL.h>
42 #include <OpenGL/gl.h>
43
44 #include <AGL/agl.h>
45
46 /*****************************************************************************
47  * VLCGLView interface
48  *****************************************************************************/
49 @interface VLCGLView : NSOpenGLView <VLCVoutViewResetting>
50 {
51     vout_thread_t * p_vout;
52 }
53
54 + (void)resetVout: (vout_thread_t *)p_vout;
55 - (id) initWithVout: (vout_thread_t *) p_vout;
56 @end
57
58 struct vout_sys_t
59 {
60     NSAutoreleasePool * o_pool;
61     VLCGLView         * o_glview;
62     VLCVoutView       * o_vout_view;
63     vlc_bool_t          b_saved_frame;
64     NSRect              s_frame;
65     vlc_bool_t          b_got_frame;
66     vlc_mutex_t         lock;
67     /* Mozilla plugin-related variables */
68     vlc_bool_t          b_embedded;
69     AGLContext          agl_ctx;
70     AGLDrawable         agl_drawable;
71     int                 i_offx, i_offy;
72     int                 i_width, i_height;
73 };
74
75 /*****************************************************************************
76  * Local prototypes
77  *****************************************************************************/
78
79 static int  Init   ( vout_thread_t * p_vout );
80 static void End    ( vout_thread_t * p_vout );
81 static int  Manage ( vout_thread_t * p_vout );
82 static int  Control( vout_thread_t *, int, va_list );
83 static void Swap   ( vout_thread_t * p_vout );
84 static int  Lock   ( vout_thread_t * p_vout );
85 static void Unlock ( vout_thread_t * p_vout );
86
87 static int  aglInit   ( vout_thread_t * p_vout );
88 static void aglEnd    ( vout_thread_t * p_vout );
89 static int  aglManage ( vout_thread_t * p_vout );
90 static int  aglControl( vout_thread_t *, int, va_list );
91 static void aglSwap   ( vout_thread_t * p_vout );
92
93 int E_(OpenVideoGL)  ( vlc_object_t * p_this )
94 {
95     vout_thread_t * p_vout = (vout_thread_t *) p_this;
96     vlc_value_t value_drawable;
97
98     if( !CGDisplayUsesOpenGLAcceleration( kCGDirectMainDisplay ) )
99     {
100         msg_Warn( p_vout, "no OpenGL hardware acceleration found. "
101                           "Video display will be slow" );
102         return( 1 );
103     }
104     msg_Dbg( p_vout, "display is Quartz Extreme accelerated" );
105
106     p_vout->p_sys = malloc( sizeof( vout_sys_t ) );
107     if( p_vout->p_sys == NULL )
108     {
109         msg_Err( p_vout, "out of memory" );
110         return( 1 );
111     }
112
113     memset( p_vout->p_sys, 0, sizeof( vout_sys_t ) );
114
115     vlc_mutex_init( p_vout, &p_vout->p_sys->lock );
116
117     var_Get( p_vout->p_libvlc, "drawable", &value_drawable );
118     if( value_drawable.i_int != 0 )
119     {
120         static const GLint ATTRIBUTES[] = { 
121             AGL_WINDOW,
122             AGL_RGBA,
123             AGL_NO_RECOVERY,
124             AGL_ACCELERATED,
125             AGL_DOUBLEBUFFER,
126             AGL_RED_SIZE,   8,
127             AGL_GREEN_SIZE, 8,
128             AGL_BLUE_SIZE,  8,
129             AGL_ALPHA_SIZE, 8,
130             AGL_DEPTH_SIZE, 24,
131             AGL_NONE };
132
133         AGLPixelFormat pixFormat;
134
135         p_vout->p_sys->b_embedded = VLC_TRUE;
136
137         pixFormat = aglChoosePixelFormat(NULL, 0, ATTRIBUTES);
138         if( NULL == pixFormat )
139         {
140             msg_Err( p_vout, "no screen renderer available for required attributes." );
141             return VLC_EGENERIC;
142         }
143         
144         p_vout->p_sys->agl_ctx = aglCreateContext(pixFormat, NULL);
145         aglDestroyPixelFormat(pixFormat);
146         if( NULL == p_vout->p_sys->agl_ctx )
147         {
148             msg_Err( p_vout, "cannot create AGL context." );
149             return VLC_EGENERIC;
150         }
151         else {
152             // tell opengl not to sync buffer swap with vertical retrace (too inefficient)
153             GLint param = 0;
154             aglSetInteger(p_vout->p_sys->agl_ctx, AGL_SWAP_INTERVAL, &param);
155             aglEnable(p_vout->p_sys->agl_ctx, AGL_SWAP_INTERVAL);
156         }
157
158         p_vout->pf_init             = aglInit;
159         p_vout->pf_end              = aglEnd;
160         p_vout->pf_manage           = aglManage;
161         p_vout->pf_control          = aglControl;
162         p_vout->pf_swap             = aglSwap;
163         p_vout->pf_lock             = Lock;
164         p_vout->pf_unlock           = Unlock;
165     }
166     else
167     {
168         p_vout->p_sys->b_embedded = VLC_FALSE;
169
170         p_vout->p_sys->o_pool = [[NSAutoreleasePool alloc] init];
171
172         /* Create the GL view */
173         p_vout->p_sys->o_glview = [[VLCGLView alloc] initWithVout: p_vout];
174         [p_vout->p_sys->o_glview autorelease];
175
176         /* Spawn the window */
177
178         if( !(p_vout->p_sys->o_vout_view = [VLCVoutView getVoutView: p_vout
179                         subView: p_vout->p_sys->o_glview frame: nil]) )
180         {
181             return VLC_EGENERIC;
182         }
183         p_vout->pf_init   = Init;
184         p_vout->pf_end    = End;
185         p_vout->pf_manage = Manage;
186         p_vout->pf_control= Control;
187         p_vout->pf_swap   = Swap;
188         p_vout->pf_lock   = Lock;
189         p_vout->pf_unlock = Unlock;
190     }
191     p_vout->p_sys->b_got_frame = VLC_FALSE;
192
193     return VLC_SUCCESS;
194 }
195
196 void E_(CloseVideoGL) ( vlc_object_t * p_this )
197 {
198     vout_thread_t * p_vout = (vout_thread_t *) p_this;
199     if( p_vout->p_sys->b_embedded )
200     {
201         aglDestroyContext(p_vout->p_sys->agl_ctx);
202     }
203     else if(!VLCIntf->b_die)
204     {
205         NSAutoreleasePool *o_pool = [[NSAutoreleasePool alloc] init];
206
207         /* Close the window */
208         [p_vout->p_sys->o_vout_view closeVout];
209
210         [o_pool release];
211     }
212     /* Clean up */
213     vlc_mutex_destroy( &p_vout->p_sys->lock );
214     free( p_vout->p_sys );
215 }
216
217 static int Init( vout_thread_t * p_vout )
218 {
219     [[p_vout->p_sys->o_glview openGLContext] makeCurrentContext];
220     return VLC_SUCCESS;
221 }
222
223 static void End( vout_thread_t * p_vout )
224 {
225     [[p_vout->p_sys->o_glview openGLContext] makeCurrentContext];
226 }
227
228 static int Manage( vout_thread_t * p_vout )
229 {
230     if( p_vout->i_changes & VOUT_ASPECT_CHANGE )
231     {
232         [p_vout->p_sys->o_glview reshape];
233         p_vout->i_changes &= ~VOUT_ASPECT_CHANGE;
234     }
235     if( p_vout->i_changes & VOUT_CROP_CHANGE )
236     {
237         [p_vout->p_sys->o_glview reshape];
238         p_vout->i_changes &= ~VOUT_CROP_CHANGE;
239     }
240
241     if( p_vout->i_changes & VOUT_FULLSCREEN_CHANGE )
242     {
243         NSAutoreleasePool *o_pool = [[NSAutoreleasePool alloc] init];
244
245         p_vout->b_fullscreen = !p_vout->b_fullscreen;
246
247         if( p_vout->b_fullscreen )
248             [p_vout->p_sys->o_vout_view enterFullscreen];
249         else
250             [p_vout->p_sys->o_vout_view leaveFullscreen];
251
252         [o_pool release];
253
254         p_vout->i_changes &= ~VOUT_FULLSCREEN_CHANGE;
255     }
256
257     [p_vout->p_sys->o_vout_view manage];
258     return VLC_SUCCESS;
259 }
260
261 /*****************************************************************************
262  * Control: control facility for the vout
263  *****************************************************************************/
264 static int Control( vout_thread_t *p_vout, int i_query, va_list args )
265 {
266     vlc_bool_t b_arg;
267
268     switch( i_query )
269     {
270         case VOUT_SET_STAY_ON_TOP:
271             b_arg = va_arg( args, vlc_bool_t );
272             [p_vout->p_sys->o_vout_view setOnTop: b_arg];
273             return VLC_SUCCESS;
274
275         case VOUT_CLOSE:
276         case VOUT_REPARENT:
277         default:
278             return vout_vaControlDefault( p_vout, i_query, args );
279     }
280 }
281
282 static void Swap( vout_thread_t * p_vout )
283 {
284     p_vout->p_sys->b_got_frame = VLC_TRUE;
285     [[p_vout->p_sys->o_glview openGLContext] makeCurrentContext];
286     glFlush();
287 }
288
289 static int Lock( vout_thread_t * p_vout )
290 {
291     vlc_mutex_lock( &p_vout->p_sys->lock );
292     CGLLockContext( (CGLContextObj)p_vout->p_sys->agl_ctx );
293     return 0;
294 }
295
296 static void Unlock( vout_thread_t * p_vout )
297 {
298     CGLUnlockContext( (CGLContextObj)p_vout->p_sys->agl_ctx );
299     vlc_mutex_unlock( &p_vout->p_sys->lock );
300 }
301
302 /*****************************************************************************
303  * VLCGLView implementation
304  *****************************************************************************/
305 @implementation VLCGLView
306
307 /* This function will reset the o_vout_view. It's useful to go fullscreen. */
308 + (void)resetVout: (vout_thread_t *)p_vout
309 {
310     if( p_vout->b_fullscreen )
311     {
312         /* Save window size and position */
313         p_vout->p_sys->s_frame.size =
314             [p_vout->p_sys->o_vout_view frame].size;
315         p_vout->p_sys->s_frame.origin =
316             [[p_vout->p_sys->o_vout_view getWindow ]frame].origin;
317         p_vout->p_sys->b_saved_frame = VLC_TRUE;
318     }
319
320     [p_vout->p_sys->o_vout_view closeVout];
321
322 #define o_glview p_vout->p_sys->o_glview
323     o_glview = [[VLCGLView alloc] initWithVout: p_vout];
324     [o_glview autorelease];
325     
326     if( p_vout->p_sys->b_saved_frame )
327     {
328         p_vout->p_sys->o_vout_view = [VLCVoutView getVoutView: p_vout
329                                                       subView: o_glview
330                                                         frame: &p_vout->p_sys->s_frame];
331     }
332     else
333     {
334         p_vout->p_sys->o_vout_view = [VLCVoutView getVoutView: p_vout
335                                                       subView: o_glview frame: nil];
336         
337     }
338     
339     [[o_glview openGLContext] makeCurrentContext];
340 #undef o_glview
341 }
342
343 - (id) initWithVout: (vout_thread_t *) vout
344 {
345     p_vout = vout;
346
347     NSOpenGLPixelFormatAttribute attribs[] =
348     {
349         NSOpenGLPFAAccelerated,
350         NSOpenGLPFANoRecovery,
351         NSOpenGLPFAColorSize, 24,
352         NSOpenGLPFAAlphaSize, 8,
353         NSOpenGLPFADepthSize, 24,
354         NSOpenGLPFAWindow,
355         0
356     };
357
358     NSOpenGLPixelFormat * fmt = [[NSOpenGLPixelFormat alloc]
359         initWithAttributes: attribs];
360
361     if( !fmt )
362     {
363         msg_Warn( p_vout, "could not create OpenGL video output" );
364         return nil;
365     }
366
367     self = [super initWithFrame: NSMakeRect(0,0,10,10) pixelFormat: fmt];
368     [fmt release];
369
370     [[self openGLContext] makeCurrentContext];
371     [[self openGLContext] update];
372
373     /* Swap buffers only during the vertical retrace of the monitor.
374        http://developer.apple.com/documentation/GraphicsImaging/
375        Conceptual/OpenGL/chap5/chapter_5_section_44.html */
376     long params[] = { 1 };
377     CGLSetParameter( CGLGetCurrentContext(), kCGLCPSwapInterval,
378                      params );
379     return self;
380 }
381
382 - (void) reshape
383 {
384     int x, y;
385     vlc_value_t val;
386
387     Lock( p_vout );
388     NSRect bounds = [self bounds];
389
390     [[self openGLContext] makeCurrentContext];
391
392     var_Get( p_vout, "macosx-stretch", &val );
393     if( val.b_bool )
394     {
395         x = bounds.size.width;
396         y = bounds.size.height;
397     }
398     else if( bounds.size.height * p_vout->fmt_in.i_visible_width *
399              p_vout->fmt_in.i_sar_num <
400              bounds.size.width * p_vout->fmt_in.i_visible_height *
401              p_vout->fmt_in.i_sar_den )
402     {
403         x = ( bounds.size.height * p_vout->fmt_in.i_visible_width *
404               p_vout->fmt_in.i_sar_num ) /
405             ( p_vout->fmt_in.i_visible_height * p_vout->fmt_in.i_sar_den);
406
407         y = bounds.size.height;
408     }
409     else
410     {
411         x = bounds.size.width;
412         y = ( bounds.size.width * p_vout->fmt_in.i_visible_height *
413               p_vout->fmt_in.i_sar_den) /
414             ( p_vout->fmt_in.i_visible_width * p_vout->fmt_in.i_sar_num  );
415     }
416
417     glViewport( ( bounds.size.width - x ) / 2,
418                 ( bounds.size.height - y ) / 2, x, y );
419
420     if( p_vout->p_sys->b_got_frame )
421     {
422         /* Ask the opengl module to redraw */
423         vout_thread_t * p_parent;
424         p_parent = (vout_thread_t *) p_vout->p_parent;
425         Unlock( p_vout );
426         if( p_parent && p_parent->pf_display )
427         {
428             p_parent->pf_display( p_parent, NULL );
429         }
430     }
431     else
432     {
433         glClear( GL_COLOR_BUFFER_BIT );
434         Unlock( p_vout );
435     }
436     [super reshape];
437 }
438
439 - (void) update
440 {
441     Lock( p_vout );
442     [super update];
443     Unlock( p_vout );
444 }
445
446 - (void) drawRect: (NSRect) rect
447 {
448     Lock( p_vout );
449     [[self openGLContext] makeCurrentContext];
450     /* FIXME: we should use CGLFlushDrawable instead, as glFlush is pretty slow */
451     glFlush();
452     [super drawRect:rect];
453     Unlock( p_vout );
454 }
455
456 @end
457
458 /*****************************************************************************
459  * embedded AGL context implementation
460  *****************************************************************************/
461
462 static void aglSetViewport( vout_thread_t *p_vout, Rect viewBounds, Rect clipBounds );
463 static void aglReshape( vout_thread_t * p_vout );
464
465 static int aglInit( vout_thread_t * p_vout )
466 {
467     vlc_value_t val;
468
469     Rect viewBounds;    
470     Rect clipBounds;
471     
472     var_Get( p_vout->p_libvlc, "drawable", &val );
473     p_vout->p_sys->agl_drawable = (AGLDrawable)val.i_int;
474     aglSetDrawable(p_vout->p_sys->agl_ctx, p_vout->p_sys->agl_drawable);
475
476     var_Get( p_vout->p_libvlc, "drawable-view-top", &val );
477     viewBounds.top = val.i_int;
478     var_Get( p_vout->p_libvlc, "drawable-view-left", &val );
479     viewBounds.left = val.i_int;
480     var_Get( p_vout->p_libvlc, "drawable-view-bottom", &val );
481     viewBounds.bottom = val.i_int;
482     var_Get( p_vout->p_libvlc, "drawable-view-right", &val );
483     viewBounds.right = val.i_int;
484     var_Get( p_vout->p_libvlc, "drawable-clip-top", &val );
485     clipBounds.top = val.i_int;
486     var_Get( p_vout->p_libvlc, "drawable-clip-left", &val );
487     clipBounds.left = val.i_int;
488     var_Get( p_vout->p_libvlc, "drawable-clip-bottom", &val );
489     clipBounds.bottom = val.i_int;
490     var_Get( p_vout->p_libvlc, "drawable-clip-right", &val );
491     clipBounds.right = val.i_int;
492
493     aglSetViewport(p_vout, viewBounds, clipBounds);
494
495     aglSetCurrentContext(p_vout->p_sys->agl_ctx);
496     return VLC_SUCCESS;
497 }
498
499 static void aglEnd( vout_thread_t * p_vout )
500 {
501     aglSetCurrentContext(NULL);
502 }
503
504 static void aglReshape( vout_thread_t * p_vout )
505 {
506     unsigned int x, y;
507     unsigned int i_height = p_vout->p_sys->i_height;
508     unsigned int i_width  = p_vout->p_sys->i_width;
509
510     Lock( p_vout );
511
512     vout_PlacePicture(p_vout, i_width, i_height, &x, &y, &i_width, &i_height); 
513
514     aglSetCurrentContext(p_vout->p_sys->agl_ctx);
515
516     glViewport( p_vout->p_sys->i_offx + x, p_vout->p_sys->i_offy + y, i_width, i_height );
517
518     if( p_vout->p_sys->b_got_frame )
519     {
520         /* Ask the opengl module to redraw */
521         vout_thread_t * p_parent;
522         p_parent = (vout_thread_t *) p_vout->p_parent;
523         Unlock( p_vout );
524         if( p_parent && p_parent->pf_display )
525         {
526             p_parent->pf_display( p_parent, NULL );
527         }
528     }
529     else
530     {
531         glClear( GL_COLOR_BUFFER_BIT );
532         Unlock( p_vout );
533     }
534 }
535
536 static int aglManage( vout_thread_t * p_vout )
537 {
538     if( p_vout->i_changes & VOUT_ASPECT_CHANGE )
539     {
540         aglReshape(p_vout);
541         p_vout->i_changes &= ~VOUT_ASPECT_CHANGE;
542     }
543     if( p_vout->i_changes & VOUT_CROP_CHANGE )
544     {
545         aglReshape(p_vout);
546         p_vout->i_changes &= ~VOUT_CROP_CHANGE;
547     }
548     return VLC_SUCCESS;
549 }
550
551 static int aglControl( vout_thread_t *p_vout, int i_query, va_list args )
552 {
553     switch( i_query )
554     {
555         case VOUT_SET_VIEWPORT:
556         {
557             Rect viewBounds, clipBounds;
558             viewBounds.top = va_arg( args, int);
559             viewBounds.left = va_arg( args, int);
560             viewBounds.bottom = va_arg( args, int);
561             viewBounds.right = va_arg( args, int);
562             clipBounds.top = va_arg( args, int);
563             clipBounds.left = va_arg( args, int);
564             clipBounds.bottom = va_arg( args, int);
565             clipBounds.right = va_arg( args, int);
566             aglSetViewport(p_vout, viewBounds, clipBounds);
567             return VLC_SUCCESS;
568         }
569
570         case VOUT_REPARENT:
571         {
572             AGLDrawable drawable = (AGLDrawable)va_arg( args, int);
573             if( drawable != p_vout->p_sys->agl_drawable )
574             {
575                 p_vout->p_sys->agl_drawable = drawable;
576                 aglSetDrawable(p_vout->p_sys->agl_ctx, drawable);
577             }
578             return VLC_SUCCESS;
579         }
580
581         default:
582             return vout_vaControlDefault( p_vout, i_query, args );
583     }
584 }
585
586 static void aglSwap( vout_thread_t * p_vout )
587 {
588     p_vout->p_sys->b_got_frame = VLC_TRUE;
589     aglSwapBuffers(p_vout->p_sys->agl_ctx);
590 }
591
592 static void aglSetViewport( vout_thread_t *p_vout, Rect viewBounds, Rect clipBounds )
593 {
594     // mozilla plugin provides coordinates based on port bounds
595     // however AGL coordinates are based on window structure region
596     // and are vertically flipped
597     GLint rect[4];
598     CGrafPtr port = (CGrafPtr)p_vout->p_sys->agl_drawable;
599     Rect winBounds, clientBounds;
600
601     GetWindowBounds(GetWindowFromPort(port),
602         kWindowStructureRgn, &winBounds);
603     GetWindowBounds(GetWindowFromPort(port),
604         kWindowContentRgn, &clientBounds);
605
606     /* update video clipping bounds in drawable */
607     rect[0] = (clientBounds.left-winBounds.left)
608             + clipBounds.left;                  // from window left edge
609     rect[1] = (winBounds.bottom-winBounds.top)
610             - (clientBounds.top-winBounds.top)
611             - clipBounds.bottom;                // from window bottom edge
612     rect[2] = clipBounds.right-clipBounds.left; // width
613     rect[3] = clipBounds.bottom-clipBounds.top; // height
614     aglSetInteger(p_vout->p_sys->agl_ctx, AGL_BUFFER_RECT, rect);
615     aglEnable(p_vout->p_sys->agl_ctx, AGL_BUFFER_RECT);
616
617     /* update video internal bounds in drawable */
618     p_vout->p_sys->i_width  = viewBounds.right-viewBounds.left;
619     p_vout->p_sys->i_height = viewBounds.bottom-viewBounds.top;
620     p_vout->p_sys->i_offx   = -clipBounds.left - viewBounds.left;
621     p_vout->p_sys->i_offy   = clipBounds.bottom + viewBounds.top
622                             - p_vout->p_sys->i_height; 
623
624     aglUpdateContext(p_vout->p_sys->agl_ctx);
625     aglReshape( p_vout );
626 }
627