1 /*****************************************************************************
2 * opengl.c: OpenGL video output
3 *****************************************************************************
4 * Copyright (C) 2004 VideoLAN
7 * Authors: Cyril Deguet <asmax@videolan.org>
8 * Gildas Bazin <gbazin@videolan.org>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
23 *****************************************************************************/
25 /*****************************************************************************
27 *****************************************************************************/
28 #include <errno.h> /* ENOMEM */
29 #include <stdlib.h> /* malloc(), free() */
36 #include <OpenGL/gl.h>
37 #include <OpenGL/glext.h>
39 /* On OS X, use GL_TEXTURE_RECTANGLE_EXT instead of GL_TEXTURE_2D.
40 This allows sizes which are not powers of 2 */
41 #define VLCGL_TARGET GL_TEXTURE_RECTANGLE_EXT
43 /* OS X OpenGL supports YUV. Hehe. */
44 #define VLCGL_FORMAT GL_YCBCR_422_APPLE
45 #define VLCGL_TYPE GL_UNSIGNED_SHORT_8_8_APPLE
49 #define VLCGL_TARGET GL_TEXTURE_2D
52 #ifndef GL_UNSIGNED_SHORT_5_6_5
53 #define GL_UNSIGNED_SHORT_5_6_5 0x8363
55 //#define VLCGL_RGB_FORMAT GL_RGB
56 //#define VLCGL_RGB_TYPE GL_UNSIGNED_SHORT_5_6_5
59 //#define VLCGL_RGB_FORMAT GL_RGB
60 //#define VLCGL_RGB_TYPE GL_UNSIGNED_BYTE
63 #define VLCGL_RGB_FORMAT GL_RGBA
64 #define VLCGL_RGB_TYPE GL_UNSIGNED_BYTE
66 /* Use RGB on Win32/GLX */
67 #define VLCGL_FORMAT VLCGL_RGB_FORMAT
68 #define VLCGL_TYPE VLCGL_RGB_TYPE
72 #define OPENGL_EFFECT_NONE 1
73 #define OPENGL_EFFECT_CUBE 2
74 #define OPENGL_EFFECT_TRANSPARENT_CUBE 4
76 /*****************************************************************************
78 *****************************************************************************/
79 static int CreateVout ( vlc_object_t * );
80 static void DestroyVout ( vlc_object_t * );
81 static int Init ( vout_thread_t * );
82 static void End ( vout_thread_t * );
83 static int Manage ( vout_thread_t * );
84 static void Render ( vout_thread_t *, picture_t * );
85 static void DisplayVideo ( vout_thread_t *, picture_t * );
86 static int Control ( vout_thread_t *, int, va_list );
88 static inline int GetAlignedSize( int );
90 static int InitTextures( vout_thread_t * );
91 static int SendEvents( vlc_object_t *, char const *,
92 vlc_value_t, vlc_value_t, void * );
94 /*****************************************************************************
96 *****************************************************************************/
97 #define EFFECT_TEXT N_("Select effect")
98 #define EFFECT_LONGTEXT N_( \
99 "Allows you to select different visual effects.")
102 set_description( _("OpenGL video output") );
104 set_capability( "video output", 200 );
106 set_capability( "video output", 20 );
108 add_shortcut( "opengl" );
109 set_callbacks( CreateVout, DestroyVout );
111 add_string( "opengl-effect", "none", NULL, EFFECT_TEXT,
112 EFFECT_LONGTEXT, VLC_TRUE );
115 /*****************************************************************************
116 * vout_sys_t: video output method descriptor
117 *****************************************************************************
118 * This structure is part of the video output thread descriptor.
119 * It describes the OpenGL specific properties of the output thread.
120 *****************************************************************************/
123 vout_thread_t *p_vout;
125 uint8_t *pp_buffer[2];
129 GLuint p_textures[2];
134 /*****************************************************************************
135 * CreateVout: This function allocates and initializes the OpenGL vout method.
136 *****************************************************************************/
137 static int CreateVout( vlc_object_t *p_this )
139 vout_thread_t *p_vout = (vout_thread_t *)p_this;
142 /* Allocate structure */
143 p_vout->p_sys = p_sys = malloc( sizeof( vout_sys_t ) );
146 msg_Err( p_vout, "out of memory" );
150 var_Create( p_vout, "opengl-effect", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
153 p_sys->i_tex_width = p_vout->render.i_width;
154 p_sys->i_tex_height = p_vout->render.i_height;
156 /* A texture must have a size aligned on a power of 2 */
157 p_sys->i_tex_width = GetAlignedSize( p_vout->render.i_width );
158 p_sys->i_tex_height = GetAlignedSize( p_vout->render.i_height );
161 msg_Dbg( p_vout, "Texture size: %dx%d", p_sys->i_tex_width,
162 p_sys->i_tex_height );
166 (vout_thread_t *)vlc_object_create( p_this, VLC_OBJECT_OPENGL );
167 if( p_sys->p_vout == NULL )
169 msg_Err( p_vout, "out of memory" );
172 vlc_object_attach( p_sys->p_vout, p_this );
174 p_sys->p_vout->i_window_width = p_vout->i_window_width;
175 p_sys->p_vout->i_window_height = p_vout->i_window_height;
176 p_sys->p_vout->b_fullscreen = p_vout->b_fullscreen;
177 p_sys->p_vout->render.i_width = p_vout->render.i_width;
178 p_sys->p_vout->render.i_height = p_vout->render.i_height;
179 p_sys->p_vout->render.i_aspect = p_vout->render.i_aspect;
180 p_sys->p_vout->b_scale = p_vout->b_scale;
181 p_sys->p_vout->i_alignment = p_vout->i_alignment;
183 p_sys->p_vout->p_module =
184 module_Need( p_sys->p_vout, "opengl provider", NULL, 0 );
185 if( p_sys->p_vout->p_module == NULL )
187 msg_Warn( p_vout, "No OpenGL provider found" );
188 vlc_object_detach( p_sys->p_vout );
189 vlc_object_destroy( p_sys->p_vout );
193 p_vout->pf_init = Init;
194 p_vout->pf_end = End;
195 p_vout->pf_manage = Manage;
196 p_vout->pf_render = Render;
197 p_vout->pf_display = DisplayVideo;
198 p_vout->pf_control = Control;
200 /* Forward events from the opengl provider */
201 var_Create( p_sys->p_vout, "mouse-x", VLC_VAR_INTEGER );
202 var_Create( p_sys->p_vout, "mouse-y", VLC_VAR_INTEGER );
203 var_Create( p_sys->p_vout, "mouse-moved", VLC_VAR_BOOL );
204 var_Create( p_sys->p_vout, "mouse-clicked", VLC_VAR_INTEGER );
205 var_Create( p_sys->p_vout, "video-on-top", VLC_VAR_BOOL );
207 var_AddCallback( p_sys->p_vout, "mouse-x", SendEvents, p_vout );
208 var_AddCallback( p_sys->p_vout, "mouse-y", SendEvents, p_vout );
209 var_AddCallback( p_sys->p_vout, "mouse-moved", SendEvents, p_vout );
210 var_AddCallback( p_sys->p_vout, "mouse-clicked", SendEvents, p_vout );
215 /*****************************************************************************
216 * Init: initialize the OpenGL video thread output method
217 *****************************************************************************/
218 static int Init( vout_thread_t *p_vout )
220 vout_sys_t *p_sys = p_vout->p_sys;
224 p_sys->p_vout->pf_init( p_sys->p_vout );
227 p_vout->output.i_chroma = VLC_FOURCC('Y','U','Y','2');
228 p_vout->output.i_rmask = 0x00ff0000;
229 p_vout->output.i_gmask = 0x0000ff00;
230 p_vout->output.i_bmask = 0x000000ff;
233 #if VLCGL_RGB_FORMAT == GL_RGB
234 # if VLCGL_RGB_TYPE == GL_UNSIGNED_BYTE
235 p_vout->output.i_chroma = VLC_FOURCC('R','V','2','4');
236 p_vout->output.i_rmask = 0x000000ff;
237 p_vout->output.i_gmask = 0x0000ff00;
238 p_vout->output.i_bmask = 0x00ff0000;
241 p_vout->output.i_chroma = VLC_FOURCC('R','V','1','6');
242 p_vout->output.i_rmask = 0xf800;
243 p_vout->output.i_gmask = 0x07e0;
244 p_vout->output.i_bmask = 0x001f;
248 p_vout->output.i_chroma = VLC_FOURCC('R','V','3','2');
249 p_vout->output.i_rmask = 0x000000ff;
250 p_vout->output.i_gmask = 0x0000ff00;
251 p_vout->output.i_bmask = 0x00ff0000;
256 /* Since OpenGL can do rescaling for us, stick to the default
257 * coordinates and aspect. */
258 p_vout->output.i_width = p_vout->render.i_width;
259 p_vout->output.i_height = p_vout->render.i_height;
260 p_vout->output.i_aspect = p_vout->render.i_aspect;
262 /* We know the chroma, allocate one buffer which will be used
263 * directly by the decoder */
264 p_sys->pp_buffer[0] =
265 malloc( p_sys->i_tex_width * p_sys->i_tex_height * i_pixel_pitch );
266 if( !p_sys->pp_buffer[0] )
268 msg_Err( p_vout, "Out of memory" );
271 p_sys->pp_buffer[1] =
272 malloc( p_sys->i_tex_width * p_sys->i_tex_height * i_pixel_pitch );
273 if( !p_sys->pp_buffer[1] )
275 msg_Err( p_vout, "Out of memory" );
279 p_vout->p_picture[0].i_planes = 1;
280 p_vout->p_picture[0].p->p_pixels = p_sys->pp_buffer[0];
281 p_vout->p_picture[0].p->i_lines = p_vout->output.i_height;
282 p_vout->p_picture[0].p->i_visible_lines = p_vout->output.i_height;
283 p_vout->p_picture[0].p->i_pixel_pitch = i_pixel_pitch;
284 p_vout->p_picture[0].p->i_pitch = p_vout->output.i_width *
285 p_vout->p_picture[0].p->i_pixel_pitch;
286 p_vout->p_picture[0].p->i_visible_pitch = p_vout->output.i_width *
287 p_vout->p_picture[0].p->i_pixel_pitch;
289 p_vout->p_picture[0].i_status = DESTROYED_PICTURE;
290 p_vout->p_picture[0].i_type = DIRECT_PICTURE;
292 PP_OUTPUTPICTURE[ 0 ] = &p_vout->p_picture[0];
294 I_OUTPUTPICTURES = 1;
296 InitTextures( p_vout );
299 glDisable(GL_DEPTH_TEST);
300 glDepthMask(GL_FALSE);
301 glDisable(GL_CULL_FACE);
302 glClear( GL_COLOR_BUFFER_BIT );
304 /* Check if the user asked for useless visual effects */
305 var_Get( p_vout, "opengl-effect", &val );
306 if( !val.psz_string || !strcmp( val.psz_string, "none" ))
308 p_sys->i_effect = OPENGL_EFFECT_NONE;
310 else if( !strcmp( val.psz_string, "cube" ) )
312 p_sys->i_effect = OPENGL_EFFECT_CUBE;
314 glEnable( GL_CULL_FACE);
315 //glEnable( GL_DEPTH_TEST );
317 else if( !strcmp( val.psz_string, "transparent-cube" ) )
319 p_sys->i_effect = OPENGL_EFFECT_TRANSPARENT_CUBE;
321 glDisable( GL_DEPTH_TEST );
322 glEnable( GL_BLEND );
323 glBlendFunc( GL_SRC_ALPHA, GL_ONE );
327 msg_Warn( p_vout, "no valid opengl effect provided, using "
329 p_sys->i_effect = OPENGL_EFFECT_NONE;
331 if( val.psz_string ) free( val.psz_string );
333 if( p_sys->i_effect & ( OPENGL_EFFECT_CUBE |
334 OPENGL_EFFECT_TRANSPARENT_CUBE ) )
336 /* Set the perpective */
337 glMatrixMode( GL_PROJECTION );
339 glFrustum( -1.0, 1.0, -1.0, 1.0, 3.0, 20.0 );
340 glMatrixMode( GL_MODELVIEW );
342 glTranslatef( 0.0, 0.0, - 5.0 );
348 /*****************************************************************************
349 * End: terminate GLX video thread output method
350 *****************************************************************************/
351 static void End( vout_thread_t *p_vout )
357 /*****************************************************************************
358 * Destroy: destroy GLX video thread output method
359 *****************************************************************************
360 * Terminate an output method created by CreateVout
361 *****************************************************************************/
362 static void DestroyVout( vlc_object_t *p_this )
364 vout_thread_t *p_vout = (vout_thread_t *)p_this;
365 vout_sys_t *p_sys = p_vout->p_sys;
367 module_Unneed( p_sys->p_vout, p_sys->p_vout->p_module );
368 vlc_object_detach( p_sys->p_vout );
369 vlc_object_destroy( p_sys->p_vout );
371 /* Free the texture buffer*/
372 if( p_sys->pp_buffer[0] ) free( p_sys->pp_buffer[0] );
373 if( p_sys->pp_buffer[1] ) free( p_sys->pp_buffer[1] );
378 /*****************************************************************************
379 * Manage: handle Sys events
380 *****************************************************************************
381 * This function should be called regularly by video output thread. It returns
382 * a non null value if an error occurred.
383 *****************************************************************************/
384 static int Manage( vout_thread_t *p_vout )
386 vout_sys_t *p_sys = p_vout->p_sys;
387 int i_ret, i_fullscreen_change;
389 i_fullscreen_change = ( p_vout->i_changes & VOUT_FULLSCREEN_CHANGE );
391 p_sys->p_vout->i_changes = p_vout->i_changes;
392 i_ret = p_sys->p_vout->pf_manage( p_sys->p_vout );
393 p_vout->i_changes = p_sys->p_vout->i_changes;
396 /* On OS X, we create the window and the GL view when entering
397 fullscreen - the textures have to be inited again */
398 if( i_fullscreen_change )
400 InitTextures( p_vout );
402 switch( p_sys->i_effect )
404 case OPENGL_EFFECT_CUBE:
405 glEnable( GL_CULL_FACE );
408 case OPENGL_EFFECT_TRANSPARENT_CUBE:
409 glDisable( GL_DEPTH_TEST );
410 glEnable( GL_BLEND );
411 glBlendFunc( GL_SRC_ALPHA, GL_ONE );
415 if( p_sys->i_effect & ( OPENGL_EFFECT_CUBE |
416 OPENGL_EFFECT_TRANSPARENT_CUBE ) )
418 /* Set the perpective */
419 glMatrixMode( GL_PROJECTION );
421 glFrustum( -1.0, 1.0, -1.0, 1.0, 3.0, 20.0 );
422 glMatrixMode( GL_MODELVIEW );
424 glTranslatef( 0.0, 0.0, - 5.0 );
432 /*****************************************************************************
433 * Render: render previously calculated output
434 *****************************************************************************/
435 static void Render( vout_thread_t *p_vout, picture_t *p_pic )
437 vout_sys_t *p_sys = p_vout->p_sys;
438 float f_width, f_height;
440 /* glTexCoord works differently with GL_TEXTURE_2D and
441 GL_TEXTURE_RECTANGLE_EXT */
443 f_width = (float)p_vout->output.i_width;
444 f_height = (float)p_vout->output.i_height;
446 f_width = (float)p_vout->output.i_width / p_sys->i_tex_width;
447 f_height = (float)p_vout->output.i_height / p_sys->i_tex_height;
450 glClear( GL_COLOR_BUFFER_BIT );
452 /* On Win32/GLX, we do this the usual way:
453 + Fill the buffer with new content,
454 + Reload the texture,
457 On OS X with VRAM or AGP texturing, the order has to be:
458 + Reload the texture,
459 + Fill the buffer with new content,
462 (Thanks to gcc from the Arstechnica forums for the tip)
464 Therefore, we have to use two buffers and textures. On Win32/GLX,
465 we reload the texture to be displayed and use it right away. On
466 OS X, we first render, then reload the texture to be used next
470 glBindTexture( VLCGL_TARGET, p_sys->p_textures[p_sys->i_index] );
473 /* Update the texture */
474 glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0,
475 p_vout->render.i_width, p_vout->render.i_height,
476 VLCGL_RGB_FORMAT, VLCGL_RGB_TYPE, p_sys->pp_buffer[0] );
479 if( p_sys->i_effect == OPENGL_EFFECT_NONE )
481 glEnable( VLCGL_TARGET );
482 glBegin( GL_POLYGON );
483 glTexCoord2f( 0.0, 0.0 ); glVertex2f( -1.0, 1.0 );
484 glTexCoord2f( f_width, 0.0 ); glVertex2f( 1.0, 1.0 );
485 glTexCoord2f( f_width, f_height ); glVertex2f( 1.0, -1.0 );
486 glTexCoord2f( 0.0, f_height ); glVertex2f( -1.0, -1.0 );
491 glRotatef( 1.0, 0.3, 0.5, 0.7 );
493 glEnable( VLCGL_TARGET );
497 glTexCoord2f( 0, 0 ); glVertex3f( - 1.0, 1.0, 1.0 );
498 glTexCoord2f( 0, f_height ); glVertex3f( - 1.0, - 1.0, 1.0 );
499 glTexCoord2f( f_width, f_height ); glVertex3f( 1.0, - 1.0, 1.0 );
500 glTexCoord2f( f_width, 0 ); glVertex3f( 1.0, 1.0, 1.0 );
503 glTexCoord2f( 0, 0 ); glVertex3f( - 1.0, 1.0, - 1.0 );
504 glTexCoord2f( 0, f_height ); glVertex3f( - 1.0, - 1.0, - 1.0 );
505 glTexCoord2f( f_width, f_height ); glVertex3f( - 1.0, - 1.0, 1.0 );
506 glTexCoord2f( f_width, 0 ); glVertex3f( - 1.0, 1.0, 1.0 );
509 glTexCoord2f( 0, 0 ); glVertex3f( 1.0, 1.0, - 1.0 );
510 glTexCoord2f( 0, f_height ); glVertex3f( 1.0, - 1.0, - 1.0 );
511 glTexCoord2f( f_width, f_height ); glVertex3f( - 1.0, - 1.0, - 1.0 );
512 glTexCoord2f( f_width, 0 ); glVertex3f( - 1.0, 1.0, - 1.0 );
515 glTexCoord2f( 0, 0 ); glVertex3f( 1.0, 1.0, 1.0 );
516 glTexCoord2f( 0, f_height ); glVertex3f( 1.0, - 1.0, 1.0 );
517 glTexCoord2f( f_width, f_height ); glVertex3f( 1.0, - 1.0, - 1.0 );
518 glTexCoord2f( f_width, 0 ); glVertex3f( 1.0, 1.0, - 1.0 );
521 glTexCoord2f( 0, 0 ); glVertex3f( - 1.0, 1.0, - 1.0 );
522 glTexCoord2f( 0, f_height ); glVertex3f( - 1.0, 1.0, 1.0 );
523 glTexCoord2f( f_width, f_height ); glVertex3f( 1.0, 1.0, 1.0 );
524 glTexCoord2f( f_width, 0 ); glVertex3f( 1.0, 1.0, - 1.0 );
527 glTexCoord2f( 0, 0 ); glVertex3f( - 1.0, - 1.0, 1.0 );
528 glTexCoord2f( 0, f_height ); glVertex3f( - 1.0, - 1.0, - 1.0 );
529 glTexCoord2f( f_width, f_height ); glVertex3f( 1.0, - 1.0, - 1.0 );
530 glTexCoord2f( f_width, 0 ); glVertex3f( 1.0, - 1.0, 1.0 );
534 glDisable( VLCGL_TARGET );
538 p_sys->i_index = ( p_sys->i_index + 1 ) & 1;
539 p_pic->p->p_pixels = p_sys->pp_buffer[p_sys->i_index];
541 /* Update the texture */
542 glBindTexture( VLCGL_TARGET, p_sys->p_textures[p_sys->i_index] );
543 glTexSubImage2D( VLCGL_TARGET, 0, 0, 0, p_sys->i_tex_width,
544 p_sys->i_tex_height, VLCGL_FORMAT, VLCGL_TYPE,
545 p_sys->pp_buffer[p_sys->i_index] );
549 /*****************************************************************************
550 * DisplayVideo: displays previously rendered output
551 *****************************************************************************/
552 static void DisplayVideo( vout_thread_t *p_vout, picture_t *p_pic )
554 vout_sys_t *p_sys = p_vout->p_sys;
555 p_sys->p_vout->pf_swap( p_sys->p_vout );
558 int GetAlignedSize( int i_size )
560 /* Return the nearest power of 2 */
562 while( i_result < i_size )
569 /*****************************************************************************
570 * Control: control facility for the vout
571 *****************************************************************************/
572 static int Control( vout_thread_t *p_vout, int i_query, va_list args )
574 vout_sys_t *p_sys = p_vout->p_sys;
576 if( p_sys->p_vout->pf_control )
577 return p_sys->p_vout->pf_control( p_sys->p_vout, i_query, args );
579 return vout_vaControlDefault( p_vout, i_query, args );
582 static int InitTextures( vout_thread_t *p_vout )
584 vout_sys_t *p_sys = p_vout->p_sys;
587 glDeleteTextures( 2, p_sys->p_textures );
588 glGenTextures( 2, p_sys->p_textures );
590 for( i_index = 0; i_index < 2; i_index++ )
592 glBindTexture( VLCGL_TARGET, p_sys->p_textures[i_index] );
594 /* Set the texture parameters */
595 glTexParameterf( VLCGL_TARGET, GL_TEXTURE_PRIORITY, 1.0 );
597 glTexParameteri( VLCGL_TARGET, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
598 glTexParameteri( VLCGL_TARGET, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
600 glTexParameteri( VLCGL_TARGET, GL_TEXTURE_WRAP_S, GL_CLAMP );
601 glTexParameteri( VLCGL_TARGET, GL_TEXTURE_WRAP_T, GL_CLAMP );
603 glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
606 /* Tell the driver not to make a copy of the texture but to use
608 glEnable( GL_UNPACK_CLIENT_STORAGE_APPLE );
609 glPixelStorei( GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE );
612 /* Use VRAM texturing */
613 glTexParameteri( VLCGL_TARGET, GL_TEXTURE_STORAGE_HINT_APPLE,
614 GL_STORAGE_CACHED_APPLE );
616 /* Use AGP texturing */
617 glTexParameteri( VLCGL_TARGET, GL_TEXTURE_STORAGE_HINT_APPLE,
618 GL_STORAGE_SHARED_APPLE );
622 /* Call glTexImage2D only once, and use glTexSubImage2D later */
623 glTexImage2D( VLCGL_TARGET, 0, 3, p_sys->i_tex_width,
624 p_sys->i_tex_height, 0, VLCGL_FORMAT, VLCGL_TYPE,
625 p_sys->pp_buffer[i_index] );
631 /*****************************************************************************
632 * SendEvents: forward mouse and keyboard events to the parent p_vout
633 *****************************************************************************/
634 static int SendEvents( vlc_object_t *p_this, char const *psz_var,
635 vlc_value_t oldval, vlc_value_t newval, void *_p_vout )
637 return var_Set( (vlc_object_t *)_p_vout, psz_var, newval );