Equalizer
1.4.1
|
00001 00002 /* Copyright (c) 2007-2012, Stefan Eilemann <eile@equalizergraphics.com> 00003 * 00004 * Redistribution and use in source and binary forms, with or without 00005 * modification, are permitted provided that the following conditions are met: 00006 * 00007 * - Redistributions of source code must retain the above copyright notice, this 00008 * list of conditions and the following disclaimer. 00009 * - Redistributions in binary form must reproduce the above copyright notice, 00010 * this list of conditions and the following disclaimer in the documentation 00011 * and/or other materials provided with the distribution. 00012 * - Neither the name of Eyescale Software GmbH nor the names of its 00013 * contributors may be used to endorse or promote products derived from this 00014 * software without specific prior written permission. 00015 * 00016 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 00017 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 00018 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 00019 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 00020 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 00021 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 00022 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 00023 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 00024 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 00025 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 00026 * POSSIBILITY OF SUCH DAMAGE. 00027 */ 00028 00029 #include "channel.h" 00030 00031 #include "config.h" 00032 #include "configEvent.h" 00033 00034 #include <co/plugins/compressor.h> 00035 00036 #ifdef WIN32_API 00037 # define snprintf _snprintf 00038 #endif 00039 00040 namespace eqPixelBench 00041 { 00042 namespace 00043 { 00044 #pragma warning(disable: 411) // class defines no constructor to initialize ... 00045 struct EnumMap 00046 { 00047 const char* internalFormatString; 00048 const uint32_t internalFormat; 00049 const size_t pixelSize; 00050 }; 00051 00052 #pragma warning(default: 411) 00053 00054 #define ENUM_MAP_ITEM( internalFormat, pixelSize ) \ 00055 { #internalFormat, EQ_COMPRESSOR_DATATYPE_ ## internalFormat, pixelSize } 00056 00057 static EnumMap _enums[] = { 00058 ENUM_MAP_ITEM( RGBA32F, 16 ), // initial buffer resize 00059 ENUM_MAP_ITEM( RGBA, 4 ), 00060 ENUM_MAP_ITEM( RGB10_A2, 4 ), 00061 ENUM_MAP_ITEM( RGBA16F, 8 ), 00062 ENUM_MAP_ITEM( RGBA32F, 16 ), 00063 ENUM_MAP_ITEM( DEPTH, 4 ), 00064 { 0, 0, 0 }}; 00065 #define NUM_IMAGES 8 00066 } 00067 00068 Channel::Channel( eq::Window* parent ) 00069 : eq::Channel( parent ) 00070 { 00071 eq::FrameDataPtr frameData = new eq::FrameData; 00072 _frame.setFrameData( frameData ); 00073 00074 for( unsigned i = 0; i < NUM_IMAGES; ++i ) 00075 frameData->newImage( eq::Frame::TYPE_MEMORY, getDrawableConfig( )); 00076 } 00077 00078 bool Channel::configExit() 00079 { 00080 _frame.getData()->resetPlugins(); 00081 return eq::Channel::configExit(); 00082 } 00083 00084 void Channel::frameStart( const eq::uint128_t& frameID, 00085 const uint32_t frameNumber ) 00086 { 00087 Config* config = static_cast< Config* >( getConfig( )); 00088 const lunchbox::Clock* clock = config->getClock(); 00089 00090 if( clock ) 00091 { 00092 ConfigEvent event; 00093 event.msec = clock->getTimef(); 00094 00095 const std::string& name = getName(); 00096 if( name.empty( )) 00097 snprintf( event.data.user.data, 32, "%p", this); 00098 else 00099 snprintf( event.data.user.data, 32, "%s", name.c_str( )); 00100 00101 event.data.user.data[31] = 0; 00102 event.area.x() = 0; 00103 event.area.y() = 0; 00104 00105 snprintf( event.formatType, 32, "app->pipe thread latency"); 00106 event.data.type = ConfigEvent::START_LATENCY; 00107 00108 config->sendEvent( event ); 00109 } 00110 00111 eq::Channel::frameStart( frameID, frameNumber ); 00112 } 00113 00114 void Channel::frameDraw( const eq::uint128_t& frameID ) 00115 { 00116 //----- setup GL state 00117 applyBuffer(); 00118 applyViewport(); 00119 00120 glMatrixMode( GL_PROJECTION ); 00121 glLoadIdentity(); 00122 00123 applyFrustum(); 00124 00125 glMatrixMode( GL_MODELVIEW ); 00126 glLoadIdentity(); 00127 applyHeadTransform(); 00128 00129 setupAssemblyState(); 00130 00131 _testFormats( 1.0f ); 00132 _testFormats( 0.5f ); 00133 _testFormats( 2.0f ); 00134 _testTiledOperations(); 00135 _testDepthAssemble(); 00136 00137 resetAssemblyState(); 00138 } 00139 00140 ConfigEvent Channel::_createConfigEvent() 00141 { 00142 ConfigEvent event; 00143 const std::string& name = getName(); 00144 00145 if( name.empty( )) 00146 snprintf( event.data.user.data, 32, "%p", this ); 00147 else 00148 snprintf( event.data.user.data, 32, "%s", name.c_str( )); 00149 00150 event.data.user.data[31] = 0; 00151 return event; 00152 } 00153 00154 void Channel::_testFormats( float applyZoom ) 00155 { 00156 glGetError(); // reset 00157 00158 //----- setup constant data 00159 const eq::Images& images = _frame.getImages(); 00160 eq::Image* image = images[ 0 ]; 00161 LBASSERT( image ); 00162 00163 Config* config = static_cast< Config* >( getConfig( )); 00164 const eq::PixelViewport& pvp = getPixelViewport(); 00165 const eq::Vector2i offset( pvp.x, pvp.y ); 00166 const eq::Zoom zoom( applyZoom, applyZoom ); 00167 00168 lunchbox::Clock clock; 00169 eq::Window::ObjectManager* glObjects = getObjectManager(); 00170 00171 //----- test all default format/type combinations 00172 ConfigEvent event = _createConfigEvent(); 00173 for( uint32_t i=0; _enums[i].internalFormatString; ++i ) 00174 { 00175 const uint32_t internalFormat = _enums[i].internalFormat; 00176 image->flush(); 00177 image->setInternalFormat( eq::Frame::BUFFER_COLOR, internalFormat ); 00178 image->setQuality( eq::Frame::BUFFER_COLOR, 0.f ); 00179 image->setAlphaUsage( false ); 00180 00181 const GLEWContext* glewContext = glewGetContext(); 00182 const std::vector< uint32_t >& names = 00183 image->findTransferers( eq::Frame::BUFFER_COLOR, glewContext ); 00184 00185 for( std::vector< uint32_t >::const_iterator j = names.begin(); 00186 j != names.end(); ++j ) 00187 { 00188 _draw( 0 ); 00189 00190 // setup 00191 event.formatType[31] = '\0'; 00192 event.data.type = ConfigEvent::READBACK; 00193 00194 image->allocDownloader( eq::Frame::BUFFER_COLOR, *j, glewContext ); 00195 image->setPixelViewport( pvp ); 00196 00197 const uint32_t outputToken = 00198 image->getExternalFormat( eq::Frame::BUFFER_COLOR ); 00199 snprintf( event.formatType, 32, "%s/%x/%x", 00200 _enums[i].internalFormatString, outputToken, *j ); 00201 00202 // read 00203 glFinish(); 00204 size_t nLoops = 0; 00205 try 00206 { 00207 clock.reset(); 00208 while( clock.getTime64() < 100 /*ms*/ ) 00209 { 00210 image->startReadback( eq::Frame::BUFFER_COLOR, pvp, zoom, 00211 glObjects ); 00212 image->finishReadback( zoom, glObjects->glewGetContext( )); 00213 ++nLoops; 00214 } 00215 glFinish(); 00216 event.msec = clock.getTimef() / float( nLoops ); 00217 00218 const eq::PixelData& pixels = 00219 image->getPixelData( eq::Frame::BUFFER_COLOR ); 00220 event.area.x() = pixels.pvp.w; 00221 event.area.y() = pixels.pvp.h; 00222 event.dataSizeGPU = pixels.pvp.getArea() * _enums[i].pixelSize; 00223 event.dataSizeCPU = 00224 image->getPixelDataSize( eq::Frame::BUFFER_COLOR ); 00225 } 00226 catch( const eq::GLException& e ) // debug mode 00227 { 00228 event.msec = -static_cast<float>( e.glError ); 00229 } 00230 00231 GLenum error = glGetError(); // release mode 00232 if( error != GL_NO_ERROR ) 00233 event.msec = -static_cast<float>( error ); 00234 00235 config->sendEvent( event ); 00236 00237 if( event.msec < 0 ) // error, don't write back data 00238 continue; 00239 00240 // write 00241 eq::Compositor::ImageOp op; 00242 op.channel = this; 00243 op.buffers = eq::Frame::BUFFER_COLOR; 00244 op.offset = offset; 00245 op.zoom = zoom; 00246 00247 event.data.type = ConfigEvent::ASSEMBLE; 00248 event.dataSizeCPU = 00249 image->getPixelDataSize( eq::Frame::BUFFER_COLOR ); 00250 00251 try 00252 { 00253 clock.reset(); 00254 eq::Compositor::assembleImage( image, op ); 00255 event.msec = clock.getTimef(); 00256 00257 const eq::PixelData& pixels = 00258 image->getPixelData( eq::Frame::BUFFER_COLOR ); 00259 event.area.x() = pixels.pvp.w; 00260 event.area.y() = pixels.pvp.h; 00261 event.dataSizeGPU = 00262 image->getPixelDataSize( eq::Frame::BUFFER_COLOR ); 00263 } 00264 catch( const eq::GLException& e ) // debug mode 00265 { 00266 event.msec = -static_cast<float>( e.glError ); 00267 } 00268 00269 error = glGetError(); // release mode 00270 if( error != GL_NO_ERROR ) 00271 event.msec = -static_cast<float>( error ); 00272 00273 config->sendEvent( event ); 00274 } 00275 } 00276 } 00277 00278 void Channel::_testTiledOperations() 00279 { 00280 glGetError(); // reset 00281 00282 //----- setup constant data 00283 const eq::Images& images = _frame.getImages(); 00284 LBASSERT( images[0] ); 00285 00286 eq::Config* config = getConfig(); 00287 const eq::PixelViewport& pvp = getPixelViewport(); 00288 const eq::Vector2i offset( pvp.x, pvp.y ); 00289 00290 ConfigEvent event = _createConfigEvent(); 00291 event.area.x() = pvp.w; 00292 00293 lunchbox::Clock clock; 00294 eq::Window::ObjectManager* glObjects = getObjectManager(); 00295 const GLEWContext* glewContext = glewGetContext(); 00296 00297 //----- test tiled assembly algorithms 00298 eq::PixelViewport subPVP = pvp; 00299 subPVP.h /= NUM_IMAGES; 00300 00301 for( unsigned i = 0; i < NUM_IMAGES; ++i ) 00302 { 00303 LBASSERT( images[ i ] ); 00304 images[ i ]->setPixelViewport( subPVP ); 00305 } 00306 00307 for( unsigned tiles = 0; tiles < NUM_IMAGES; ++tiles ) 00308 { 00309 EQ_GL_CALL( _draw( 0 )); 00310 event.area.y() = subPVP.h * (tiles+1); 00311 00312 //---- readback of 'tiles' depth images 00313 event.data.type = ConfigEvent::READBACK; 00314 snprintf( event.formatType, 32, "%d depth tiles", tiles+1 ); 00315 00316 event.msec = 0; 00317 for( unsigned j = 0; j <= tiles; ++j ) 00318 { 00319 subPVP.y = pvp.y + j * subPVP.h; 00320 eq::Image* image = images[ j ]; 00321 LBCHECK( image->allocDownloader( eq::Frame::BUFFER_DEPTH, 00322 EQ_COMPRESSOR_TRANSFER_DEPTH_TO_DEPTH_UNSIGNED_INT, 00323 glewContext )); 00324 image->clearPixelData( eq::Frame::BUFFER_DEPTH ); 00325 00326 clock.reset(); 00327 image->startReadback( eq::Frame::BUFFER_DEPTH, subPVP, 00328 eq::Zoom::NONE, glObjects ); 00329 image->finishReadback( eq::Zoom::NONE, glObjects->glewGetContext()); 00330 event.msec += clock.getTimef(); 00331 } 00332 00333 config->sendEvent( event ); 00334 00335 if( tiles == NUM_IMAGES-1 ) 00336 for( unsigned j = 0; j <= tiles; ++j ) 00337 _saveImage( images[j], 00338 "EQ_COMPRESSOR_DATATYPE_DEPTH_UNSIGNED_INT", 00339 "tiles" ); 00340 00341 //---- readback of 'tiles' color images 00342 event.data.type = ConfigEvent::READBACK; 00343 snprintf( event.formatType, 32, "%d color tiles", tiles+1 ); 00344 00345 event.msec = 0; 00346 for( unsigned j = 0; j <= tiles; ++j ) 00347 { 00348 subPVP.y = pvp.y + j * subPVP.h; 00349 eq::Image* image = images[ j ]; 00350 00351 LBCHECK( image->allocDownloader( eq::Frame::BUFFER_COLOR, 00352 EQ_COMPRESSOR_TRANSFER_RGBA_TO_BGRA, 00353 glewContext )); 00354 image->clearPixelData( eq::Frame::BUFFER_COLOR ); 00355 00356 clock.reset(); 00357 image->startReadback( eq::Frame::BUFFER_COLOR, subPVP, 00358 eq::Zoom::NONE, glObjects ); 00359 image->finishReadback( eq::Zoom::NONE, glObjects->glewGetContext()); 00360 event.msec += clock.getTimef(); 00361 } 00362 config->sendEvent( event ); 00363 00364 if( tiles == NUM_IMAGES-1 ) 00365 for( unsigned j = 0; j <= tiles; ++j ) 00366 _saveImage( images[j],"EQ_COMPRESSOR_DATATYPE_BGRA","tiles" ); 00367 00368 //---- benchmark assembly operations 00369 subPVP.y = pvp.y + tiles * subPVP.h; 00370 00371 eq::Compositor::ImageOp op; 00372 op.channel = this; 00373 op.buffers = eq::Frame::BUFFER_COLOR | eq::Frame::BUFFER_DEPTH; 00374 op.offset = offset; 00375 00376 // fixed-function 00377 event.data.type = ConfigEvent::ASSEMBLE; 00378 snprintf( event.formatType, 32, "tiles, GL1.1, %d images", tiles+1 ); 00379 00380 clock.reset(); 00381 for( unsigned j = 0; j <= tiles; ++j ) 00382 eq::Compositor::assembleImage( images[j], op ); 00383 00384 event.msec = clock.getTimef(); 00385 config->sendEvent( event ); 00386 00387 // CPU 00388 snprintf( event.formatType, 32, "tiles, CPU, %d images", tiles+1 ); 00389 00390 std::vector< eq::Frame* > frames; 00391 frames.push_back( &_frame ); 00392 00393 clock.reset(); 00394 eq::Compositor::assembleFramesCPU( frames, this ); 00395 event.msec = clock.getTimef(); 00396 config->sendEvent( event ); 00397 } 00398 } 00399 00400 void Channel::_testDepthAssemble() 00401 { 00402 glGetError(); // reset 00403 00404 //----- setup constant data 00405 const eq::Images& images = _frame.getImages(); 00406 eq::Image* image = images[ 0 ]; 00407 LBASSERT( image ); 00408 00409 eq::Config* config = getConfig(); 00410 const eq::PixelViewport& pvp = getPixelViewport(); 00411 const eq::Vector2i offset( pvp.x, pvp.y ); 00412 00413 ConfigEvent event = _createConfigEvent(); 00414 event.area.x() = pvp.w; 00415 00416 lunchbox::Clock clock; 00417 eq::Window::ObjectManager* glObjects = getObjectManager(); 00418 const GLEWContext* glewContext = glewGetContext(); 00419 00420 //----- test depth-based assembly algorithms 00421 for( unsigned i = 0; i < NUM_IMAGES; ++i ) 00422 { 00423 image = images[ i ]; 00424 LBASSERT( image ); 00425 image->setPixelViewport( pvp ); 00426 } 00427 00428 event.area.y() = pvp.h; 00429 00430 for( unsigned i = 0; i < NUM_IMAGES; ++i ) 00431 { 00432 _draw( i ); 00433 00434 // fill depth & color image 00435 image = images[ i ]; 00436 00437 LBCHECK( image->allocDownloader( eq::Frame::BUFFER_COLOR, 00438 EQ_COMPRESSOR_TRANSFER_RGBA_TO_BGRA, 00439 glewContext )); 00440 00441 LBCHECK( image->allocDownloader( eq::Frame::BUFFER_DEPTH, 00442 EQ_COMPRESSOR_TRANSFER_DEPTH_TO_DEPTH_UNSIGNED_INT, 00443 glewContext )); 00444 00445 image->clearPixelData( eq::Frame::BUFFER_COLOR ); 00446 image->clearPixelData( eq::Frame::BUFFER_DEPTH ); 00447 00448 image->startReadback( eq::Frame::BUFFER_COLOR | eq::Frame::BUFFER_DEPTH, 00449 pvp, eq::Zoom::NONE, glObjects ); 00450 image->finishReadback( eq::Zoom::NONE, glObjects->glewGetContext( )); 00451 00452 if( i == NUM_IMAGES-1 ) 00453 _saveImage( image,"EQ_COMPRESSOR_DATATYPE_DEPTH_UNSIGNED_INT", 00454 "depthAssemble" ); 00455 00456 // benchmark 00457 eq::Compositor::ImageOp op; 00458 op.channel = this; 00459 op.buffers = eq::Frame::BUFFER_COLOR | eq::Frame::BUFFER_DEPTH; 00460 op.offset = offset; 00461 00462 // fixed-function 00463 event.data.type = ConfigEvent::ASSEMBLE; 00464 snprintf( event.formatType, 32, "depth, GL1.1, %d images", i+1 ); 00465 00466 clock.reset(); 00467 for( unsigned j = 0; j <= i; ++j ) 00468 eq::Compositor::assembleImageDB_FF( images[j], op ); 00469 00470 event.msec = clock.getTimef(); 00471 config->sendEvent( event ); 00472 00473 // GLSL 00474 if( GLEW_VERSION_2_0 ) 00475 { 00476 snprintf( event.formatType, 32, "depth, GLSL, %d images", i+1 ); 00477 00478 clock.reset(); 00479 for( unsigned j = 0; j <= i; ++j ) 00480 eq::Compositor::assembleImageDB_GLSL( images[j], op ); 00481 event.msec = clock.getTimef(); 00482 config->sendEvent( event ); 00483 } 00484 00485 // CPU 00486 snprintf( event.formatType, 32, "depth, CPU, %d images", i+1 ); 00487 00488 std::vector< eq::Frame* > frames; 00489 frames.push_back( &_frame ); 00490 00491 clock.reset(); 00492 eq::Compositor::assembleFramesCPU( frames, this ); 00493 event.msec = clock.getTimef(); 00494 config->sendEvent( event ); 00495 } 00496 } 00497 00498 void Channel::_saveImage( const eq::Image* image, 00499 const char* externalformat, 00500 const char* info ) 00501 { 00502 return; 00503 00504 static uint32_t counter = 0; 00505 std::ostringstream stringstream; 00506 stringstream << "Image_" << ++counter << "_" << externalformat << "_" 00507 << info; 00508 image->writeImages( stringstream.str( )); 00509 } 00510 00511 void Channel::_draw( const eq::uint128_t& spin ) 00512 { 00513 EQ_GL_CALL( glPushAttrib( GL_ALL_ATTRIB_BITS )); 00514 00515 bindFrameBuffer(); 00516 eq::Channel::frameDraw( spin ); 00517 00518 EQ_GL_CALL( glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT )); 00519 EQ_GL_CALL( glEnable( GL_DEPTH_TEST )); 00520 00521 const float lightPos[] = { 0.0f, 0.0f, 1.0f, 0.0f }; 00522 glLightfv( GL_LIGHT0, GL_POSITION, lightPos ); 00523 00524 const float lightAmbient[] = { 0.2f, 0.2f, 0.2f, 1.0f }; 00525 glLightfv( GL_LIGHT0, GL_AMBIENT, lightAmbient ); 00526 00527 // rotate scene around the origin 00528 glRotatef( static_cast< float >( spin.low() + 3 ) * 10, 1.0f, 0.5f, 0.25f ); 00529 00530 // render six axis-aligned colored quads around the origin 00531 // front 00532 glColor3f( 1.0f, 0.5f, 0.5f ); 00533 glNormal3f( 0.0f, 0.0f, 1.0f ); 00534 glBegin( GL_TRIANGLE_STRIP ); 00535 glVertex3f( .7f, .7f, -1.0f ); 00536 glVertex3f( -.7f, .7f, -1.0f ); 00537 glVertex3f( .7f, -.7f, -1.0f ); 00538 glVertex3f( -.7f, -.7f, -1.0f ); 00539 glEnd(); 00540 00541 // bottom 00542 glColor3f( 0.5f, 1.0f, 0.5f ); 00543 glNormal3f( 0.0f, 1.0f, 0.0f ); 00544 glBegin( GL_TRIANGLE_STRIP ); 00545 glVertex3f( .7f, -1.0f, .7f ); 00546 glVertex3f( -.7f, -1.0f, .7f ); 00547 glVertex3f( .7f, -1.0f, -.7f ); 00548 glVertex3f( -.7f, -1.0f, -.7f ); 00549 glEnd(); 00550 00551 // back 00552 glColor3f( 0.5f, 0.5f, 1.0f ); 00553 glNormal3f( 0.0f, 0.0f, -1.0f ); 00554 glBegin( GL_TRIANGLE_STRIP ); 00555 glVertex3f( .7f, .7f, 1.0f ); 00556 glVertex3f( -.7f, .7f, 1.0f ); 00557 glVertex3f( .7f, -.7f, 1.0f ); 00558 glVertex3f( -.7f, -.7f, 1.0f ); 00559 glEnd(); 00560 00561 // top 00562 glColor3f( 1.0f, 1.0f, 0.5f ); 00563 glNormal3f( 0.f, -1.f, 0.f ); 00564 glBegin( GL_TRIANGLE_STRIP ); 00565 glVertex3f( .7f, 1.0f, .7f ); 00566 glVertex3f( -.7f, 1.0f, .7f ); 00567 glVertex3f( .7f, 1.0f, -.7f ); 00568 glVertex3f( -.7f, 1.0f, -.7f ); 00569 glEnd(); 00570 00571 // right 00572 glColor3f( 1.0f, 0.5f, 1.0f ); 00573 glNormal3f( -1.f, 0.f, 0.f ); 00574 glBegin( GL_TRIANGLE_STRIP ); 00575 glVertex3f( 1.0f, .7f, .7f ); 00576 glVertex3f( 1.0f, -.7f, .7f ); 00577 glVertex3f( 1.0f, .7f, -.7f ); 00578 glVertex3f( 1.0f, -.7f, -.7f ); 00579 glEnd(); 00580 00581 // left 00582 glColor3f( 0.5f, 1.0f, 1.0f ); 00583 glNormal3f( 1.f, 0.f, 0.f ); 00584 glBegin( GL_TRIANGLE_STRIP ); 00585 glVertex3f( -1.0f, .7f, .7f ); 00586 glVertex3f( -1.0f, -.7f, .7f ); 00587 glVertex3f( -1.0f, .7f, -.7f ); 00588 glVertex3f( -1.0f, -.7f, -.7f ); 00589 glEnd(); 00590 00591 EQ_GL_CALL( glPopAttrib( )); 00592 } 00593 00594 00595 }