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",
206 VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
208 var_AddCallback( p_sys->p_vout, "mouse-x", SendEvents, p_vout );
209 var_AddCallback( p_sys->p_vout, "mouse-y", SendEvents, p_vout );
210 var_AddCallback( p_sys->p_vout, "mouse-moved", SendEvents, p_vout );
211 var_AddCallback( p_sys->p_vout, "mouse-clicked", SendEvents, p_vout );
216 /*****************************************************************************
217 * Init: initialize the OpenGL video thread output method
218 *****************************************************************************/
219 static int Init( vout_thread_t *p_vout )
221 vout_sys_t *p_sys = p_vout->p_sys;
225 p_sys->p_vout->pf_init( p_sys->p_vout );
228 p_vout->output.i_chroma = VLC_FOURCC('Y','U','Y','2');
229 p_vout->output.i_rmask = 0x00ff0000;
230 p_vout->output.i_gmask = 0x0000ff00;
231 p_vout->output.i_bmask = 0x000000ff;
234 #if VLCGL_RGB_FORMAT == GL_RGB
235 # if VLCGL_RGB_TYPE == GL_UNSIGNED_BYTE
236 p_vout->output.i_chroma = VLC_FOURCC('R','V','2','4');
237 p_vout->output.i_rmask = 0x000000ff;
238 p_vout->output.i_gmask = 0x0000ff00;
239 p_vout->output.i_bmask = 0x00ff0000;
242 p_vout->output.i_chroma = VLC_FOURCC('R','V','1','6');
243 p_vout->output.i_rmask = 0xf800;
244 p_vout->output.i_gmask = 0x07e0;
245 p_vout->output.i_bmask = 0x001f;
249 p_vout->output.i_chroma = VLC_FOURCC('R','V','3','2');
250 p_vout->output.i_rmask = 0x000000ff;
251 p_vout->output.i_gmask = 0x0000ff00;
252 p_vout->output.i_bmask = 0x00ff0000;
257 /* Since OpenGL can do rescaling for us, stick to the default
258 * coordinates and aspect. */
259 p_vout->output.i_width = p_vout->render.i_width;
260 p_vout->output.i_height = p_vout->render.i_height;
261 p_vout->output.i_aspect = p_vout->render.i_aspect;
263 /* We know the chroma, allocate one buffer which will be used
264 * directly by the decoder */
265 p_sys->pp_buffer[0] =
266 malloc( p_sys->i_tex_width * p_sys->i_tex_height * i_pixel_pitch );
267 if( !p_sys->pp_buffer[0] )
269 msg_Err( p_vout, "Out of memory" );
272 p_sys->pp_buffer[1] =
273 malloc( p_sys->i_tex_width * p_sys->i_tex_height * i_pixel_pitch );
274 if( !p_sys->pp_buffer[1] )
276 msg_Err( p_vout, "Out of memory" );
280 p_vout->p_picture[0].i_planes = 1;
281 p_vout->p_picture[0].p->p_pixels = p_sys->pp_buffer[0];
282 p_vout->p_picture[0].p->i_lines = p_vout->output.i_height;
283 p_vout->p_picture[0].p->i_visible_lines = p_vout->output.i_height;
284 p_vout->p_picture[0].p->i_pixel_pitch = i_pixel_pitch;
285 p_vout->p_picture[0].p->i_pitch = p_vout->output.i_width *
286 p_vout->p_picture[0].p->i_pixel_pitch;
287 p_vout->p_picture[0].p->i_visible_pitch = p_vout->output.i_width *
288 p_vout->p_picture[0].p->i_pixel_pitch;
290 p_vout->p_picture[0].i_status = DESTROYED_PICTURE;
291 p_vout->p_picture[0].i_type = DIRECT_PICTURE;
293 PP_OUTPUTPICTURE[ 0 ] = &p_vout->p_picture[0];
295 I_OUTPUTPICTURES = 1;
297 InitTextures( p_vout );
300 glDisable(GL_DEPTH_TEST);
301 glDepthMask(GL_FALSE);
302 glDisable(GL_CULL_FACE);
303 glClear( GL_COLOR_BUFFER_BIT );
305 /* Check if the user asked for useless visual effects */
306 var_Get( p_vout, "opengl-effect", &val );
307 if( !val.psz_string || !strcmp( val.psz_string, "none" ))
309 p_sys->i_effect = OPENGL_EFFECT_NONE;
311 else if( !strcmp( val.psz_string, "cube" ) )
313 p_sys->i_effect = OPENGL_EFFECT_CUBE;
315 glEnable( GL_CULL_FACE);
316 //glEnable( GL_DEPTH_TEST );
318 else if( !strcmp( val.psz_string, "transparent-cube" ) )
320 p_sys->i_effect = OPENGL_EFFECT_TRANSPARENT_CUBE;
322 glDisable( GL_DEPTH_TEST );
323 glEnable( GL_BLEND );
324 glBlendFunc( GL_SRC_ALPHA, GL_ONE );
328 msg_Warn( p_vout, "no valid opengl effect provided, using "
330 p_sys->i_effect = OPENGL_EFFECT_NONE;
332 if( val.psz_string ) free( val.psz_string );
334 if( p_sys->i_effect & ( OPENGL_EFFECT_CUBE |
335 OPENGL_EFFECT_TRANSPARENT_CUBE ) )
337 /* Set the perpective */
338 glMatrixMode( GL_PROJECTION );
340 glFrustum( -1.0, 1.0, -1.0, 1.0, 3.0, 20.0 );
341 glMatrixMode( GL_MODELVIEW );
343 glTranslatef( 0.0, 0.0, - 5.0 );
349 /*****************************************************************************
350 * End: terminate GLX video thread output method
351 *****************************************************************************/
352 static void End( vout_thread_t *p_vout )
358 /*****************************************************************************
359 * Destroy: destroy GLX video thread output method
360 *****************************************************************************
361 * Terminate an output method created by CreateVout
362 *****************************************************************************/
363 static void DestroyVout( vlc_object_t *p_this )
365 vout_thread_t *p_vout = (vout_thread_t *)p_this;
366 vout_sys_t *p_sys = p_vout->p_sys;
368 module_Unneed( p_sys->p_vout, p_sys->p_vout->p_module );
369 vlc_object_detach( p_sys->p_vout );
370 vlc_object_destroy( p_sys->p_vout );
372 /* Free the texture buffer*/
373 if( p_sys->pp_buffer[0] ) free( p_sys->pp_buffer[0] );
374 if( p_sys->pp_buffer[1] ) free( p_sys->pp_buffer[1] );
379 /*****************************************************************************
380 * Manage: handle Sys events
381 *****************************************************************************
382 * This function should be called regularly by video output thread. It returns
383 * a non null value if an error occurred.
384 *****************************************************************************/
385 static int Manage( vout_thread_t *p_vout )
387 vout_sys_t *p_sys = p_vout->p_sys;
388 int i_ret, i_fullscreen_change;
390 i_fullscreen_change = ( p_vout->i_changes & VOUT_FULLSCREEN_CHANGE );
392 p_sys->p_vout->i_changes = p_vout->i_changes;
393 i_ret = p_sys->p_vout->pf_manage( p_sys->p_vout );
394 p_vout->i_changes = p_sys->p_vout->i_changes;
397 /* On OS X, we create the window and the GL view when entering
398 fullscreen - the textures have to be inited again */
399 if( i_fullscreen_change )
401 InitTextures( p_vout );
403 switch( p_sys->i_effect )
405 case OPENGL_EFFECT_CUBE:
406 glEnable( GL_CULL_FACE );
409 case OPENGL_EFFECT_TRANSPARENT_CUBE:
410 glDisable( GL_DEPTH_TEST );
411 glEnable( GL_BLEND );
412 glBlendFunc( GL_SRC_ALPHA, GL_ONE );
416 if( p_sys->i_effect & ( OPENGL_EFFECT_CUBE |
417 OPENGL_EFFECT_TRANSPARENT_CUBE ) )
419 /* Set the perpective */
420 glMatrixMode( GL_PROJECTION );
422 glFrustum( -1.0, 1.0, -1.0, 1.0, 3.0, 20.0 );
423 glMatrixMode( GL_MODELVIEW );
425 glTranslatef( 0.0, 0.0, - 5.0 );
433 /*****************************************************************************
434 * Render: render previously calculated output
435 *****************************************************************************/
436 static void Render( vout_thread_t *p_vout, picture_t *p_pic )
438 vout_sys_t *p_sys = p_vout->p_sys;
439 float f_width, f_height;
441 /* glTexCoord works differently with GL_TEXTURE_2D and
442 GL_TEXTURE_RECTANGLE_EXT */
444 f_width = (float)p_vout->output.i_width;
445 f_height = (float)p_vout->output.i_height;
447 f_width = (float)p_vout->output.i_width / p_sys->i_tex_width;
448 f_height = (float)p_vout->output.i_height / p_sys->i_tex_height;
451 glClear( GL_COLOR_BUFFER_BIT );
453 /* On Win32/GLX, we do this the usual way:
454 + Fill the buffer with new content,
455 + Reload the texture,
458 On OS X with VRAM or AGP texturing, the order has to be:
459 + Reload the texture,
460 + Fill the buffer with new content,
463 (Thanks to gcc from the Arstechnica forums for the tip)
465 Therefore, we have to use two buffers and textures. On Win32/GLX,
466 we reload the texture to be displayed and use it right away. On
467 OS X, we first render, then reload the texture to be used next
471 glBindTexture( VLCGL_TARGET, p_sys->p_textures[p_sys->i_index] );
474 /* Update the texture */
475 glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0,
476 p_vout->render.i_width, p_vout->render.i_height,
477 VLCGL_RGB_FORMAT, VLCGL_RGB_TYPE, p_sys->pp_buffer[0] );
480 if( p_sys->i_effect == OPENGL_EFFECT_NONE )
482 glEnable( VLCGL_TARGET );
483 glBegin( GL_POLYGON );
484 glTexCoord2f( 0.0, 0.0 ); glVertex2f( -1.0, 1.0 );
485 glTexCoord2f( f_width, 0.0 ); glVertex2f( 1.0, 1.0 );
486 glTexCoord2f( f_width, f_height ); glVertex2f( 1.0, -1.0 );
487 glTexCoord2f( 0.0, f_height ); glVertex2f( -1.0, -1.0 );
492 glRotatef( 1.0, 0.3, 0.5, 0.7 );
494 glEnable( VLCGL_TARGET );
498 glTexCoord2f( 0, 0 ); glVertex3f( - 1.0, 1.0, 1.0 );
499 glTexCoord2f( 0, f_height ); glVertex3f( - 1.0, - 1.0, 1.0 );
500 glTexCoord2f( f_width, f_height ); glVertex3f( 1.0, - 1.0, 1.0 );
501 glTexCoord2f( f_width, 0 ); glVertex3f( 1.0, 1.0, 1.0 );
504 glTexCoord2f( 0, 0 ); glVertex3f( - 1.0, 1.0, - 1.0 );
505 glTexCoord2f( 0, f_height ); glVertex3f( - 1.0, - 1.0, - 1.0 );
506 glTexCoord2f( f_width, f_height ); glVertex3f( - 1.0, - 1.0, 1.0 );
507 glTexCoord2f( f_width, 0 ); glVertex3f( - 1.0, 1.0, 1.0 );
510 glTexCoord2f( 0, 0 ); glVertex3f( 1.0, 1.0, - 1.0 );
511 glTexCoord2f( 0, f_height ); glVertex3f( 1.0, - 1.0, - 1.0 );
512 glTexCoord2f( f_width, f_height ); glVertex3f( - 1.0, - 1.0, - 1.0 );
513 glTexCoord2f( f_width, 0 ); glVertex3f( - 1.0, 1.0, - 1.0 );
516 glTexCoord2f( 0, 0 ); glVertex3f( 1.0, 1.0, 1.0 );
517 glTexCoord2f( 0, f_height ); glVertex3f( 1.0, - 1.0, 1.0 );
518 glTexCoord2f( f_width, f_height ); glVertex3f( 1.0, - 1.0, - 1.0 );
519 glTexCoord2f( f_width, 0 ); glVertex3f( 1.0, 1.0, - 1.0 );
522 glTexCoord2f( 0, 0 ); glVertex3f( - 1.0, 1.0, - 1.0 );
523 glTexCoord2f( 0, f_height ); glVertex3f( - 1.0, 1.0, 1.0 );
524 glTexCoord2f( f_width, f_height ); glVertex3f( 1.0, 1.0, 1.0 );
525 glTexCoord2f( f_width, 0 ); glVertex3f( 1.0, 1.0, - 1.0 );
528 glTexCoord2f( 0, 0 ); glVertex3f( - 1.0, - 1.0, 1.0 );
529 glTexCoord2f( 0, f_height ); glVertex3f( - 1.0, - 1.0, - 1.0 );
530 glTexCoord2f( f_width, f_height ); glVertex3f( 1.0, - 1.0, - 1.0 );
531 glTexCoord2f( f_width, 0 ); glVertex3f( 1.0, - 1.0, 1.0 );
535 glDisable( VLCGL_TARGET );
539 p_sys->i_index = ( p_sys->i_index + 1 ) & 1;
540 p_pic->p->p_pixels = p_sys->pp_buffer[p_sys->i_index];
542 /* Update the texture */
543 glBindTexture( VLCGL_TARGET, p_sys->p_textures[p_sys->i_index] );
544 glTexSubImage2D( VLCGL_TARGET, 0, 0, 0, p_sys->i_tex_width,
545 p_sys->i_tex_height, VLCGL_FORMAT, VLCGL_TYPE,
546 p_sys->pp_buffer[p_sys->i_index] );
550 /*****************************************************************************
551 * DisplayVideo: displays previously rendered output
552 *****************************************************************************/
553 static void DisplayVideo( vout_thread_t *p_vout, picture_t *p_pic )
555 vout_sys_t *p_sys = p_vout->p_sys;
556 p_sys->p_vout->pf_swap( p_sys->p_vout );
559 int GetAlignedSize( int i_size )
561 /* Return the nearest power of 2 */
563 while( i_result < i_size )
570 /*****************************************************************************
571 * Control: control facility for the vout
572 *****************************************************************************/
573 static int Control( vout_thread_t *p_vout, int i_query, va_list args )
575 vout_sys_t *p_sys = p_vout->p_sys;
577 if( p_sys->p_vout->pf_control )
578 return p_sys->p_vout->pf_control( p_sys->p_vout, i_query, args );
580 return vout_vaControlDefault( p_vout, i_query, args );
583 static int InitTextures( vout_thread_t *p_vout )
585 vout_sys_t *p_sys = p_vout->p_sys;
588 glDeleteTextures( 2, p_sys->p_textures );
589 glGenTextures( 2, p_sys->p_textures );
591 for( i_index = 0; i_index < 2; i_index++ )
593 glBindTexture( VLCGL_TARGET, p_sys->p_textures[i_index] );
595 /* Set the texture parameters */
596 glTexParameterf( VLCGL_TARGET, GL_TEXTURE_PRIORITY, 1.0 );
598 glTexParameteri( VLCGL_TARGET, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
599 glTexParameteri( VLCGL_TARGET, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
601 glTexParameteri( VLCGL_TARGET, GL_TEXTURE_WRAP_S, GL_CLAMP );
602 glTexParameteri( VLCGL_TARGET, GL_TEXTURE_WRAP_T, GL_CLAMP );
604 glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
607 /* Tell the driver not to make a copy of the texture but to use
609 glEnable( GL_UNPACK_CLIENT_STORAGE_APPLE );
610 glPixelStorei( GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE );
613 /* Use VRAM texturing */
614 glTexParameteri( VLCGL_TARGET, GL_TEXTURE_STORAGE_HINT_APPLE,
615 GL_STORAGE_CACHED_APPLE );
617 /* Use AGP texturing */
618 glTexParameteri( VLCGL_TARGET, GL_TEXTURE_STORAGE_HINT_APPLE,
619 GL_STORAGE_SHARED_APPLE );
623 /* Call glTexImage2D only once, and use glTexSubImage2D later */
624 glTexImage2D( VLCGL_TARGET, 0, 3, p_sys->i_tex_width,
625 p_sys->i_tex_height, 0, VLCGL_FORMAT, VLCGL_TYPE,
626 p_sys->pp_buffer[i_index] );
632 /*****************************************************************************
633 * SendEvents: forward mouse and keyboard events to the parent p_vout
634 *****************************************************************************/
635 static int SendEvents( vlc_object_t *p_this, char const *psz_var,
636 vlc_value_t oldval, vlc_value_t newval, void *_p_vout )
638 return var_Set( (vlc_object_t *)_p_vout, psz_var, newval );