3 * Copyright (C) 2012 Christophe Thommeret
4 * Author: Christophe Thommeret <hftom@free.fr>
5 * Based on Nehe's GLX port by Mihael.Vrbanec@stud.uni-karlsruhe.de
6 * http://nehe.gamedev.net/
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 #define GL_GLEXT_PROTOTYPES
31 #include <X11/keysym.h>
40 #include <framework/mlt.h>
43 #define STARTWIDTH 1280
44 #define STARTHEIGHT 720
47 extern int XInitThreads();
51 typedef struct consumer_xgl_s *consumer_xgl;
55 struct mlt_consumer_s parent;
56 mlt_properties properties;
79 pthread_mutex_t mutex;
81 mlt_frame mlt_frame_ref;
109 XSetWindowAttributes attr;
111 unsigned int width, height;
116 static GLWindow GLWin;
117 static HiddenContext hiddenctx;
119 static frame_new new_frame;
121 static thread_video vthread;
122 static consumer_xgl xgl;
123 static mlt_filter glsl_manager;
127 static void* video_thread( void *arg );
131 int _width = GLWin.width;
132 int _height = GLWin.height;
133 GLfloat left, right, top, bottom;
134 GLfloat war = (GLfloat)_width/(GLfloat)_height;
136 if ( war < new_frame.aspect_ratio ) {
139 top = war / new_frame.aspect_ratio;
140 bottom = -war / new_frame.aspect_ratio;
145 left = -new_frame.aspect_ratio / war;
146 right = new_frame.aspect_ratio / war;
149 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
154 glTranslatef( _width/2, _height/2, 0 );
155 glScalef( _width/2, _height/2, 1.0 );
157 glBindTexture( GL_TEXTURE_2D, fb.texture );
160 glTexCoord2f( 0.0f, 0.0f ); glVertex2f( left, top );
161 glTexCoord2f( 0.0f, 1.0f ); glVertex2f( left, bottom );
162 glTexCoord2f( 1.0f, 1.0f ); glVertex2f( right, bottom );
163 glTexCoord2f( 1.0f, 0.0f ); glVertex2f( right, top );
168 glXSwapBuffers( GLWin.dpy, GLWin.win );
170 if ( !vthread.running ) {
171 pthread_create( &vthread.thread, NULL, video_thread, NULL );
178 static void show_frame()
180 if ( (fb.width != new_frame.width) || (fb.height != new_frame.height) ) {
181 glDeleteFramebuffers( 1, &fb.fbo );
182 glDeleteTextures( 1, &fb.texture );
184 fb.width = new_frame.width;
185 fb.height = new_frame.height;
186 glGenFramebuffers( 1, &fb.fbo );
187 glGenTextures( 1, &fb.texture );
188 glBindTexture( GL_TEXTURE_2D, fb.texture );
189 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, fb.width, fb.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );
190 glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
191 glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
192 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
193 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
194 glBindFramebuffer( GL_FRAMEBUFFER, fb.fbo );
195 glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb.texture, 0 );
196 glBindFramebuffer( GL_FRAMEBUFFER, 0 );
199 glPushAttrib(GL_VIEWPORT_BIT);
200 glMatrixMode(GL_PROJECTION);
203 glBindFramebuffer( GL_FRAMEBUFFER, fb.fbo );
205 glViewport( 0, 0, new_frame.width, new_frame.height );
206 glMatrixMode( GL_PROJECTION );
208 glOrtho( 0.0, new_frame.width, 0.0, new_frame.height, -1.0, 1.0 );
209 glMatrixMode( GL_MODELVIEW );
212 glActiveTexture( GL_TEXTURE0 );
213 glBindTexture( GL_TEXTURE_2D, new_frame.texture );
216 glTexCoord2f( 0.0f, 0.0f ); glVertex2f( 0.0f, 0.0f );
217 glTexCoord2f( 0.0f, 1.0f ); glVertex2f( 0.0f, new_frame.height );
218 glTexCoord2f( 1.0f, 1.0f ); glVertex2f( new_frame.width, new_frame.height );
219 glTexCoord2f( 1.0f, 0.0f ); glVertex2f( new_frame.width, 0.0f );
222 glBindFramebuffer( GL_FRAMEBUFFER, 0 );
223 mlt_events_fire( MLT_CONSUMER_PROPERTIES(&xgl->parent), "consumer-frame-show", new_frame.mlt_frame_ref, NULL );
224 mlt_frame_close( new_frame.mlt_frame_ref );
225 new_frame.mlt_frame_ref = NULL;
227 glMatrixMode(GL_PROJECTION);
229 glMatrixMode(GL_MODELVIEW);
239 void* video_thread( void *arg )
241 mlt_frame next = NULL;
242 mlt_consumer consumer = &xgl->parent;
243 mlt_properties consumer_props = MLT_CONSUMER_PROPERTIES( consumer );
244 struct timeval start, end;
247 gettimeofday( &start, NULL );
249 while ( vthread.running )
251 // Get a frame from the attached producer
252 next = mlt_consumer_rt_frame( consumer );
254 if ( !mlt_properties_get_int( MLT_FILTER_PROPERTIES( glsl_manager ), "glsl_supported" ) ) {
255 mlt_log_error( MLT_CONSUMER_SERVICE(consumer), "OpenGL Shading Language is not supported on this machine.\n" );
260 // Ensure that we have a frame
263 mlt_properties properties = MLT_FRAME_PROPERTIES( next );
264 if ( mlt_properties_get_int( properties, "rendered" ) == 1 )
266 // Get the image, width and height
267 mlt_image_format vfmt = mlt_image_glsl_texture;
268 int width = 0, height = 0;
270 int error = mlt_frame_get_image( next, (uint8_t**) &image, &vfmt, &width, &height, 0 );
271 if ( !error && image && width && height && !new_frame.new ) {
272 new_frame.width = width;
273 new_frame.height = height;
274 new_frame.texture = *image;
275 new_frame.mlt_frame_ref = next;
276 new_frame.aspect_ratio = ((double)width / (double)height) * mlt_properties_get_double( properties, "aspect_ratio" );
280 while ( new_frame.new && --loop )
285 mlt_frame_close( next );
289 gettimeofday( &end, NULL );
290 duration = 1000000.0 / mlt_properties_get_double( consumer_props, "fps" );
291 duration -= ( end.tv_sec * 1000000 + end.tv_usec ) - ( start.tv_sec * 1000000 + start.tv_usec );
293 usleep( (int)duration );
294 gettimeofday( &start, NULL );
298 mlt_frame_close( next );
299 static int dropped = 0;
300 mlt_log_info( MLT_CONSUMER_SERVICE(consumer), "dropped video frame %d\n", ++dropped );
306 mlt_consumer_stopped( consumer );
313 static void resizeGLScene()
315 glXMakeCurrent( GLWin.dpy, GLWin.win, GLWin.ctx );
317 if ( GLWin.height == 0 )
319 if ( GLWin.width == 0 )
321 glViewport( 0, 0, GLWin.width, GLWin.height );
322 glMatrixMode( GL_PROJECTION );
324 glOrtho( 0.0, GLWin.width, 0.0, GLWin.height, -1.0, 1.0 );
325 glMatrixMode( GL_MODELVIEW );
332 static void initGL( void )
334 glXMakeCurrent( GLWin.dpy, GLWin.win, GLWin.ctx );
336 glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
337 glClearDepth( 1.0f );
338 glDepthFunc( GL_LEQUAL );
339 glEnable( GL_DEPTH_TEST );
340 glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
341 glEnable( GL_BLEND );
342 glShadeModel( GL_SMOOTH );
343 glEnable( GL_TEXTURE_2D );
344 glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
346 typedef int (*GLXSWAPINTERVALSGI) ( int );
347 GLXSWAPINTERVALSGI mglXSwapInterval = (GLXSWAPINTERVALSGI)glXGetProcAddressARB( (const GLubyte*)"glXSwapIntervalSGI" );
348 if ( mglXSwapInterval )
349 mglXSwapInterval( 1 );
352 fb.width = STARTWIDTH;
353 fb.height = STARTHEIGHT;
354 glGenFramebuffers( 1, &fb.fbo );
355 glGenTextures( 1, &fb.texture );
356 glBindTexture( GL_TEXTURE_2D, fb.texture );
357 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, fb.width, fb.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );
358 glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
359 glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
360 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
361 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
362 glBindFramebuffer( GL_FRAMEBUFFER, fb.fbo );
363 glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb.texture, 0 );
364 glBindFramebuffer( GL_FRAMEBUFFER, 0 );
371 static void createGLWindow()
373 const char* title = "OpenGL consumer";
374 int width = STARTWIDTH;
375 int height = STARTHEIGHT;
377 int attrListSgl[] = { GLX_RGBA, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8,
378 GLX_BLUE_SIZE, 8, GLX_DEPTH_SIZE, 16, None };
380 int attrListDbl[] = { GLX_RGBA, GLX_DOUBLEBUFFER, GLX_RED_SIZE, 8,
381 GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_DEPTH_SIZE, 16, None };
387 unsigned int borderDummy;
389 GLWin.dpy = XOpenDisplay( 0 );
390 GLWin.screen = DefaultScreen( GLWin.dpy );
392 vi = glXChooseVisual( GLWin.dpy, GLWin.screen, attrListDbl );
394 vi = glXChooseVisual( GLWin.dpy, GLWin.screen, attrListSgl );
396 GLWin.ctx = glXCreateContext( GLWin.dpy, vi, 0, GL_TRUE );
398 cmap = XCreateColormap( GLWin.dpy, RootWindow( GLWin.dpy, vi->screen ), vi->visual, AllocNone );
399 GLWin.attr.colormap = cmap;
400 GLWin.attr.border_pixel = 0;
402 GLWin.attr.event_mask = ExposureMask | KeyPressMask | ButtonPressMask | StructureNotifyMask;
403 GLWin.win = XCreateWindow( GLWin.dpy, RootWindow(GLWin.dpy, vi->screen), 0, 0, width, height,
404 0, vi->depth, InputOutput, vi->visual, CWBorderPixel | CWColormap | CWEventMask, &GLWin.attr );
405 wmDelete = XInternAtom( GLWin.dpy, "WM_DELETE_WINDOW", True );
406 XSetWMProtocols( GLWin.dpy, GLWin.win, &wmDelete, 1 );
407 XSetStandardProperties( GLWin.dpy, GLWin.win, title, title, None, NULL, 0, NULL );
408 XMapRaised( GLWin.dpy, GLWin.win );
410 glXMakeCurrent( GLWin.dpy, GLWin.win, GLWin.ctx );
411 XGetGeometry( GLWin.dpy, GLWin.win, &winDummy, &GLWin.x, &GLWin.y,
412 &GLWin.width, &GLWin.height, &borderDummy, &GLWin.depth );
414 // Verify GLSL works on this machine
415 hiddenctx.ctx = glXCreateContext( GLWin.dpy, vi, GLWin.ctx, GL_TRUE );
416 if ( hiddenctx.ctx ) {
417 hiddenctx.dpy = GLWin.dpy;
418 hiddenctx.screen = GLWin.screen;
419 hiddenctx.win = RootWindow( hiddenctx.dpy, hiddenctx.screen );
427 static void killGLWindow()
430 if ( !glXMakeCurrent( GLWin.dpy, None, NULL ) ) {
431 printf("Error releasing drawing context : killGLWindow\n");
433 glXDestroyContext( GLWin.dpy, GLWin.ctx );
438 glXDestroyContext( hiddenctx.dpy, hiddenctx.ctx );
440 XCloseDisplay( GLWin.dpy );
449 while ( xgl->running ) {
450 while ( XPending( GLWin.dpy ) > 0 ) {
451 XNextEvent( GLWin.dpy, &event );
452 switch ( event.type ) {
454 if ( event.xexpose.count != 0 )
457 case ConfigureNotify:
458 if ( (event.xconfigure.width != GLWin.width) || (event.xconfigure.height != GLWin.height) ) {
459 GLWin.width = event.xconfigure.width;
460 GLWin.height = event.xconfigure.height;
465 switch ( XLookupKeysym( &event.xkey, 0 ) ) {
470 mlt_producer producer = mlt_properties_get_data( xgl->properties, "transport_producer", NULL );
471 char keyboard[ 2 ] = " ";
472 void (*callback)( mlt_producer, char * ) = mlt_properties_get_data( xgl->properties, "transport_callback", NULL );
473 if ( callback != NULL && producer != NULL )
475 keyboard[ 0 ] = ( char )XLookupKeysym( &event.xkey, 0 );
476 callback( producer, keyboard );
483 if ( *XGetAtomName( GLWin.dpy, event.xclient.message_type ) == *"WM_PROTOCOLS" )
500 void start_xgl( consumer_xgl consumer )
504 pthread_mutex_init( &new_frame.mutex, NULL );
505 new_frame.aspect_ratio = 16.0 / 9.0;
507 new_frame.width = STARTWIDTH;
508 new_frame.height = STARTHEIGHT;
509 new_frame.mlt_frame_ref = NULL;
512 xgl->xgl_started = 1;
516 if ( vthread.running ) {
518 pthread_join( vthread.thread, NULL );
523 static void on_consumer_thread_started( mlt_properties owner, HiddenContext* context )
525 // Initialize this thread's OpenGL state
526 glXMakeCurrent( context->dpy, context->win, context->ctx );
527 mlt_events_fire( MLT_FILTER_PROPERTIES(glsl_manager), "init glsl", NULL );
530 /** Forward references to static functions.
533 static int consumer_start( mlt_consumer parent );
534 static int consumer_stop( mlt_consumer parent );
535 static int consumer_is_stopped( mlt_consumer parent );
536 static void consumer_close( mlt_consumer parent );
537 static void *consumer_thread( void * );
541 /** This is what will be called by the factory - anything can be passed in
542 via the argument, but keep it simple.
545 mlt_consumer consumer_xgl_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg )
547 // Create the consumer object
548 consumer_xgl this = calloc( sizeof( struct consumer_xgl_s ), 1 );
550 // If no malloc'd and consumer init ok
551 if ( this != NULL && mlt_consumer_init( &this->parent, this, profile ) == 0 )
554 this->queue = mlt_deque_init( );
556 // Get the parent consumer object
557 mlt_consumer parent = &this->parent;
559 // We have stuff to clean up, so override the close method
560 parent->close = consumer_close;
562 // get a handle on properties
563 mlt_service service = MLT_CONSUMER_SERVICE( parent );
564 this->properties = MLT_SERVICE_PROPERTIES( service );
567 mlt_properties_set( this->properties, "rescale", "bilinear" );
568 mlt_properties_set( this->properties, "deinterlace_method", "onefield" );
570 // default image format
571 mlt_properties_set( this->properties, "mlt_image_format", "glsl" );
573 // Default buffer for low latency
574 mlt_properties_set_int( this->properties, "buffer", 1 );
576 // Ensure we don't join on a non-running object
578 this->xgl_started = 0;
580 // Allow thread to be started/stopped
581 parent->start = consumer_start;
582 parent->stop = consumer_stop;
583 parent->is_stopped = consumer_is_stopped;
585 // "init glsl" is required to instantiate glsl filters.
586 glsl_manager = mlt_factory_filter( profile, "glsl.manager", NULL );
587 if ( glsl_manager ) {
588 mlt_events_listen( this->properties, &hiddenctx, "consumer-thread-started", (mlt_listener) on_consumer_thread_started );
590 mlt_consumer_close( parent );
594 // Return the consumer produced
598 // malloc or consumer init failed
607 int consumer_start( mlt_consumer parent )
609 consumer_xgl this = parent->child;
611 if ( !this->running )
613 consumer_stop( parent );
618 pthread_create( &this->thread, NULL, consumer_thread, this );
626 int consumer_stop( mlt_consumer parent )
628 // Get the actual object
629 consumer_xgl this = parent->child;
631 if ( this->running && this->joined == 0 )
633 // Kill the thread and clean up
638 pthread_join( this->thread, NULL );
646 int consumer_is_stopped( mlt_consumer parent )
648 consumer_xgl this = parent->child;
649 return !this->running;
654 static void *consumer_thread( void *arg )
657 consumer_xgl this = arg;
667 /** Callback to allow override of the close method.
670 static void consumer_close( mlt_consumer parent )
672 // Get the actual object
673 consumer_xgl this = parent->child;
676 ///mlt_consumer_stop( parent );
677 mlt_filter_close( glsl_manager );
679 // Now clean up the rest
680 mlt_consumer_close( parent );
683 mlt_deque_close( this->queue );
685 if ( this->xgl_started )
688 // Finally clean up this