compositor.cpp

00001 
00002 /* Copyright (c) 2007-2009, Stefan Eilemann <eile@equalizergraphics.com> 
00003  *
00004  * This library is free software; you can redistribute it and/or modify it under
00005  * the terms of the GNU Lesser General Public License version 2.1 as published
00006  * by the Free Software Foundation.
00007  *  
00008  * This library is distributed in the hope that it will be useful, but WITHOUT
00009  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
00010  * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
00011  * details.
00012  * 
00013  * You should have received a copy of the GNU Lesser General Public License
00014  * along with this library; if not, write to the Free Software Foundation, Inc.,
00015  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
00016  */
00017 
00018 #include <pthread.h>
00019 #include <eq/base/perThread.h>
00020 
00021 #include "compositor.h"
00022 
00023 #include "channel.h"
00024 #include "client.h"
00025 #include "channelStatistics.h"
00026 #include "image.h"
00027 #include "log.h"
00028 #include "server.h"
00029 #include "windowSystem.h"
00030 
00031 #include <eq/base/debug.h>
00032 
00033 #include <eq/base/executionListener.h>
00034 #include <eq/base/monitor.h>
00035 
00036 #ifdef EQ_USE_PARACOMP
00037 #  include <pcapi.h>
00038 #endif
00039 
00040 #ifdef WIN32
00041 #  define bzero( ptr, size ) memset( ptr, 0, size );
00042 #endif
00043 
00044 using eq::base::Monitor;
00045 using namespace std;
00046 
00047 namespace eq
00048 {
00049 
00050 #define glewGetContext channel->glewGetContext
00051 
00052 namespace
00053 {
00054 // use to address one shader and program per shared context set
00055 static const char seed = 42;
00056 static const char* shaderDBKey = &seed;
00057 static const char* colorDBKey  = shaderDBKey + 1;
00058 static const char* depthDBKey  = shaderDBKey + 2;
00059 static const char* colorKey    = shaderDBKey + 3;
00060 static const char* depthKey    = shaderDBKey + 4;
00061 
00062 class ResultImage : public Image
00063 {
00064 public:
00065     virtual ~ResultImage() {}
00066 
00067     void notifyPerThreadDelete() { delete this; }
00068 };
00069 
00070 
00071 // Image used for CPU-based assembly
00072 static base::PerThread< ResultImage > _resultImage;
00073 
00074 
00075 static bool _useCPUAssembly( const FrameVector& frames, Channel* channel, 
00076                              const bool blendAlpha = false )
00077 {
00078     // It doesn't make sense to use CPU-assembly for only one frame
00079     if( frames.size() < 2 )
00080         return false;
00081 
00082     // Test that at least two input frames have color and depth buffers or that
00083     // alpha-blended assembly is used with multiple RGBA buffers. We assume then
00084     // that we will have at least one image per frame so most likely it's worth
00085     // to wait for the images and to do a CPU-based assembly.
00086     const uint32_t desiredBuffers = blendAlpha ? Frame::BUFFER_COLOR :
00087                                     Frame::BUFFER_COLOR | Frame::BUFFER_DEPTH;
00088     size_t nFrames = 0;
00089     for( FrameVector::const_iterator i = frames.begin();
00090          i != frames.end(); ++i )
00091     {
00092         const Frame* frame = *i;
00093         if( frame->getPixel() != Pixel::ALL ) // Not supported yet on the CPU
00094             return false;
00095 
00096         if( frame->getBuffers() == desiredBuffers )
00097             ++nFrames;
00098     }
00099     if( nFrames < 2 )
00100         return false;
00101 
00102     // Now wait for all images to be ready and test if our assumption was
00103     // correct, that there are enough images to make a CPU-based assembly
00104     // worthwhile and all other preconditions for our current CPU-based assembly
00105     // code are true.
00106     size_t   nImages     = 0;
00107     uint32_t colorFormat = 0;
00108     uint32_t colorType   = 0;
00109     uint32_t depthFormat = 0;
00110     uint32_t depthType   = 0;
00111 
00112     for( FrameVector::const_iterator i = frames.begin();
00113          i != frames.end(); ++i )
00114     {
00115         const Frame* frame = *i;
00116         {
00117             ChannelStatistics event( Statistic::CHANNEL_WAIT_FRAME, channel );
00118             frame->waitReady();
00119         }
00120 
00121         const vector< Image* >& images = frame->getImages();        
00122         for( vector< Image* >::const_iterator j = images.begin(); 
00123              j != images.end(); ++j )
00124         {
00125             const Image* image = *j;
00126 
00127             const bool hasColor = image->hasPixelData( Frame::BUFFER_COLOR );
00128             const bool hasDepth = image->hasPixelData( Frame::BUFFER_DEPTH );
00129             
00130             if(( blendAlpha && hasColor && image->hasAlpha( )) ||
00131                ( hasColor && hasDepth ))
00132             {
00133                 if( colorFormat == 0 )
00134                 {
00135                     colorFormat = image->getFormat( Frame::BUFFER_COLOR );
00136                     colorType   = image->getType(   Frame::BUFFER_COLOR );
00137 
00138                     if (( colorType == GL_HALF_FLOAT ) || 
00139                         ( colorType == GL_FLOAT ))
00140                         return false;
00141                 }
00142 
00143                 if( colorFormat != image->getFormat( Frame::BUFFER_COLOR ) ||
00144                     colorType   != image->getType(   Frame::BUFFER_COLOR ))
00145 
00146                     return false;
00147 
00148                 if( image->hasPixelData( Frame::BUFFER_DEPTH ))
00149                 {
00150                     if( depthFormat == 0 )
00151                     {
00152                         depthFormat = image->getFormat( Frame::BUFFER_DEPTH );
00153                         depthType   = image->getType(   Frame::BUFFER_DEPTH );
00154                     }
00155 
00156                     if( depthFormat != image->getFormat(Frame::BUFFER_DEPTH ) ||
00157                         depthType   != image->getType(  Frame::BUFFER_DEPTH ))
00158 
00159                         return false;
00160                 }
00161 
00162                 ++nImages;
00163             }
00164         }
00165 
00166         if( nImages > 1 ) // early-out to reduce wait time
00167             return true;
00168     }
00169 
00170     return false;
00171 }
00172 }
00173 
00174 void Compositor::assembleFrames( const FrameVector& frames,
00175                                  Channel* channel )
00176 {
00177     if( frames.empty( ))
00178         return;
00179 
00180     if( _useCPUAssembly( frames, channel ))
00181         assembleFramesCPU( frames, channel );
00182     else
00183         assembleFramesUnsorted( frames, channel );
00184 }
00185 
00186 void Compositor::assembleFramesSorted( const FrameVector& frames,
00187                                        Channel* channel, const bool blendAlpha )
00188 {
00189     if( frames.empty( ))
00190         return;
00191 
00192     if( blendAlpha )
00193     {
00194         glEnable( GL_BLEND );
00195         EQASSERT( GLEW_EXT_blend_func_separate );
00196         glBlendFuncSeparate( GL_ONE, GL_SRC_ALPHA, GL_ZERO, GL_SRC_ALPHA );
00197     }
00198 
00199     if( _useCPUAssembly( frames, channel, blendAlpha ))
00200         assembleFramesCPU( frames, channel, blendAlpha );
00201     else
00202     {
00203         for( FrameVector::const_iterator i = frames.begin();
00204              i != frames.end(); ++i )
00205         {
00206             Frame* frame = *i;
00207             {
00208                 ChannelStatistics event( Statistic::CHANNEL_WAIT_FRAME,
00209                                          channel );
00210                 frame->waitReady( );
00211             }
00212             assembleFrame( frame, channel );
00213         }
00214     }
00215 
00216     if( blendAlpha )
00217         glDisable( GL_BLEND );
00218 }
00219 
00220 void Compositor::assembleFramesUnsorted( const FrameVector& frames, 
00221                                          Channel* channel )
00222 {
00223     if( frames.empty( ))
00224         return;
00225 
00226     EQVERB << "Unsorted GPU assembly" << endl;
00227     // This is an optimized assembly version. The frames are not assembled in
00228     // the saved order, but in the order they become available, which is faster
00229     // because less time is spent waiting on frame availability.
00230     //
00231     // The ready frames are counted in a monitor. Whenever a frame becomes
00232     // available, it increments the monitor which causes this code to wake up
00233     // and assemble it.
00234 
00235     // register monitor with all input frames
00236     Monitor<uint32_t> monitor;
00237     for( FrameVector::const_iterator i = frames.begin();
00238          i != frames.end(); ++i )
00239     {
00240         Frame* frame = *i;
00241         frame->addListener( monitor );
00242     }
00243 
00244     uint32_t    nUsedFrames  = 0;
00245     FrameVector unusedFrames = frames;
00246 
00247     // wait and assemble frames
00248     while( !unusedFrames.empty( ))
00249     {
00250         {
00251             ChannelStatistics event( Statistic::CHANNEL_WAIT_FRAME, channel );
00252             monitor.waitGE( ++nUsedFrames );
00253         }
00254 
00255         for( FrameVector::iterator i = unusedFrames.begin();
00256              i != unusedFrames.end(); ++i )
00257         {
00258             Frame* frame = *i;
00259             if( !frame->isReady( ))
00260                 continue;
00261 
00262             assembleFrame( frame, channel );
00263             unusedFrames.erase( i );
00264             break;
00265         }
00266     }
00267 
00268     // de-register the monitor
00269     for( FrameVector::const_iterator i = frames.begin(); i != frames.end();
00270          ++i )
00271     {
00272         Frame* frame = *i;
00273         // syncAssembleFrame( frame );
00274         frame->removeListener( monitor );
00275     }
00276 }
00277 
00278 void Compositor::assembleFramesCPU( const FrameVector& frames,
00279                                     Channel* channel, const bool blendAlpha )
00280 {
00281     if( frames.empty( ))
00282         return;
00283 
00284     EQVERB << "Sorted CPU assembly" << endl;
00285     // Assembles images from DB and 2D compounds using the CPU and then
00286     // assembles the result image. Does not yet support Pixel or Eye
00287     // compounds.
00288 
00289     const Image* result = mergeFramesCPU( frames, blendAlpha );
00290     if( !result )
00291         return;
00292 
00293     // assemble result on dest channel
00294     ImageOp operation;
00295     operation.channel = channel;
00296     operation.buffers = Frame::BUFFER_COLOR | Frame::BUFFER_DEPTH;
00297     assembleImage( result, operation );
00298 
00299 #if 0
00300     static uint32_t counter = 0;
00301     ostringstream stringstream;
00302     stringstream << "Image_" << ++counter;
00303     result->writeImages( stringstream.str( ));
00304 #endif
00305 }
00306 
00307 const Image* Compositor::mergeFramesCPU( const FrameVector& frames,
00308                                          const bool blendAlpha )
00309 {
00310     EQVERB << "Sorted CPU assembly" << endl;
00311 
00312     // Collect input image information and check preconditions
00313     PixelViewport destPVP;
00314     uint32_t colorFormat = GL_NONE;
00315     uint32_t colorType   = GL_NONE;
00316     uint32_t depthFormat = GL_NONE;
00317     uint32_t depthType   = GL_NONE;
00318 
00319     if( !_collectOutputData( frames, destPVP, 
00320                              colorFormat, colorType, depthFormat, depthType ))
00321     {
00322         return 0;
00323     }
00324 
00325     // prepare output image
00326     Image* result = _resultImage.get();
00327     if( !result )
00328     {
00329         _resultImage = new ResultImage;
00330         result       = _resultImage.get();
00331     }
00332 
00333     // pre-condition check for current _merge implementations
00334     EQASSERT( colorFormat != GL_NONE );
00335     EQASSERT( colorType   != GL_NONE );
00336 
00337     result->setFormat( Frame::BUFFER_COLOR, colorFormat );
00338     result->setType(   Frame::BUFFER_COLOR, colorType );
00339     result->setPixelViewport( destPVP );
00340     result->clearPixelData( Frame::BUFFER_COLOR );
00341 
00342     void* destDepth = 0;
00343     if( depthFormat != GL_NONE ) // at least one depth assembly
00344     {
00345         EQASSERT( depthFormat == GL_DEPTH_COMPONENT );
00346         EQASSERT( depthType   == GL_UNSIGNED_INT );
00347 
00348         result->setFormat( Frame::BUFFER_DEPTH, depthFormat );
00349         result->setType(   Frame::BUFFER_DEPTH, depthType );
00350         result->clearPixelData( Frame::BUFFER_DEPTH );
00351         destDepth = result->getPixelPointer( Frame::BUFFER_DEPTH );
00352     }
00353 
00354     // assembly
00355     _mergeFrames( frames, blendAlpha, 
00356                   result->getPixelPointer( Frame::BUFFER_COLOR ),
00357                   destDepth, destPVP );
00358     return result;
00359 }
00360 
00361 bool Compositor::_collectOutputData( const FrameVector& frames,
00362                                      PixelViewport& destPVP, 
00363                                      uint32_t& colorFormat, uint32_t& colorType,
00364                                      uint32_t& depthFormat, uint32_t& depthType)
00365 {
00366     for( FrameVector::const_iterator i = frames.begin(); i != frames.end(); ++i)
00367     {
00368         Frame* frame = *i;
00369         frame->waitReady();
00370 
00371         EQASSERTINFO( frame->getPixel() == Pixel::ALL,
00372                       "CPU-based pixel recomposition not implemented" );
00373         if( frame->getPixel() != Pixel::ALL )
00374             return false;
00375 
00376         const vector< Image* >& images = frame->getImages();        
00377         for( vector< Image* >::const_iterator j = images.begin(); 
00378              j != images.end(); ++j )
00379         {
00380             const Image* image = *j;
00381             EQASSERT( image->getStorageType() == Frame::TYPE_MEMORY );
00382             if( image->getStorageType() != Frame::TYPE_MEMORY )
00383                 return false;
00384 
00385             if( !image->hasPixelData( Frame::BUFFER_COLOR ))
00386                 continue;
00387 
00388             destPVP.merge( image->getPixelViewport() + frame->getOffset( ));
00389 
00390             EQASSERT( colorFormat == GL_NONE ||
00391                       colorFormat == image->getFormat( Frame::BUFFER_COLOR ));
00392             EQASSERT( colorType == GL_NONE ||
00393                       colorType == image->getType( Frame::BUFFER_COLOR ));
00394 
00395             colorFormat = image->getFormat( Frame::BUFFER_COLOR );
00396             colorType   = image->getType( Frame::BUFFER_COLOR );
00397 
00398             if( image->hasPixelData( Frame::BUFFER_DEPTH ))
00399             {
00400                 EQASSERT( depthFormat == GL_NONE ||
00401                           depthFormat == image->getFormat(Frame::BUFFER_DEPTH));
00402                 EQASSERT( depthType == GL_NONE ||
00403                           depthType == image->getType( Frame::BUFFER_DEPTH ));
00404 
00405                 depthFormat = image->getFormat( Frame::BUFFER_DEPTH );
00406                 depthType   = image->getType( Frame::BUFFER_DEPTH );
00407             }
00408         }
00409     }
00410 
00411     if( !destPVP.hasArea( ))
00412     {
00413         EQWARN << "Nothing to assemble: " << destPVP << endl;
00414         return false;
00415     }
00416 
00417     return true;
00418 }
00419 
00420 bool Compositor::mergeFramesCPU( const FrameVector& frames,
00421                                  const bool blendAlpha, void* colorBuffer,
00422                                  const uint32_t colorBufferSize,
00423                                  void* depthBuffer,
00424                                  const uint32_t depthBufferSize,
00425                                  PixelViewport& outPVP )
00426 {
00427     EQASSERT( colorBuffer );
00428     EQVERB << "Sorted CPU assembly" << endl;
00429     
00430     // Collect input image information and check preconditions
00431     uint32_t colorFormat = GL_NONE;
00432     uint32_t colorType   = GL_NONE;
00433     uint32_t depthFormat = GL_NONE;
00434     uint32_t depthType   = GL_NONE;
00435     outPVP.invalidate();
00436 
00437     if( !_collectOutputData( frames, outPVP, 
00438                              colorFormat, colorType, depthFormat, depthType ))
00439     {
00440         return false;
00441     }
00442 
00443     // pre-condition check for current _merge implementations
00444     EQASSERT( colorFormat != GL_NONE );
00445     EQASSERT( colorType   != GL_NONE );
00446 
00447     // check output buffers
00448     const uint32_t area = outPVP.getArea();
00449     const uint32_t colorPixelDepth = (colorFormat == GL_RGB) ? 3 : 4;
00450 
00451     if( colorBufferSize < area * colorPixelDepth )
00452     {
00453         EQWARN << "Color output buffer to small" << endl;
00454         return false;
00455     }
00456 
00457     if( depthFormat != GL_NONE ) // at least one depth assembly
00458     {
00459         EQASSERT( depthBuffer );
00460         EQASSERT( depthFormat == GL_DEPTH_COMPONENT );
00461         EQASSERT( depthType   == GL_UNSIGNED_INT );
00462 
00463         if( !depthBuffer )
00464         {
00465             EQWARN << "No depth output buffer provided" << endl;
00466             return false;
00467         }
00468 
00469         if( depthBufferSize < area * 4 )
00470         {
00471             EQWARN << "Depth output buffer to small" << endl;
00472             return false;
00473         }
00474     }
00475 
00476     // assembly
00477     _mergeFrames( frames, blendAlpha, colorBuffer, depthBuffer, outPVP );
00478     return true;
00479 }
00480 
00481 void Compositor::_mergeFrames( const FrameVector& frames,
00482                                const bool blendAlpha, 
00483                                void* colorBuffer, void* depthBuffer,
00484                                const PixelViewport& destPVP )
00485 {
00486     for( FrameVector::const_iterator i = frames.begin(); i != frames.end(); ++i)
00487     {
00488         const Frame* frame = *i;
00489         const ImageVector& images = frame->getImages();        
00490         for( ImageVector::const_iterator j = images.begin();
00491              j != images.end(); ++j )
00492         {
00493             const Image* image = *j;
00494 
00495             if( !image->hasPixelData( Frame::BUFFER_COLOR ))
00496                 continue;
00497 
00498             if( image->hasPixelData( Frame::BUFFER_DEPTH ))
00499                 _mergeDBImage( colorBuffer, depthBuffer, destPVP,
00500                                image, frame->getOffset( ));
00501             else if( blendAlpha && image->hasAlpha( ))
00502                 _mergeBlendImage( colorBuffer, destPVP, 
00503                                   image, frame->getOffset( ));
00504             else
00505                 _merge2DImage( colorBuffer, depthBuffer, destPVP, 
00506                                image, frame->getOffset());
00507         }
00508     }
00509 }
00510 
00511 
00512 void Compositor::_mergeDBImage( void* destColor, void* destDepth,
00513                                 const PixelViewport& destPVP,
00514                                 const Image* image, 
00515                                 const Vector2i& offset )
00516 {
00517     EQASSERT( destColor && destDepth );
00518 
00519     EQVERB << "CPU-DB assembly" << endl;
00520 
00521     uint32_t* destC = reinterpret_cast< uint32_t* >( destColor );
00522     uint32_t* destD = reinterpret_cast< uint32_t* >( destDepth );
00523 
00524     const PixelViewport&  pvp    = image->getPixelViewport();
00525 
00526 #ifdef EQ_USE_PARACOMP_DEPTH
00527     if( pvp == destPVP && offset == vmml::Vector2i::ZERO )
00528     {
00529         // Use Paracomp to composite
00530         if( _mergeImage_PC( PC_COMP_DEPTH, destColor, destDepth, image ))
00531             return;
00532 
00533         EQWARN << "Paracomp compositing failed, using fallback" << endl;
00534     }
00535 #endif
00536 
00537     const int32_t         destX  = offset.x() + pvp.x - destPVP.x;
00538     const int32_t         destY  = offset.y() + pvp.y - destPVP.y;
00539 
00540     const uint32_t* color = reinterpret_cast< const uint32_t* >
00541         ( image->getPixelPointer( Frame::BUFFER_COLOR ));
00542     const uint32_t* depth = reinterpret_cast< const uint32_t* >
00543         ( image->getPixelPointer( Frame::BUFFER_DEPTH ));
00544 
00545 #ifdef EQ_USE_OPENMP
00546 #  pragma omp parallel for
00547 #endif
00548     for( int32_t y = 0; y < pvp.h; ++y )
00549     {
00550         const uint32_t skip =  (destY + y) * destPVP.w + destX;
00551         uint32_t* destColorIt = destC + skip;
00552         uint32_t* destDepthIt = destD + skip;
00553         const uint32_t* colorIt = color + y * pvp.w;
00554         const uint32_t* depthIt = depth + y * pvp.w;
00555 
00556         for( int32_t x = 0; x < pvp.w; ++x )
00557         {
00558             if( *destDepthIt > *depthIt )
00559             {
00560                 *destColorIt = *colorIt;
00561                 *destDepthIt = *depthIt;
00562             }
00563             
00564             ++destColorIt;
00565             ++destDepthIt;
00566             ++colorIt;
00567             ++depthIt;
00568         }
00569     }
00570 }
00571 
00572 void Compositor::_merge2DImage( void* destColor, void* destDepth,
00573                                 const eq::PixelViewport& destPVP,
00574                                 const Image* image,
00575                                 const Vector2i& offset )
00576 {
00577     // This is mostly copy&paste code from _mergeDBImage :-/
00578     EQVERB << "CPU-2D assembly" << endl;
00579 
00580     uint8_t* destC = reinterpret_cast< uint8_t* >( destColor );
00581     uint8_t* destD = reinterpret_cast< uint8_t* >( destDepth );
00582 
00583     const PixelViewport&  pvp    = image->getPixelViewport();
00584     const int32_t         destX  = offset.x() + pvp.x - destPVP.x;
00585     const int32_t         destY  = offset.y() + pvp.y - destPVP.y;
00586 
00587     EQASSERT( image->hasPixelData( Frame::BUFFER_COLOR ));
00588 
00589     const uint8_t*   color = image->getPixelPointer( Frame::BUFFER_COLOR );
00590     const size_t pixelSize = image->getDepth( Frame::BUFFER_COLOR );
00591     const size_t rowLength = pvp.w * pixelSize;
00592 
00593 #ifdef EQ_USE_OPENMP
00594 #  pragma omp parallel for
00595 #endif
00596     for( int32_t y = 0; y < pvp.h; ++y )
00597     {
00598         const size_t skip = ( (destY + y) * destPVP.w + destX ) * pixelSize;
00599         memcpy( destC + skip, color + y * pvp.w * pixelSize, rowLength);
00600         if( destD ) // clear depth, for depth-assembly into existing FB
00601             bzero( destD + skip, rowLength );
00602     }
00603 }
00604 
00605 
00606 void Compositor::_mergeBlendImage( void* dest, const eq::PixelViewport& destPVP,
00607                                    const Image* image,
00608                                    const Vector2i& offset )
00609 {
00610     EQVERB << "CPU-Blend assembly"<< endl;
00611 
00612     int32_t* destColor = reinterpret_cast< int32_t* >( dest );
00613 
00614     const PixelViewport&  pvp    = image->getPixelViewport();
00615     const int32_t         destX  = offset.x() + pvp.x - destPVP.x;
00616     const int32_t         destY  = offset.y() + pvp.y - destPVP.y;
00617 
00618     EQASSERT( image->getDepth( Frame::BUFFER_COLOR ) == 4 );
00619     EQASSERT( image->hasPixelData( Frame::BUFFER_COLOR ));
00620     EQASSERT( image->hasAlpha( ));
00621     
00622 #ifdef EQ_USE_PARACOMP_BLEND
00623     if( pvp == destPVP && offset == vmml::Vector2i::ZERO )
00624     { 
00625         // Use Paracomp to composite
00626         if( !_mergeImage_PC( PC_COMP_ALPHA_SORT2_HP, dest, 0, image ))
00627             EQWARN << "Paracomp compositing failed, using fallback" << endl;
00628         else
00629             continue; // Go to next input image
00630     }
00631 #endif
00632 
00633     const int32_t* color = reinterpret_cast< const int32_t* >
00634                                ( image->getPixelPointer( Frame::BUFFER_COLOR ));
00635 
00636     // Blending of two slices, none of which is on final image (i.e. result
00637     // could be blended on to something else) should be performed with:
00638     // glBlendFuncSeparate( GL_ONE, GL_SRC_ALPHA, GL_ZERO, GL_SRC_ALPHA )
00639     // which means:
00640     // dstColor = 1*srcColor + srcAlpha*dstColor
00641     // dstAlpha = 0*srcAlpha + srcAlpha*dstAlpha
00642     // because we accumulate light which is go through (= 1-Alpha) and we
00643     // already have colors as Alpha*Color
00644 
00645     int32_t* destColorStart = destColor + destY*destPVP.w + destX;
00646     const uint32_t step = sizeof( int32_t );
00647 
00648 #ifdef EQ_USE_OPENMP
00649 #  pragma omp parallel for
00650 #endif
00651     for( int32_t y = 0; y < pvp.h; ++y )
00652     {
00653         const unsigned char* src =
00654             reinterpret_cast< const uint8_t* >( color + pvp.w * y );
00655         unsigned char*       dst =
00656             reinterpret_cast< uint8_t* >( destColorStart + destPVP.w * y );
00657 
00658         for( int32_t x = 0; x < pvp.w; ++x )
00659         {
00660             dst[0] = EQ_MIN( src[0] + (src[3]*dst[0] >> 8), 255 );
00661             dst[1] = EQ_MIN( src[1] + (src[3]*dst[1] >> 8), 255 );
00662             dst[2] = EQ_MIN( src[2] + (src[3]*dst[2] >> 8), 255 );
00663             dst[3] =                   src[3]*dst[3] >> 8;
00664 
00665             src += step;
00666             dst += step;
00667         }
00668     }
00669 }
00670 
00671 #ifdef EQ_USE_PARACOMP
00672 namespace
00673 {
00674 static unsigned glToPCFormat( const unsigned glFormat, const unsigned glType )
00675 {
00676     switch( glFormat )
00677     {
00678         case GL_RGBA:
00679         case GL_RGBA8:
00680             if( glType == GL_UNSIGNED_BYTE )
00681                 return PC_PF_RGBA8;
00682             break;
00683 
00684         case GL_BGRA:
00685             if( glType == GL_UNSIGNED_BYTE )
00686                 return PC_PF_BGRA8;
00687             break;
00688 
00689         case GL_BGR:
00690             if( glType == GL_UNSIGNED_BYTE )
00691                 return PC_PF_BGR8;
00692             break;
00693 
00694         case GL_DEPTH_COMPONENT:
00695             if( glType == GL_FLOAT )
00696                 return PC_PF_Z32F;
00697             break;
00698     }
00699 
00700     return 0;
00701 }
00702 }
00703 #endif
00704 
00705 bool Compositor::_mergeImage_PC( int operation, void* destColor, 
00706                                  void* destDepth, const Image* source )
00707 {
00708 #ifdef EQ_USE_PARACOMP
00709     const unsigned colorFormat = 
00710         glToPCFormat( source->getFormat( Frame::BUFFER_COLOR ),
00711                       source->getType(   Frame::BUFFER_COLOR ));
00712 
00713     if( colorFormat == 0 )
00714     {
00715         EQWARN << "Format or type of image not supported by Paracomp" << endl;
00716         return false;
00717     }
00718 
00719     PCchannel input[2];
00720     PCchannel output[2];
00721     
00722     input[0].pixelFormat  = colorFormat;
00723     input[0].size         = source->getDepth( Frame::BUFFER_COLOR );
00724     output[0].pixelFormat = colorFormat;
00725     output[0].size        = source->getDepth( Frame::BUFFER_COLOR );
00726 
00727     const PixelViewport& pvp = source->getPixelViewport();
00728     EQASSERT( pvp == source->getPixelViewport( ));
00729 
00730     input[0].xOffset   = 0;
00731     input[0].yOffset   = 0;
00732     input[0].width     = pvp.w;
00733     input[0].height    = pvp.h;
00734     input[0].rowLength = pvp.w * input[0].size;
00735     input[0].address   = source->getPixelPointer( Frame::BUFFER_COLOR );
00736 
00737     output[0].xOffset   = 0;
00738     output[0].yOffset   = 0;
00739     output[0].width     = pvp.w;
00740     output[0].height    = pvp.h;
00741     output[0].rowLength = pvp.w * output[0].size;
00742     output[0].address   = destColor
00743 
00744     
00745     const bool useDepth = ( operation == PC_COMP_DEPTH );
00746     if( useDepth )
00747     {
00748         const unsigned depthFormat = 
00749             glToPCFormat( source->getFormat( Frame::BUFFER_DEPTH ),
00750                           source->getType(   Frame::BUFFER_DEPTH ));
00751 
00752         if( depthFormat == 0 )
00753         {
00754             EQWARN << "Format or type of image not supported by Paracomp" 
00755                    << endl;
00756             return false;
00757         }
00758 
00759         input[1].pixelFormat  = depthFormat;
00760         input[1].size         = source->getDepth( Frame::BUFFER_DEPTH );
00761         output[1].pixelFormat = depthFormat;
00762         output[1].size        = source->getDepth( Frame::BUFFER_DEPTH );
00763 
00764         input[1].xOffset   = 0;
00765         input[1].yOffset   = 0;
00766         input[1].width     = pvp.w;
00767         input[1].height    = pvp.h;
00768         input[1].rowLength = pvp.w * input[1].size;
00769         input[1].address   = source->getPixelPointer( Frame::BUFFER_DEPTH );
00770 
00771         output[1].xOffset   = 0;
00772         output[1].yOffset   = 0;
00773         output[1].width     = pvp.w;
00774         output[1].height    = pvp.h;
00775         output[1].rowLength = pvp.w * output[1].size;
00776         output[1].address   = destDepth;
00777     }
00778 
00779 
00780     PCchannel* inputImages[2]     = { output, input };
00781     PCchannel* outputImage[1]     = { output };
00782     PCuint     nInputChannels[2]  = { 1, 1 };
00783     PCuint     nOutputChannels[1] = { 1 };
00784 
00785     if( useDepth )
00786     {
00787         nInputChannels[ 0 ]  = 2;
00788         nInputChannels[ 1 ]  = 2;
00789         nOutputChannels[ 0 ] = 2;
00790     }
00791 
00792     const PCerr error = pcCompositeEXT( operation, 
00793                                         2, nInputChannels, inputImages,
00794                                         1, nOutputChannels, outputImage );
00795     if( error != PC_NO_ERROR )
00796     {
00797         EQWARN << "Paracomp compositing failed: " << error << endl;
00798         return false;
00799     }
00800 
00801     EQINFO << "Paracomp compositing successful" << endl;
00802     return true;
00803 #else
00804     return false;
00805 #endif
00806 }
00807 
00808 
00809 void Compositor::assembleFrame( const Frame* frame, Channel* channel )
00810 {
00811     const vector< Image* >& images = frame->getImages();
00812     if( images.empty( ))
00813         EQINFO << "No images to assemble" << endl;
00814 
00815     ImageOp operation;
00816     operation.channel = channel;
00817     operation.buffers = frame->getBuffers();
00818     operation.offset  = frame->getOffset();
00819     operation.pixel   = frame->getPixel();
00820     operation.zoom    = frame->getZoom();
00821 
00822     for( vector< Image* >::const_iterator i = images.begin(); 
00823          i != images.end(); ++i )
00824     {
00825         const Image* image = *i;
00826         assembleImage( image, operation );
00827     }
00828 }
00829 
00830 void Compositor::assembleImage( const Image* image, const ImageOp& op )
00831 {
00832     ImageOp operation = op;
00833     operation.buffers = Frame::BUFFER_NONE;
00834 
00835     if( op.buffers&Frame::BUFFER_COLOR && image->hasData( Frame::BUFFER_COLOR ))
00836         operation.buffers |= Frame::BUFFER_COLOR;
00837 
00838     if( op.buffers&Frame::BUFFER_DEPTH && image->hasData( Frame::BUFFER_DEPTH ))
00839         operation.buffers |= Frame::BUFFER_DEPTH;
00840 
00841     if( operation.buffers == Frame::BUFFER_NONE )
00842     {
00843         EQWARN << "No image attachment buffers to assemble" << endl;
00844         return;
00845     }
00846 
00847     setupStencilBuffer( image, operation );
00848 
00849     if( operation.buffers == Frame::BUFFER_COLOR )
00850         assembleImage2D( image, operation );
00851     else if( operation.buffers == ( Frame::BUFFER_COLOR | Frame::BUFFER_DEPTH ))
00852         assembleImageDB( image, operation );
00853     else
00854         EQWARN << "Don't know how to assemble using buffers " 
00855                << operation.buffers << endl;
00856 }
00857 
00858 void Compositor::setupStencilBuffer( const Image* image, const ImageOp& op )
00859 {
00860     if( op.pixel == Pixel::ALL )
00861         return;
00862 
00863     // mark stencil buffer where pixel shall not pass
00864     // TODO: OPT!
00865     glClear( GL_STENCIL_BUFFER_BIT );
00866     glEnable( GL_STENCIL_TEST );
00867     glEnable( GL_DEPTH_TEST );
00868 
00869     glStencilFunc( GL_ALWAYS, 1, 1 );
00870     glStencilOp( GL_REPLACE, GL_REPLACE, GL_REPLACE );
00871 
00872     glLineWidth( 1.0f );
00873     glDepthMask( false );
00874     glColorMask( false, false, false, false );
00875     
00876     const PixelViewport& pvp    = image->getPixelViewport();
00877 
00878     glPixelZoom( static_cast< float >( op.pixel.w ),
00879                  static_cast< float >( op.pixel.h ));
00880 
00881     if( op.pixel.w > 1 )
00882     {
00883         const float width  = static_cast< float >( pvp.w * op.pixel.w );
00884         const float step   = static_cast< float >( op.pixel.w );
00885 
00886         const float startX = 
00887             static_cast< float >( op.offset.x() + pvp.x ) + 0.5f - 
00888             static_cast< float >( op.pixel.w );
00889         const float endX   = startX + width + op.pixel.w + step;
00890 
00891         const float startY = 
00892             static_cast< float >( op.offset.y() + pvp.y + op.pixel.y );
00893         const float endY   = static_cast< float >( startY + pvp.h*op.pixel.h );
00894 
00895         glBegin( GL_QUADS );
00896         for( float x = startX + op.pixel.x + 1.0f ; x < endX; x += step)
00897         {
00898             glVertex3f( x-step, startY, 0.0f );
00899             glVertex3f( x-1.0f, startY, 0.0f );
00900             glVertex3f( x-1.0f, endY, 0.0f );        
00901             glVertex3f( x-step, endY, 0.0f );        
00902         }
00903         glEnd();
00904     }
00905     if( op.pixel.h > 1 )
00906     {
00907         const float height = static_cast< float >( pvp.h * op.pixel.h );
00908         const float step   = static_cast< float >( op.pixel.h );
00909 
00910         const float startX = 
00911             static_cast< float >( op.offset.x() + pvp.x + op.pixel.x );
00912         const float endX   = static_cast< float >( startX + pvp.w*op.pixel.w );
00913 
00914         const float startY = 
00915             static_cast< float >( op.offset.y() + pvp.y ) + 0.5f - 
00916             static_cast< float >( op.pixel.h );
00917         const float endY   = startY + height + op.pixel.h + step;
00918 
00919         glBegin( GL_QUADS );
00920         for( float y = startY + op.pixel.y; y < endY; y += step)
00921         {
00922             glVertex3f( startX, y-step, 0.0f );
00923             glVertex3f( endX,   y-step, 0.0f );        
00924             glVertex3f( endX,   y-1.0f, 0.0f );        
00925             glVertex3f( startX, y-1.0f, 0.0f );
00926         }
00927         glEnd();
00928     }
00929     
00930     glDisable( GL_DEPTH_TEST );
00931     glStencilFunc( GL_EQUAL, 0, 1 );
00932     glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );
00933     
00934     const ColorMask& colorMask = op.channel->getDrawBufferMask();
00935     glColorMask( colorMask.red, colorMask.green, colorMask.blue, true );
00936     glDepthMask( true );
00937 }
00938 
00939 void Compositor::assembleImage2D( const Image* image, const ImageOp& op )
00940 {
00941     EQASSERT( image->hasData( Frame::BUFFER_COLOR ));
00942     _drawPixels( image, op, Frame::BUFFER_COLOR );
00943 }
00944 
00945 void Compositor::_drawPixels( const Image* image, 
00946                               const ImageOp& op,
00947                               const Frame::Buffer which )
00948 {
00949     const PixelViewport& pvp = image->getPixelViewport();
00950     EQLOG( LOG_ASSEMBLY ) << "_drawPixels " << pvp << " offset " << op.offset
00951                           << endl;
00952     
00953     if ( image->getStorageType() == Frame::TYPE_MEMORY )
00954     {
00955         EQASSERT( image->hasPixelData( which ));
00956 
00957         if( op.zoom == eq::Zoom::NONE )
00958         {
00959             glRasterPos2i( op.offset.x() + pvp.x, op.offset.y() + pvp.y );
00960             glDrawPixels( pvp.w, pvp.h, 
00961                           image->getFormat( which ), 
00962                           image->getType( which ), 
00963                           image->getPixelPointer( which ));
00964             return;
00965         }
00966         // else use texture with filtering to zoom
00967 
00968         Channel* channel = op.channel; // needed for glewGetContext
00969         Window::ObjectManager* objects = channel->getObjectManager();
00970 
00971         Texture* texture = objects->obtainEqTexture(
00972             which == Frame::BUFFER_COLOR ? colorKey : depthKey );
00973 
00974         texture->upload( image, which );
00975     }
00976     else // texture image
00977     {
00978         EQASSERT( image->hasTextureData( which ));
00979         image->getTexture( which ).bind();
00980     }
00981 
00982     if ( which == Frame::BUFFER_COLOR )
00983         glDepthMask( false );
00984     else
00985     {
00986         EQASSERT( which == Frame::BUFFER_DEPTH )
00987         glColorMask( false, false, false, false );
00988     }
00989 
00990     glDisable( GL_LIGHTING );
00991     glEnable( GL_TEXTURE_RECTANGLE_ARB );
00992     glTexParameteri( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S,
00993                      GL_CLAMP_TO_EDGE );
00994     glTexParameteri( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T,
00995                      GL_CLAMP_TO_EDGE );
00996 
00997     if( op.zoom == eq::Zoom::NONE )
00998     {
00999         glTexParameteri( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER,
01000                          GL_NEAREST );
01001         glTexParameteri( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER,
01002                          GL_NEAREST );
01003     }else
01004     {
01005         glTexParameteri( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER,
01006                          GL_LINEAR );
01007         glTexParameteri( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER,
01008                          GL_LINEAR );
01009     }
01010 
01011     glColor3f( 1.0f, 1.0f, 1.0f );
01012 
01013     const float startX = static_cast< float >
01014          ( op.offset.x() + pvp.x * op.pixel.w + op.pixel.x ) * op.zoom.x();
01015     const float endX   = static_cast< float >
01016          ( op.offset.x() + (pvp.x + pvp.w) * op.pixel.w + op.pixel.x ) *op.zoom.x();
01017     const float startY = static_cast< float >
01018          ( op.offset.y() + pvp.y * op.pixel.h + op.pixel.y ) * op.zoom.y();
01019     const float endY   = static_cast< float >
01020          ( op.offset.y() + (pvp.y + pvp.h) * op.pixel.h + op.pixel.y ) *op.zoom.y();
01021 
01022     glBegin( GL_QUADS );
01023         glTexCoord2f( 0.0f, 0.0f );
01024         glVertex3f( startX, startY, 0.0f );
01025 
01026         glTexCoord2f( static_cast< float >( pvp.w ), 0.0f );
01027         glVertex3f( endX, startY, 0.0f );
01028 
01029         glTexCoord2f( static_cast<float>( pvp.w ), static_cast<float>( pvp.h ));
01030         glVertex3f( endX, endY, 0.0f );
01031         
01032         glTexCoord2f( 0.0f, static_cast< float >( pvp.h ));
01033         glVertex3f( startX, endY, 0.0f );
01034     glEnd();
01035 
01036     // restore state
01037     glDisable( GL_TEXTURE_RECTANGLE_ARB );
01038 
01039     if ( which == Frame::BUFFER_COLOR )
01040         glDepthMask( true );
01041     else
01042     {
01043         const ColorMask& colorMask = op.channel->getDrawBufferMask();
01044         glColorMask( colorMask.red, colorMask.green, colorMask.blue, true );
01045     }
01046 }
01047 
01048 void Compositor::assembleImageDB( const Image* image, const ImageOp& op )
01049 {
01050     Channel* channel = op.channel;
01051 
01052     if( GLEW_VERSION_2_0 )
01053         assembleImageDB_GLSL( image, op );
01054     else
01055         assembleImageDB_FF( image, op );
01056 }
01057 
01058 void Compositor::assembleImageDB_FF( const Image* image, const ImageOp& op )
01059 {
01060     const PixelViewport& pvp = image->getPixelViewport();
01061 
01062     EQLOG( LOG_ASSEMBLY ) << "assembleImageDB, fixed function " << pvp 
01063                           << endl;
01064     EQASSERT( image->hasData( Frame::BUFFER_COLOR ));
01065     EQASSERT( image->hasData( Frame::BUFFER_DEPTH ));
01066 
01067     // Z-Based sort-last assembly
01068     glRasterPos2i( op.offset.x() + pvp.x, op.offset.y() + pvp.y );
01069     glEnable( GL_STENCIL_TEST );
01070 
01071     // test who is in front and mark in stencil buffer
01072     glEnable( GL_DEPTH_TEST );
01073 
01074     const bool pixelComposite = ( op.pixel != Pixel::ALL );
01075     if( pixelComposite )
01076     {   // keep already marked stencil values
01077         glStencilFunc( GL_EQUAL, 1, 1 );
01078         glStencilOp( GL_KEEP, GL_ZERO, GL_REPLACE );
01079     }
01080     else
01081     {
01082         glStencilFunc( GL_ALWAYS, 1, 1 );
01083         glStencilOp( GL_ZERO, GL_ZERO, GL_REPLACE );
01084     }
01085 
01086     _drawPixels( image, op, Frame::BUFFER_DEPTH );
01087 
01088     glDisable( GL_DEPTH_TEST );
01089 
01090     // draw front-most, visible pixels using stencil mask
01091     glStencilFunc( GL_EQUAL, 1, 1 );
01092     glStencilOp( GL_KEEP, GL_ZERO, GL_ZERO );
01093 
01094     _drawPixels( image, op, Frame::BUFFER_COLOR );
01095 
01096     glDisable( GL_STENCIL_TEST );
01097 }
01098 
01099 void Compositor::assembleImageDB_GLSL( const Image* image, const ImageOp& op )
01100 {
01101     const PixelViewport& pvp = image->getPixelViewport();
01102 
01103     EQLOG( LOG_ASSEMBLY ) << "assembleImageDB, GLSL " << pvp 
01104                           << endl;
01105 
01106     Channel*               channel = op.channel; // needed for glewGetContext
01107     Window::ObjectManager* objects = channel->getObjectManager();
01108 
01109     GLuint program = objects->getProgram( shaderDBKey );
01110 
01111     if( program == Window::ObjectManager::INVALID )
01112     {
01113         // Create fragment shader which reads color and depth values from 
01114         // rectangular textures
01115         const GLuint shader = objects->newShader( shaderDBKey, 
01116                                                   GL_FRAGMENT_SHADER );
01117         EQASSERT( shader != Window::ObjectManager::INVALID );
01118 
01119         const char* source = "uniform sampler2DRect color; uniform sampler2DRect depth; void main(void){ gl_FragColor = texture2DRect( color, gl_TexCoord[0].st ); gl_FragDepth = texture2DRect( depth, gl_TexCoord[0].st ).x; }";
01120 
01121         EQ_GL_CALL( glShaderSource( shader, 1, &source, 0 ));
01122         EQ_GL_CALL( glCompileShader( shader ));
01123 
01124         GLint status;
01125         glGetShaderiv( shader, GL_COMPILE_STATUS, &status );
01126         if( !status )
01127             EQERROR << "Failed to compile fragment shader for DB compositing" 
01128                     << endl;
01129 
01130         program = objects->newProgram( shaderDBKey );
01131 
01132         EQ_GL_CALL( glAttachShader( program, shader ));
01133         EQ_GL_CALL( glLinkProgram( program ));
01134 
01135         glGetProgramiv( program, GL_LINK_STATUS, &status );
01136         if( !status )
01137         {
01138             EQWARN << "Failed to link shader program for DB compositing" 
01139                    << endl;
01140             return;
01141         }
01142 
01143         // use fragment shader and setup uniforms
01144         EQ_GL_CALL( glUseProgram( program ));
01145         
01146         const GLint depthParam = glGetUniformLocation( program, "depth" );
01147         glUniform1i( depthParam, 0 );
01148         const GLint colorParam = glGetUniformLocation( program, "color" );
01149         glUniform1i( colorParam, 1 );
01150     }
01151     else
01152     {
01153         // use fragment shader
01154         EQ_GL_CALL( glUseProgram( program ));
01155     }
01156 
01157     // Enable & download color and depth textures
01158     glEnable( GL_TEXTURE_RECTANGLE_ARB );
01159 
01160     EQ_GL_CALL( glActiveTexture( GL_TEXTURE1 ));
01161     glTexParameteri( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER,
01162                      GL_NEAREST );
01163     glTexParameteri( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, 
01164                      GL_NEAREST );
01165 
01166     const bool useImageTexture = image->getStorageType() == Frame::TYPE_TEXTURE;
01167     if( useImageTexture )
01168         image->getTexture( Frame::BUFFER_COLOR ).bind();
01169     else
01170     {
01171         Texture* texture = objects->obtainEqTexture( colorDBKey );
01172         texture->upload( image, Frame::BUFFER_COLOR );
01173     }
01174 
01175     EQ_GL_CALL( glActiveTexture( GL_TEXTURE0 ));
01176     glTexParameteri( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER,
01177                      GL_NEAREST );
01178     glTexParameteri( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, 
01179                      GL_NEAREST );
01180 
01181     if( useImageTexture )
01182         image->getTexture( Frame::BUFFER_DEPTH ).bind();
01183     else
01184     {
01185         Texture* texture = objects->obtainEqTexture( depthDBKey );
01186         texture->upload( image, Frame::BUFFER_DEPTH );
01187     }
01188 
01189     // Draw a quad using shader & textures in the right place
01190     glEnable( GL_DEPTH_TEST );
01191     glColor3f( 1.0f, 1.0f, 1.0f );
01192 
01193     const float startX = static_cast< float >
01194         ( op.offset.x() + pvp.x * op.pixel.w + op.pixel.x );
01195     const float endX   = static_cast< float >
01196         ( op.offset.x() + (pvp.x + pvp.w) * op.pixel.w + op.pixel.x );
01197     const float startY = static_cast< float >
01198         ( op.offset.y() + pvp.y * op.pixel.h + op.pixel.y );
01199     const float endY   = static_cast< float >
01200         ( op.offset.y() + (pvp.y + pvp.h) * op.pixel.h + op.pixel.y );
01201     
01202     const float w = static_cast< float >( pvp.w );
01203     const float h = static_cast< float >( pvp.h );
01204 
01205     glBegin( GL_TRIANGLE_STRIP );
01206     glMultiTexCoord2f( GL_TEXTURE0, 0.0f, 0.0f );
01207     glMultiTexCoord2f( GL_TEXTURE1, 0.0f, 0.0f );
01208     glVertex3f( startX, startY, 0.0f );
01209 
01210     glMultiTexCoord2f( GL_TEXTURE0, w, 0.0f );
01211     glMultiTexCoord2f( GL_TEXTURE1, w, 0.0f );
01212     glVertex3f( endX, startY, 0.0f );
01213 
01214     glMultiTexCoord2f( GL_TEXTURE0, 0.0f, h );
01215     glMultiTexCoord2f( GL_TEXTURE1, 0.0f, h );
01216     glVertex3f( startX, endY, 0.0f );
01217 
01218     glMultiTexCoord2f( GL_TEXTURE0, w, h );
01219     glMultiTexCoord2f( GL_TEXTURE1, w, h );
01220     glVertex3f( endX, endY, 0.0f );
01221 
01222     glEnd();
01223 
01224     // restore state
01225     glDisable( GL_TEXTURE_RECTANGLE_ARB );
01226     glDisable( GL_DEPTH_TEST );
01227     EQ_GL_CALL( glUseProgram( 0 ));
01228 }
01229 
01230 
01231 }
Generated on Mon Aug 10 18:58:32 2009 for Equalizer 0.9 by  doxygen 1.5.8