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>
39 #include <sys/syscall.h>
41 #include <framework/mlt.h>
44 #define STARTWIDTH 1280
45 #define STARTHEIGHT 720
48 extern int XInitThreads();
52 typedef struct consumer_xgl_s *consumer_xgl;
56 struct mlt_consumer_s parent;
57 mlt_properties properties;
80 pthread_mutex_t mutex;
82 mlt_frame mlt_frame_ref;
110 XSetWindowAttributes attr;
112 unsigned int width, height;
117 static GLWindow GLWin;
118 static HiddenContext hiddenctx;
120 static frame_new new_frame;
122 static thread_video vthread;
123 static consumer_xgl xgl;
124 static mlt_filter glsl_manager;
128 static void* video_thread( void *arg );
132 int _width = GLWin.width;
133 int _height = GLWin.height;
134 GLfloat left, right, top, bottom;
135 GLfloat war = (GLfloat)_width/(GLfloat)_height;
137 if ( war < new_frame.aspect_ratio ) {
140 top = war / new_frame.aspect_ratio;
141 bottom = -war / new_frame.aspect_ratio;
146 left = -new_frame.aspect_ratio / war;
147 right = new_frame.aspect_ratio / war;
150 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
155 glTranslatef( _width/2, _height/2, 0 );
156 glScalef( _width/2, _height/2, 1.0 );
158 glBindTexture( GL_TEXTURE_2D, fb.texture );
161 glTexCoord2f( 0.0f, 0.0f ); glVertex2f( left, top );
162 glTexCoord2f( 0.0f, 1.0f ); glVertex2f( left, bottom );
163 glTexCoord2f( 1.0f, 1.0f ); glVertex2f( right, bottom );
164 glTexCoord2f( 1.0f, 0.0f ); glVertex2f( right, top );
169 glXSwapBuffers( GLWin.dpy, GLWin.win );
171 if ( !vthread.running ) {
172 pthread_create( &vthread.thread, NULL, video_thread, NULL );
179 static void show_frame()
181 // fprintf(stderr,"show_frame threadID : %ld\n", syscall(SYS_gettid));
183 if ( (fb.width != new_frame.width) || (fb.height != new_frame.height) ) {
184 glDeleteFramebuffers( 1, &fb.fbo );
185 glDeleteTextures( 1, &fb.texture );
187 fb.width = new_frame.width;
188 fb.height = new_frame.height;
189 glGenFramebuffers( 1, &fb.fbo );
190 glGenTextures( 1, &fb.texture );
191 glBindTexture( GL_TEXTURE_2D, fb.texture );
192 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, fb.width, fb.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );
193 glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
194 glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
195 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
196 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
197 glBindFramebuffer( GL_FRAMEBUFFER, fb.fbo );
198 glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb.texture, 0 );
199 glBindFramebuffer( GL_FRAMEBUFFER, 0 );
202 glPushAttrib(GL_VIEWPORT_BIT);
203 glMatrixMode(GL_PROJECTION);
206 glBindFramebuffer( GL_FRAMEBUFFER, fb.fbo );
208 glViewport( 0, 0, new_frame.width, new_frame.height );
209 glMatrixMode( GL_PROJECTION );
211 glOrtho( 0.0, new_frame.width, 0.0, new_frame.height, -1.0, 1.0 );
212 glMatrixMode( GL_MODELVIEW );
215 glActiveTexture( GL_TEXTURE0 );
216 glBindTexture( GL_TEXTURE_2D, new_frame.texture );
219 glTexCoord2f( 0.0f, 0.0f ); glVertex2f( 0.0f, 0.0f );
220 glTexCoord2f( 0.0f, 1.0f ); glVertex2f( 0.0f, new_frame.height );
221 glTexCoord2f( 1.0f, 1.0f ); glVertex2f( new_frame.width, new_frame.height );
222 glTexCoord2f( 1.0f, 0.0f ); glVertex2f( new_frame.width, 0.0f );
225 glBindFramebuffer( GL_FRAMEBUFFER, 0 );
226 mlt_events_fire( MLT_CONSUMER_PROPERTIES(&xgl->parent), "consumer-frame-show", new_frame.mlt_frame_ref, NULL );
227 mlt_frame_close( new_frame.mlt_frame_ref );
228 new_frame.mlt_frame_ref = NULL;
230 glMatrixMode(GL_PROJECTION);
232 glMatrixMode(GL_MODELVIEW);
242 void* video_thread( void *arg )
244 mlt_frame next = NULL;
245 mlt_consumer consumer = &xgl->parent;
246 mlt_properties consumer_props = MLT_CONSUMER_PROPERTIES( consumer );
247 struct timeval start, end;
250 gettimeofday( &start, NULL );
252 while ( vthread.running )
254 // Get a frame from the attached producer
255 next = mlt_consumer_rt_frame( consumer );
257 if ( !mlt_properties_get_int( MLT_FILTER_PROPERTIES( glsl_manager ), "glsl_supported" ) ) {
258 fprintf( stderr, "OpenGL Shading Language is not supported on this machine.\n" );
263 // Ensure that we have a frame
266 mlt_properties properties = MLT_FRAME_PROPERTIES( next );
267 if ( mlt_properties_get_int( properties, "rendered" ) == 1 )
269 // Get the image, width and height
270 mlt_image_format vfmt = mlt_image_glsl_texture;
271 int width = 0, height = 0;
273 int error = mlt_frame_get_image( next, (uint8_t**) &image, &vfmt, &width, &height, 0 );
274 if ( !error && image && width && height && !new_frame.new ) {
275 new_frame.width = width;
276 new_frame.height = height;
277 new_frame.texture = *image;
278 new_frame.mlt_frame_ref = next;
279 new_frame.aspect_ratio = ((double)width / (double)height) * mlt_properties_get_double( properties, "aspect_ratio" );
283 while ( new_frame.new && --loop )
288 mlt_frame_close( next );
292 gettimeofday( &end, NULL );
293 duration = 1000000.0 / mlt_properties_get_double( consumer_props, "fps" );
294 duration -= ( end.tv_sec * 1000000 + end.tv_usec ) - ( start.tv_sec * 1000000 + start.tv_usec );
296 usleep( (int)duration );
297 gettimeofday( &start, NULL );
301 mlt_frame_close( next );
302 static int dropped = 0;
303 mlt_log_info( MLT_CONSUMER_SERVICE(consumer), "dropped video frame %d\n", ++dropped );
309 mlt_consumer_stopped( consumer );
316 static void resizeGLScene()
318 glXMakeCurrent( GLWin.dpy, GLWin.win, GLWin.ctx );
320 if ( GLWin.height == 0 )
322 if ( GLWin.width == 0 )
324 glViewport( 0, 0, GLWin.width, GLWin.height );
325 glMatrixMode( GL_PROJECTION );
327 glOrtho( 0.0, GLWin.width, 0.0, GLWin.height, -1.0, 1.0 );
328 glMatrixMode( GL_MODELVIEW );
335 static void initGL( void )
337 glXMakeCurrent( GLWin.dpy, GLWin.win, GLWin.ctx );
339 glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
340 glClearDepth( 1.0f );
341 glDepthFunc( GL_LEQUAL );
342 glEnable( GL_DEPTH_TEST );
343 glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
344 glEnable( GL_BLEND );
345 glShadeModel( GL_SMOOTH );
346 glEnable( GL_TEXTURE_2D );
347 glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
349 typedef int (*GLXSWAPINTERVALSGI) ( int );
350 GLXSWAPINTERVALSGI mglXSwapInterval = (GLXSWAPINTERVALSGI)glXGetProcAddressARB( (const GLubyte*)"glXSwapIntervalSGI" );
351 if ( mglXSwapInterval )
352 mglXSwapInterval( 1 );
355 fb.width = STARTWIDTH;
356 fb.height = STARTHEIGHT;
357 glGenFramebuffers( 1, &fb.fbo );
358 glGenTextures( 1, &fb.texture );
359 glBindTexture( GL_TEXTURE_2D, fb.texture );
360 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, fb.width, fb.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );
361 glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
362 glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
363 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
364 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
365 glBindFramebuffer( GL_FRAMEBUFFER, fb.fbo );
366 glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb.texture, 0 );
367 glBindFramebuffer( GL_FRAMEBUFFER, 0 );
374 static void createGLWindow()
376 const char* title = "OpenGL consumer";
377 int width = STARTWIDTH;
378 int height = STARTHEIGHT;
380 int attrListSgl[] = { GLX_RGBA, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8,
381 GLX_BLUE_SIZE, 8, GLX_DEPTH_SIZE, 16, None };
383 int attrListDbl[] = { GLX_RGBA, GLX_DOUBLEBUFFER, GLX_RED_SIZE, 8,
384 GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_DEPTH_SIZE, 16, None };
390 unsigned int borderDummy;
392 GLWin.dpy = XOpenDisplay( 0 );
393 GLWin.screen = DefaultScreen( GLWin.dpy );
395 vi = glXChooseVisual( GLWin.dpy, GLWin.screen, attrListDbl );
397 vi = glXChooseVisual( GLWin.dpy, GLWin.screen, attrListSgl );
399 GLWin.ctx = glXCreateContext( GLWin.dpy, vi, 0, GL_TRUE );
401 cmap = XCreateColormap( GLWin.dpy, RootWindow( GLWin.dpy, vi->screen ), vi->visual, AllocNone );
402 GLWin.attr.colormap = cmap;
403 GLWin.attr.border_pixel = 0;
405 GLWin.attr.event_mask = ExposureMask | KeyPressMask | ButtonPressMask | StructureNotifyMask;
406 GLWin.win = XCreateWindow( GLWin.dpy, RootWindow(GLWin.dpy, vi->screen), 0, 0, width, height,
407 0, vi->depth, InputOutput, vi->visual, CWBorderPixel | CWColormap | CWEventMask, &GLWin.attr );
408 wmDelete = XInternAtom( GLWin.dpy, "WM_DELETE_WINDOW", True );
409 XSetWMProtocols( GLWin.dpy, GLWin.win, &wmDelete, 1 );
410 XSetStandardProperties( GLWin.dpy, GLWin.win, title, title, None, NULL, 0, NULL );
411 XMapRaised( GLWin.dpy, GLWin.win );
413 glXMakeCurrent( GLWin.dpy, GLWin.win, GLWin.ctx );
414 XGetGeometry( GLWin.dpy, GLWin.win, &winDummy, &GLWin.x, &GLWin.y,
415 &GLWin.width, &GLWin.height, &borderDummy, &GLWin.depth );
417 fprintf(stderr, "Direct Rendering: %s\n",glXIsDirect( GLWin.dpy, GLWin.ctx ) ? "true" : "false" );
419 // Verify GLSL works on this machine
420 hiddenctx.ctx = glXCreateContext( GLWin.dpy, vi, GLWin.ctx, GL_TRUE );
421 if ( hiddenctx.ctx ) {
422 hiddenctx.dpy = GLWin.dpy;
423 hiddenctx.screen = GLWin.screen;
424 hiddenctx.win = RootWindow( hiddenctx.dpy, hiddenctx.screen );
432 static void killGLWindow()
435 if ( !glXMakeCurrent( GLWin.dpy, None, NULL ) ) {
436 printf("Error releasing drawing context : killGLWindow\n");
438 glXDestroyContext( GLWin.dpy, GLWin.ctx );
443 glXDestroyContext( hiddenctx.dpy, hiddenctx.ctx );
445 XCloseDisplay( GLWin.dpy );
454 while ( xgl->running ) {
455 while ( XPending( GLWin.dpy ) > 0 ) {
456 XNextEvent( GLWin.dpy, &event );
457 switch ( event.type ) {
459 if ( event.xexpose.count != 0 )
462 case ConfigureNotify:
463 if ( (event.xconfigure.width != GLWin.width) || (event.xconfigure.height != GLWin.height) ) {
464 GLWin.width = event.xconfigure.width;
465 GLWin.height = event.xconfigure.height;
470 switch ( XLookupKeysym( &event.xkey, 0 ) ) {
475 mlt_producer producer = mlt_properties_get_data( xgl->properties, "transport_producer", NULL );
476 char keyboard[ 2 ] = " ";
477 void (*callback)( mlt_producer, char * ) = mlt_properties_get_data( xgl->properties, "transport_callback", NULL );
478 if ( callback != NULL && producer != NULL )
480 keyboard[ 0 ] = ( char )XLookupKeysym( &event.xkey, 0 );
481 callback( producer, keyboard );
488 if ( *XGetAtomName( GLWin.dpy, event.xclient.message_type ) == *"WM_PROTOCOLS" )
505 void start_xgl( consumer_xgl consumer )
509 pthread_mutex_init( &new_frame.mutex, NULL );
510 new_frame.aspect_ratio = 16.0 / 9.0;
512 new_frame.width = STARTWIDTH;
513 new_frame.height = STARTHEIGHT;
514 new_frame.mlt_frame_ref = NULL;
520 if ( vthread.running ) {
522 pthread_join( vthread.thread, NULL );
528 static void on_consumer_thread_started( mlt_properties owner, HiddenContext* context )
530 fprintf(stderr, "%s: %ld\n", __FUNCTION__, syscall(SYS_gettid));
531 // Initialize this thread's OpenGL state
532 glXMakeCurrent( context->dpy, context->win, context->ctx );
533 mlt_events_fire( MLT_FILTER_PROPERTIES(glsl_manager), "init glsl", NULL );
536 /** Forward references to static functions.
539 static int consumer_start( mlt_consumer parent );
540 static int consumer_stop( mlt_consumer parent );
541 static int consumer_is_stopped( mlt_consumer parent );
542 static void consumer_close( mlt_consumer parent );
543 static void *consumer_thread( void * );
547 /** This is what will be called by the factory - anything can be passed in
548 via the argument, but keep it simple.
551 mlt_consumer consumer_xgl_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg )
553 fprintf(stderr, "consumer_xgl_init\n");
554 // Create the consumer object
555 consumer_xgl this = calloc( sizeof( struct consumer_xgl_s ), 1 );
557 // If no malloc'd and consumer init ok
558 if ( this != NULL && mlt_consumer_init( &this->parent, this, profile ) == 0 )
561 this->queue = mlt_deque_init( );
563 // Get the parent consumer object
564 mlt_consumer parent = &this->parent;
566 // We have stuff to clean up, so override the close method
567 parent->close = consumer_close;
569 // get a handle on properties
570 mlt_service service = MLT_CONSUMER_SERVICE( parent );
571 this->properties = MLT_SERVICE_PROPERTIES( service );
574 mlt_properties_set( this->properties, "rescale", "bilinear" );
575 mlt_properties_set( this->properties, "deinterlace_method", "onefield" );
577 // default image format
578 mlt_properties_set( this->properties, "mlt_image_format", "glsl" );
580 // Default buffer for low latency
581 mlt_properties_set_int( this->properties, "buffer", 1 );
583 // Ensure we don't join on a non-running object
585 this->xgl_started = 0;
587 // Allow thread to be started/stopped
588 parent->start = consumer_start;
589 parent->stop = consumer_stop;
590 parent->is_stopped = consumer_is_stopped;
592 // "init glsl" is required to instantiate glsl filters.
593 glsl_manager = mlt_factory_filter( profile, "glsl.manager", NULL );
594 if ( glsl_manager ) {
595 mlt_events_listen( this->properties, &hiddenctx, "consumer-thread-started", (mlt_listener) on_consumer_thread_started );
597 mlt_consumer_close( parent );
601 // Return the consumer produced
605 // malloc or consumer init failed
614 int consumer_start( mlt_consumer parent )
616 consumer_xgl this = parent->child;
618 if ( !this->running )
620 consumer_stop( parent );
625 pthread_create( &this->thread, NULL, consumer_thread, this );
633 int consumer_stop( mlt_consumer parent )
635 // Get the actual object
636 consumer_xgl this = parent->child;
638 if ( this->running && this->joined == 0 )
640 // Kill the thread and clean up
645 pthread_join( this->thread, NULL );
653 int consumer_is_stopped( mlt_consumer parent )
655 consumer_xgl this = parent->child;
656 return !this->running;
661 static void *consumer_thread( void *arg )
664 consumer_xgl this = arg;
674 /** Callback to allow override of the close method.
677 static void consumer_close( mlt_consumer parent )
679 // Get the actual object
680 consumer_xgl this = parent->child;
683 ///mlt_consumer_stop( parent );
684 mlt_filter_close( glsl_manager );
686 // Now clean up the rest
687 mlt_consumer_close( parent );
690 mlt_deque_close( this->queue );
692 // Finally clean up this