Line data Source code
1 :
2 : /* Copyright (c) 2006-2016, Stefan Eilemann <eile@equalizergraphics.com>
3 : * Daniel Nachbaur <danielnachbaur@gmail.com>
4 : * Cedric Stalder <cedric.stalder@gmail.com>
5 : * Enrique G. Paredes <egparedes@ifi.uzh.ch>
6 : *
7 : * This library is free software; you can redistribute it and/or modify it under
8 : * the terms of the GNU Lesser General Public License version 2.1 as published
9 : * by the Free Software Foundation.
10 : *
11 : * This library is distributed in the hope that it will be useful, but WITHOUT
12 : * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 : * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
14 : * details.
15 : *
16 : * You should have received a copy of the GNU Lesser General Public License
17 : * along with this library; if not, write to the Free Software Foundation, Inc.,
18 : * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 : */
20 :
21 : #include "image.h"
22 :
23 : #include "gl.h"
24 : #include "half.h"
25 : #include "log.h"
26 : #include "pixelData.h"
27 : #include "windowSystem.h"
28 :
29 : #include <eq/util/frameBufferObject.h>
30 : #include <eq/util/objectManager.h>
31 : #include <eq/fabric/renderContext.h>
32 :
33 : #include <co/global.h>
34 :
35 : #include <lunchbox/buffer.h>
36 : #include <lunchbox/memoryMap.h>
37 : #include <pression/compressor.h>
38 : #include <pression/decompressor.h>
39 : #include <pression/downloader.h>
40 : #include <pression/pluginRegistry.h>
41 : #include <pression/uploader.h>
42 :
43 : #include <boost/filesystem.hpp>
44 : #include <fstream>
45 :
46 : #ifdef _WIN32
47 : # include <malloc.h>
48 : #else
49 : # include <alloca.h>
50 : #endif
51 :
52 : #include "transferFinder.h"
53 :
54 : #ifdef EQUALIZER_USE_OPENSCENEGRAPH
55 : # include <osgDB/WriteFile>
56 : #endif
57 :
58 : namespace eq
59 : {
60 : namespace
61 : {
62 : /** @internal Raw image data. */
63 14 : struct Memory : public PixelData
64 : {
65 : public:
66 1634 : Memory()
67 : : state( INVALID )
68 1634 : , hasAlpha( true )
69 1634 : {}
70 :
71 0 : void flush()
72 : {
73 0 : PixelData::reset();
74 0 : state = INVALID;
75 0 : localBuffer.clear();
76 0 : hasAlpha = true;
77 0 : }
78 :
79 38 : void useLocalBuffer()
80 : {
81 38 : LBASSERT( internalFormat != 0 );
82 38 : LBASSERT( externalFormat != 0 );
83 38 : LBASSERT( pixelSize > 0 );
84 38 : LBASSERT( pvp.hasArea( ));
85 :
86 38 : localBuffer.resize( pvp.getArea() * pixelSize );
87 38 : pixels = localBuffer.getData();
88 38 : }
89 :
90 : enum State
91 : {
92 : INVALID,
93 : VALID,
94 : DOWNLOAD // async RB is in progress
95 : };
96 :
97 : State state; //!< The current state of the memory
98 :
99 : /** During the call of setPixelData or writeImage, we have to
100 : * manage an internal buffer to copy the data. Otherwise the downloader
101 : * allocates the memory. */
102 : lunchbox::Bufferb localBuffer;
103 :
104 : bool hasAlpha; //!< The uncompressed pixels contain alpha
105 : };
106 :
107 : enum ActivePlugin
108 : {
109 : PLUGIN_FULL,
110 : PLUGIN_LOSSY,
111 : PLUGIN_ALL
112 : };
113 :
114 : /** @internal The individual parameters for a buffer. */
115 : struct Attachment
116 : {
117 : ActivePlugin active;
118 : pression::Compressor compressor[ PLUGIN_ALL ];
119 : pression::Decompressor decompressor[ PLUGIN_ALL ];
120 : pression::Downloader downloader[ PLUGIN_ALL ];
121 :
122 : float quality; //!< the minimum quality
123 :
124 : /** The texture name for this image component (texture images). */
125 : util::Texture texture;
126 :
127 : /** Current pixel data (memory images). */
128 : Memory memory;
129 :
130 : Zoom zoom; //!< zoom factor of pending readback
131 :
132 1634 : Attachment()
133 : : active( PLUGIN_FULL )
134 : , quality( 1.f )
135 1634 : , texture( GL_TEXTURE_RECTANGLE_ARB )
136 1634 : {}
137 :
138 14 : ~Attachment()
139 42 : {
140 14 : LBASSERT( !compressor[ PLUGIN_FULL ].isGood( ));
141 14 : LBASSERT( !compressor[ PLUGIN_LOSSY ].isGood( ));
142 14 : LBASSERT( !decompressor[ PLUGIN_FULL ].isGood( ));
143 14 : LBASSERT( !decompressor[ PLUGIN_LOSSY ].isGood( ));
144 14 : LBASSERT( !downloader[ PLUGIN_FULL ].isGood( ));
145 14 : LBASSERT( !downloader[ PLUGIN_LOSSY ].isGood( ));
146 42 : }
147 :
148 0 : void flush()
149 : {
150 0 : memory.flush();
151 0 : texture.flush();
152 0 : resetPlugins();
153 0 : }
154 :
155 0 : void resetPlugins()
156 : {
157 0 : compressor[ PLUGIN_FULL ].clear();
158 0 : compressor[ PLUGIN_LOSSY ].clear();
159 0 : decompressor[ PLUGIN_FULL ].clear();
160 0 : decompressor[ PLUGIN_LOSSY ].clear();
161 0 : downloader[ PLUGIN_FULL ].clear();
162 0 : downloader[ PLUGIN_LOSSY ].clear();
163 0 : }
164 : };
165 : }
166 :
167 : namespace detail
168 : {
169 7 : class Image
170 : {
171 : public:
172 817 : Image()
173 : : type( eq::Frame::TYPE_MEMORY )
174 : , ignoreAlpha( false )
175 817 : , hasPremultipliedAlpha( false )
176 817 : {}
177 :
178 : /** The rectangle of the current pixel data. */
179 : PixelViewport pvp;
180 :
181 : /** The render context producing the image. */
182 : RenderContext context;
183 :
184 : /** Zoom factor used for compositing. */
185 : Zoom zoom;
186 :
187 : /** The storage type for the pixel data. */
188 : eq::Frame::Type type;
189 :
190 : Attachment color;
191 : Attachment depth;
192 :
193 : /** Alpha channel significance. */
194 : bool ignoreAlpha;
195 :
196 : bool hasPremultipliedAlpha;
197 :
198 1041 : Attachment& getAttachment( const eq::Frame::Buffer buffer )
199 : {
200 1041 : switch( buffer )
201 : {
202 : case eq::Frame::BUFFER_COLOR:
203 760 : return color;
204 : case eq::Frame::BUFFER_DEPTH:
205 281 : return depth;
206 : default:
207 0 : LBUNIMPLEMENTED;
208 : }
209 0 : return color;
210 : }
211 :
212 12 : const Attachment& getAttachment( const eq::Frame::Buffer buffer ) const
213 : {
214 12 : switch( buffer )
215 : {
216 : case eq::Frame::BUFFER_COLOR:
217 9 : return color;
218 : case eq::Frame::BUFFER_DEPTH:
219 3 : return depth;
220 : default:
221 0 : LBUNIMPLEMENTED;
222 : }
223 0 : return color;
224 : }
225 :
226 968 : Memory& getMemory( const eq::Frame::Buffer buffer )
227 968 : { return getAttachment( buffer ).memory; }
228 : const Memory& getMemory( const eq::Frame::Buffer buffer ) const
229 : { return getAttachment( buffer ).memory; }
230 :
231 12 : EqCompressorInfos findTransferers( const eq::Frame::Buffer buffer,
232 : const GLEWContext* gl ) const
233 : {
234 12 : const Attachment& attachment = getAttachment( buffer );
235 12 : const Memory& memory = attachment.memory;
236 : TransferFinder finder( memory.internalFormat, memory.externalFormat, 0,
237 12 : attachment.quality, ignoreAlpha, gl );
238 12 : co::Global::getPluginRegistry().accept( finder );
239 12 : return finder.result;
240 : }
241 : };
242 : }
243 :
244 817 : Image::Image()
245 817 : : _impl( new detail::Image )
246 : {
247 817 : reset();
248 817 : }
249 :
250 11 : Image::~Image()
251 : {
252 7 : delete _impl;
253 11 : }
254 :
255 820 : void Image::reset()
256 : {
257 820 : _impl->ignoreAlpha = false;
258 820 : _impl->hasPremultipliedAlpha = false;
259 820 : setPixelViewport( PixelViewport( ));
260 820 : setContext( RenderContext( ));
261 820 : }
262 :
263 0 : void Image::flush()
264 : {
265 0 : _impl->color.flush();
266 0 : _impl->depth.flush();
267 0 : }
268 :
269 0 : void Image::resetPlugins()
270 : {
271 0 : _impl->color.resetPlugins();
272 0 : _impl->depth.resetPlugins();
273 0 : }
274 :
275 0 : void Image::deleteGLObjects( util::ObjectManager& om )
276 : {
277 0 : const char* key = reinterpret_cast< const char* >( this );
278 0 : for( size_t i=0; i < 4; ++i )
279 : {
280 0 : om.deleteEqUploader( key + i );
281 0 : om.deleteEqTexture( key + i );
282 : }
283 0 : }
284 :
285 0 : const void* Image::_getBufferKey( const Frame::Buffer buffer ) const
286 : {
287 0 : switch( buffer )
288 : {
289 : // Check also deleteGLObjects!
290 : case Frame::BUFFER_COLOR:
291 0 : return ( reinterpret_cast< const char* >( this ) + 0 );
292 : case Frame::BUFFER_DEPTH:
293 0 : return ( reinterpret_cast< const char* >( this ) + 1 );
294 : default:
295 0 : LBUNIMPLEMENTED;
296 0 : return ( reinterpret_cast< const char* >( this ) + 2 );
297 : }
298 : }
299 :
300 0 : const void* Image::_getCompressorKey( const Frame::Buffer buffer ) const
301 : {
302 0 : const Attachment& attachment = _impl->getAttachment( buffer );
303 :
304 0 : switch( buffer )
305 : {
306 : // Check also deleteGLObjects!
307 : case Frame::BUFFER_COLOR:
308 0 : if( attachment.quality == 1.0f )
309 0 : return ( reinterpret_cast< const char* >( this ) + 0 );
310 0 : return ( reinterpret_cast< const char* >( this ) + 1 );
311 : case Frame::BUFFER_DEPTH:
312 0 : if( attachment.quality == 1.0f )
313 0 : return ( reinterpret_cast< const char* >( this ) + 2 );
314 0 : return ( reinterpret_cast< const char* >( this ) + 3 );
315 : default:
316 0 : LBUNIMPLEMENTED;
317 0 : return ( reinterpret_cast< const char* >( this ) + 0 );
318 : }
319 : }
320 :
321 44 : uint32_t Image::getPixelDataSize( const Frame::Buffer buffer ) const
322 : {
323 44 : const Memory& memory = _impl->getMemory( buffer );
324 44 : return memory.pvp.getArea() * memory.pixelSize;
325 : }
326 :
327 :
328 14 : void Image::_setExternalFormat( const Frame::Buffer buffer,
329 : const uint32_t externalFormat,
330 : const uint32_t pixelSize, const bool hasAlpha_ )
331 : {
332 14 : Memory& memory = _impl->getMemory( buffer );
333 14 : if( memory.externalFormat == externalFormat )
334 17 : return;
335 :
336 11 : memory.externalFormat = externalFormat;
337 11 : memory.pixelSize = pixelSize;
338 11 : memory.hasAlpha = buffer == Frame::BUFFER_DEPTH ? false : hasAlpha_;
339 11 : memory.state = Memory::INVALID;
340 : }
341 :
342 26 : void Image::setInternalFormat( const Frame::Buffer buffer,
343 : const uint32_t internalFormat )
344 : {
345 26 : Memory& memory = _impl->getMemory( buffer );
346 26 : if( memory.internalFormat == internalFormat )
347 15 : return;
348 :
349 11 : memory.internalFormat = internalFormat;
350 11 : allocCompressor( buffer, EQ_COMPRESSOR_INVALID );
351 11 : if( internalFormat == 0 )
352 0 : return;
353 : }
354 :
355 0 : uint32_t Image::getInternalFormat( const Frame::Buffer buffer ) const
356 : {
357 0 : const Memory& memory = _impl->getMemory( buffer );
358 0 : LBASSERT( memory.internalFormat );
359 0 : return memory.internalFormat;
360 : }
361 :
362 : namespace
363 : {
364 0 : class CompressorFinder : public pression::ConstPluginVisitor
365 : {
366 : public:
367 0 : explicit CompressorFinder( const uint32_t token ) : token_( token ) {}
368 :
369 0 : virtual fabric::VisitorResult visit( const pression::Plugin&,
370 : const EqCompressorInfo& info )
371 : {
372 0 : if( info.capabilities & EQ_COMPRESSOR_TRANSFER )
373 0 : return fabric::TRAVERSE_CONTINUE;
374 :
375 0 : if( info.tokenType == token_ )
376 0 : result.push_back( info.name );
377 0 : return fabric::TRAVERSE_CONTINUE;
378 : }
379 :
380 : std::vector< uint32_t > result;
381 :
382 : private:
383 : const uint32_t token_;
384 : };
385 : }
386 :
387 0 : std::vector< uint32_t > Image::findCompressors( const Frame::Buffer buffer )
388 : const
389 : {
390 0 : const pression::PluginRegistry& registry = co::Global::getPluginRegistry();
391 0 : CompressorFinder finder( getExternalFormat( buffer ));
392 0 : registry.accept( finder );
393 :
394 0 : LBLOG( LOG_PLUGIN )
395 0 : << "Found " << finder.result.size() << " compressors for token type 0x"
396 0 : << std::hex << getExternalFormat( buffer ) << std::dec << std::endl;
397 0 : return finder.result;
398 : }
399 :
400 0 : std::vector< uint32_t > Image::findTransferers( const Frame::Buffer buffer,
401 : const GLEWContext* gl ) const
402 : {
403 0 : std::vector< uint32_t > result;
404 0 : const EqCompressorInfos& infos = _impl->findTransferers( buffer, gl );
405 0 : for( EqCompressorInfosCIter i = infos.begin(); i != infos.end(); ++i )
406 0 : result.push_back( i->name );
407 0 : return result;
408 : }
409 :
410 42 : bool Image::hasAlpha() const
411 : {
412 84 : return hasPixelData( Frame::BUFFER_COLOR ) &&
413 84 : _impl->getMemory( Frame::BUFFER_COLOR ).hasAlpha;
414 : }
415 :
416 6 : void Image::setAlphaUsage( const bool enabled )
417 : {
418 6 : if( _impl->ignoreAlpha != enabled )
419 12 : return;
420 :
421 0 : _impl->ignoreAlpha = !enabled;
422 0 : _impl->color.memory.compressedData = pression::CompressorResult();
423 0 : _impl->depth.memory.compressedData = pression::CompressorResult();
424 : }
425 :
426 12 : void Image::setQuality( const Frame::Buffer buffer, const float quality )
427 : {
428 12 : Attachment& attachment = _impl->getAttachment( buffer );
429 12 : if( attachment.quality == quality )
430 24 : return;
431 :
432 0 : attachment.quality = quality;
433 0 : if( quality >= 1.f )
434 0 : attachment.active = PLUGIN_FULL;
435 : else
436 : {
437 0 : attachment.active = PLUGIN_LOSSY;
438 0 : attachment.compressor[ PLUGIN_LOSSY ].clear();
439 0 : attachment.decompressor[ PLUGIN_LOSSY ].clear();
440 0 : attachment.downloader[ PLUGIN_LOSSY ].clear();
441 : }
442 : }
443 :
444 0 : float Image::getQuality( const Frame::Buffer buffer ) const
445 : {
446 0 : return _impl->getAttachment( buffer ).quality;
447 : }
448 :
449 0 : bool Image::hasTextureData( const Frame::Buffer buffer ) const
450 : {
451 0 : return getTexture( buffer ).isValid();
452 : }
453 :
454 0 : const util::Texture& Image::getTexture( const Frame::Buffer buffer ) const
455 : {
456 0 : return _impl->getAttachment( buffer ).texture;
457 : }
458 :
459 93 : const uint8_t* Image::getPixelPointer( const Frame::Buffer buffer ) const
460 : {
461 93 : LBASSERT( hasPixelData( buffer ));
462 93 : return reinterpret_cast< const uint8_t* >( _impl->getMemory( buffer ).pixels );
463 : }
464 :
465 12 : uint8_t* Image::getPixelPointer( const Frame::Buffer buffer )
466 : {
467 12 : LBASSERT( hasPixelData( buffer ));
468 12 : return reinterpret_cast< uint8_t* >( _impl->getMemory( buffer ).pixels );
469 : }
470 :
471 84 : const PixelData& Image::getPixelData( const Frame::Buffer buffer ) const
472 : {
473 84 : LBASSERT( hasPixelData( buffer ));
474 84 : return _impl->getMemory( buffer );
475 : }
476 :
477 0 : bool Image::upload( const Frame::Buffer buffer, util::Texture* texture,
478 : const Vector2i& position, util::ObjectManager& om ) const
479 : {
480 : // freed by deleteGLObjects, e.g., called from Pipe::flushFrames()
481 : pression::Uploader* uploader = om.obtainEqUploader(
482 0 : _getCompressorKey( buffer ));
483 0 : const PixelData& pixelData = getPixelData( buffer );
484 0 : const uint32_t externalFormat = pixelData.externalFormat;
485 0 : const uint32_t internalFormat = pixelData.internalFormat;
486 : const uint64_t flags = EQ_COMPRESSOR_TRANSFER | EQ_COMPRESSOR_DATA_2D |
487 0 : ( texture ? texture->getCompressorTarget() :
488 0 : EQ_COMPRESSOR_USE_FRAMEBUFFER );
489 0 : const GLEWContext* const gl = om.glewGetContext();
490 :
491 0 : if( !uploader->supports( externalFormat, internalFormat, flags, gl ))
492 0 : uploader->setup( co::Global::getPluginRegistry(), externalFormat,
493 0 : internalFormat, flags, gl );
494 :
495 0 : if( !uploader->isGood( gl ))
496 : {
497 0 : LBWARN << "No upload plugin for " << std::hex << externalFormat
498 0 : << " -> " << internalFormat << std::dec << " upload" <<std::endl;
499 0 : return false;
500 : }
501 :
502 0 : PixelViewport pvp = getPixelViewport();
503 0 : pvp.x = position.x() + pvp.x;
504 0 : pvp.y = position.y() + pvp.y;
505 0 : if( texture )
506 0 : texture->init( internalFormat, _impl->pvp.w, _impl->pvp.h );
507 :
508 : uint64_t inDims[4], outDims[4];
509 0 : pixelData.pvp.convertToPlugin( inDims );
510 0 : pvp.convertToPlugin( outDims );
511 : uploader->upload( pixelData.pixels, inDims, flags, outDims,
512 0 : texture ? texture->getName() : 0, gl );
513 0 : return true;
514 : }
515 :
516 : //---------------------------------------------------------------------------
517 : // asynchronous readback
518 : //---------------------------------------------------------------------------
519 : // TODO: 2.0 API: rename to readback and return Future
520 0 : bool Image::startReadback( const uint32_t buffers, const PixelViewport& pvp,
521 : const RenderContext& context, const Zoom& zoom,
522 : util::ObjectManager& glObjects )
523 : {
524 0 : LBLOG( LOG_ASSEMBLY ) << "startReadback " << pvp << ", buffers " << buffers
525 0 : << std::endl;
526 :
527 0 : _impl->pvp = pvp;
528 0 : _impl->context = context;
529 0 : _impl->color.memory.state = Memory::INVALID;
530 0 : _impl->depth.memory.state = Memory::INVALID;
531 :
532 0 : bool needFinish = (buffers & Frame::BUFFER_COLOR) &&
533 0 : _startReadback( Frame::BUFFER_COLOR, zoom, glObjects );
534 :
535 0 : if( (buffers & Frame::BUFFER_DEPTH) &&
536 0 : _startReadback( Frame::BUFFER_DEPTH, zoom, glObjects ))
537 : {
538 0 : needFinish = true;
539 : }
540 :
541 0 : _impl->pvp.x = 0;
542 0 : _impl->pvp.y = 0;
543 0 : return needFinish;
544 : }
545 :
546 0 : bool Image::_startReadback( const Frame::Buffer buffer, const Zoom& zoom,
547 : util::ObjectManager& glObjects )
548 : {
549 0 : Attachment& attachment = _impl->getAttachment( buffer );
550 0 : attachment.memory.compressedData = pression::CompressorResult();
551 :
552 0 : if( _impl->type == Frame::TYPE_TEXTURE )
553 : {
554 0 : LBASSERTINFO( zoom == Zoom::NONE, "Texture readback zoom not " <<
555 : "implemented, zoom happens during compositing" );
556 0 : util::Texture& texture = attachment.texture;
557 0 : texture.setGLEWContext( glObjects.glewGetContext( ));
558 0 : texture.copyFromFrameBuffer( getInternalFormat( buffer ), _impl->pvp );
559 0 : texture.setGLEWContext( 0 );
560 0 : return false;
561 : }
562 :
563 0 : attachment.zoom = zoom;
564 0 : if( zoom == Zoom::NONE ) // normal framebuffer readback
565 0 : return startReadback( buffer, 0, glObjects.glewGetContext( ));
566 :
567 : // else copy to texture, draw zoomed quad into FBO, (read FBO texture)
568 0 : return _readbackZoom( buffer, glObjects );
569 : }
570 :
571 0 : bool Image::startReadback( const Frame::Buffer buffer,
572 : const util::Texture* texture, const GLEWContext* gl )
573 : {
574 0 : Attachment& attachment = _impl->getAttachment( buffer );
575 0 : pression::Downloader& downloader = attachment.downloader[attachment.active];
576 0 : Memory& memory = attachment.memory;
577 0 : const uint32_t inputToken = memory.internalFormat;
578 :
579 0 : uint32_t flags = EQ_COMPRESSOR_TRANSFER | EQ_COMPRESSOR_DATA_2D |
580 0 : ( texture ? texture->getCompressorTarget() :
581 0 : EQ_COMPRESSOR_USE_FRAMEBUFFER );
582 0 : const bool noAlpha = _impl->ignoreAlpha && buffer == Frame::BUFFER_COLOR;
583 :
584 0 : if( !downloader.supports( inputToken, noAlpha, flags ))
585 0 : downloader.setup( co::Global::getPluginRegistry(), inputToken,
586 0 : attachment.quality, noAlpha, flags, gl );
587 :
588 0 : if( !downloader.isGood( ))
589 : {
590 0 : LBWARN << "Download plugin initialization failed using input 0x"
591 0 : << std::hex << inputToken << std::dec << std::endl;
592 0 : return false;
593 : }
594 :
595 : // get the pixel type produced by the downloader
596 0 : const EqCompressorInfo& info = downloader.getInfo();
597 0 : const bool alpha = (info.capabilities & EQ_COMPRESSOR_IGNORE_ALPHA) == 0;
598 : _setExternalFormat( buffer, info.outputTokenType, info.outputTokenSize,
599 0 : alpha );
600 0 : attachment.memory.state = Memory::DOWNLOAD;
601 :
602 0 : if( !memory.hasAlpha )
603 0 : flags |= EQ_COMPRESSOR_IGNORE_ALPHA;
604 :
605 0 : uint64_t outDims[4] = {0};
606 0 : if( texture )
607 : {
608 0 : LBASSERT( texture->isValid( ));
609 0 : const uint64_t inDims[4] = { 0ull, uint64_t( texture->getWidth( )),
610 0 : 0ull, uint64_t( texture->getHeight( )) };
611 0 : if( downloader.start( &memory.pixels, inDims, flags, outDims,
612 0 : texture->getName(), gl ))
613 : {
614 0 : return true;
615 : }
616 : }
617 : else
618 : {
619 : uint64_t inDims[4];
620 0 : _impl->pvp.convertToPlugin( inDims );
621 0 : if( downloader.start( &memory.pixels, inDims, flags, outDims, 0, gl ))
622 0 : return true;
623 : }
624 :
625 0 : memory.pvp.convertFromPlugin( outDims );
626 0 : attachment.memory.state = Memory::VALID;
627 0 : return false;
628 : }
629 :
630 0 : void Image::finishReadback( const GLEWContext* context )
631 : {
632 0 : LBASSERT( context );
633 0 : LBLOG( LOG_ASSEMBLY ) << "finishReadback" << std::endl;
634 :
635 0 : _finishReadback( Frame::BUFFER_COLOR, context );
636 0 : _finishReadback( Frame::BUFFER_DEPTH, context );
637 :
638 : #ifndef NDEBUG
639 0 : if( getenv( "EQ_DUMP_IMAGES" ))
640 : {
641 0 : static a_int32_t counter;
642 0 : std::ostringstream stringstream;
643 :
644 0 : stringstream << "Image_" << std::setfill( '0' ) << std::setw(5)
645 0 : << ++counter;
646 0 : writeImages( stringstream.str( ));
647 : }
648 : #endif
649 0 : }
650 :
651 0 : void Image::_finishReadback( const Frame::Buffer buffer,
652 : const GLEWContext* context )
653 : {
654 0 : if( _impl->type == Frame::TYPE_TEXTURE )
655 0 : return;
656 :
657 0 : Attachment& attachment = _impl->getAttachment( buffer );
658 0 : Memory& memory = attachment.memory;
659 0 : if( memory.state != Memory::DOWNLOAD )
660 0 : return;
661 :
662 0 : pression::Downloader& downloader = attachment.downloader[attachment.active];
663 0 : const uint32_t inputToken = memory.internalFormat;
664 0 : const bool alpha = _impl->ignoreAlpha && buffer == Frame::BUFFER_COLOR;
665 : uint32_t flags = EQ_COMPRESSOR_TRANSFER | EQ_COMPRESSOR_DATA_2D |
666 0 : ( attachment.zoom == Zoom::NONE ? EQ_COMPRESSOR_USE_FRAMEBUFFER :
667 0 : EQ_COMPRESSOR_USE_TEXTURE_RECT );
668 :
669 0 : if( !downloader.supports( inputToken, alpha, flags ))
670 : {
671 0 : LBWARN << "Download plugin initialization failed" << std::endl;
672 0 : attachment.memory.state = Memory::INVALID;
673 0 : return;
674 : }
675 :
676 0 : if( memory.hasAlpha && buffer == Frame::BUFFER_COLOR )
677 0 : _impl->hasPremultipliedAlpha = true;
678 :
679 0 : flags |= ( memory.hasAlpha ? 0 : EQ_COMPRESSOR_IGNORE_ALPHA );
680 :
681 0 : uint64_t outDims[4] = {0};
682 : uint64_t inDims[4];
683 0 : PixelViewport pvp = _impl->pvp;
684 0 : if( attachment.zoom != Zoom::NONE )
685 : {
686 0 : pvp.apply( attachment.zoom );
687 0 : pvp.x = 0;
688 0 : pvp.y = 0;
689 : }
690 0 : _impl->pvp.convertToPlugin( inDims );
691 :
692 0 : downloader.finish( &memory.pixels, inDims, flags, outDims, context );
693 0 : memory.pvp.convertFromPlugin( outDims );
694 0 : memory.state = Memory::VALID;
695 : }
696 :
697 0 : bool Image::_readbackZoom( const Frame::Buffer buffer, util::ObjectManager& om )
698 : {
699 0 : LBASSERT( om.supportsEqTexture( ));
700 0 : LBASSERT( om.supportsEqFrameBufferObject( ));
701 :
702 0 : const Attachment& attachment = _impl->getAttachment( buffer );
703 0 : PixelViewport pvp = _impl->pvp;
704 0 : pvp.apply( attachment.zoom );
705 0 : if( !pvp.hasArea( ))
706 0 : return false;
707 :
708 : // copy frame buffer to texture
709 0 : const uint32_t inputToken = attachment.memory.internalFormat;
710 0 : const void* bufferKey = _getBufferKey( buffer );
711 : util::Texture* texture = om.obtainEqTexture( bufferKey,
712 0 : GL_TEXTURE_RECTANGLE_ARB );
713 0 : texture->copyFromFrameBuffer( inputToken, _impl->pvp );
714 :
715 : // draw zoomed quad into FBO
716 : // uses the same FBO for color and depth, with masking.
717 0 : const void* fboKey = _getBufferKey( Frame::BUFFER_COLOR );
718 0 : util::FrameBufferObject* fbo = om.getEqFrameBufferObject( fboKey );
719 :
720 0 : if( fbo )
721 : {
722 0 : LBCHECK( fbo->resize( pvp.w, pvp.h ));
723 : }
724 : else
725 : {
726 0 : fbo = om.newEqFrameBufferObject( fboKey );
727 0 : LBCHECK( fbo->init( pvp.w, pvp.h, inputToken, 24, 0 ));
728 : }
729 0 : fbo->bind();
730 0 : texture->bind();
731 :
732 0 : if( buffer == Frame::BUFFER_COLOR )
733 0 : glDepthMask( false );
734 : else
735 : {
736 0 : LBASSERT( buffer == Frame::BUFFER_DEPTH )
737 0 : glColorMask( false, false, false, false );
738 : }
739 :
740 0 : glDisable( GL_LIGHTING );
741 0 : glEnable( GL_TEXTURE_RECTANGLE_ARB );
742 0 : texture->applyZoomFilter( FILTER_LINEAR );
743 0 : glColor3f( 1.0f, 1.0f, 1.0f );
744 :
745 0 : glBegin( GL_QUADS );
746 0 : glTexCoord2f( 0.0f, 0.0f );
747 0 : glVertex3f( 0, 0, 0.0f );
748 :
749 0 : glTexCoord2f( static_cast< float >( _impl->pvp.w ), 0.0f );
750 0 : glVertex3f( static_cast< float >( pvp.w ), 0, 0.0f );
751 :
752 : glTexCoord2f( static_cast< float >( _impl->pvp.w ),
753 0 : static_cast< float >( _impl->pvp.h ));
754 : glVertex3f( static_cast< float >( pvp.w ),
755 0 : static_cast< float >( pvp.h ), 0.0f );
756 :
757 0 : glTexCoord2f( 0.0f, static_cast< float >( _impl->pvp.h ));
758 0 : glVertex3f( 0, static_cast< float >( pvp.h ), 0.0f );
759 0 : glEnd();
760 :
761 : // restore state
762 0 : glDisable( GL_TEXTURE_RECTANGLE_ARB );
763 : // TODO channel->bindFramebuffer()
764 0 : glBindTexture( GL_TEXTURE_RECTANGLE_ARB, 0 );
765 0 : fbo->unbind();
766 :
767 0 : const util::Texture* zoomedTexture = 0;
768 0 : if( buffer == Frame::BUFFER_COLOR )
769 : {
770 0 : glDepthMask( true );
771 0 : zoomedTexture = fbo->getColorTextures().front();
772 : }
773 : else
774 : {
775 0 : const ColorMask colorMask; // TODO = channel->getDrawBufferMask();
776 0 : glColorMask( colorMask.red, colorMask.green, colorMask.blue, true );
777 0 : zoomedTexture = &fbo->getDepthTexture();
778 : }
779 0 : LBASSERT( zoomedTexture->isValid( ));
780 0 : LBLOG( LOG_ASSEMBLY ) << "Scale " << _impl->pvp << " -> " << pvp << std::endl;
781 :
782 : // BUG TODO: this is a bug in case of color and depth buffers read-back, as
783 : // _impl->pvp will be incorrect for the depth buffer!
784 : //
785 : // This should be done separately for color an depth buffers!
786 0 : _impl->pvp = pvp;
787 :
788 0 : LBLOG( LOG_ASSEMBLY ) << "Read texture " << getPixelDataSize( buffer )
789 0 : << std::endl;
790 0 : return startReadback( buffer, zoomedTexture, om.glewGetContext( ));
791 : }
792 :
793 839 : void Image::setPixelViewport( const PixelViewport& pvp )
794 : {
795 839 : _impl->pvp = pvp;
796 839 : _impl->color.memory.state = Memory::INVALID;
797 839 : _impl->depth.memory.state = Memory::INVALID;
798 839 : _impl->color.memory.compressedData = pression::CompressorResult();
799 839 : _impl->depth.memory.compressedData = pression::CompressorResult();
800 839 : }
801 :
802 12 : void Image::clearPixelData( const Frame::Buffer buffer )
803 : {
804 12 : Memory& memory = _impl->getAttachment( buffer ).memory;
805 12 : memory.pvp = _impl->pvp;
806 12 : const ssize_t size = getPixelDataSize( buffer );
807 12 : if( size == 0 )
808 12 : return;
809 :
810 12 : validatePixelData( buffer );
811 :
812 12 : switch( memory.externalFormat )
813 : {
814 : case EQ_COMPRESSOR_DATATYPE_DEPTH_UNSIGNED_INT:
815 3 : memset( memory.pixels, 0xFF, size );
816 3 : break;
817 :
818 : case EQ_COMPRESSOR_DATATYPE_RGBA:
819 : case EQ_COMPRESSOR_DATATYPE_BGRA:
820 : {
821 9 : uint8_t* data = reinterpret_cast< uint8_t* >( memory.pixels );
822 : #ifdef Darwin
823 : const unsigned char pixel[4] = { 0, 0, 0, 255 };
824 : memset_pattern4( data, &pixel, size );
825 : #else
826 9 : lunchbox::setZero( data, size );
827 : #pragma omp parallel for
828 14776329 : for( ssize_t i = 3; i < size; i+=4 )
829 14776320 : data[i] = 255;
830 : #endif
831 9 : break;
832 : }
833 : default:
834 0 : LBWARN << "Unknown external format " << memory.externalFormat
835 0 : << ", initializing to 0" << std::endl;
836 0 : lunchbox::setZero( memory.pixels, size );
837 0 : break;
838 : }
839 : }
840 :
841 38 : void Image::validatePixelData( const Frame::Buffer buffer )
842 : {
843 38 : Memory& memory = _impl->getAttachment( buffer ).memory;
844 38 : memory.useLocalBuffer();
845 38 : memory.state = Memory::VALID;
846 38 : memory.compressedData = pression::CompressorResult();
847 38 : }
848 :
849 12 : void Image::setPixelData( const Frame::Buffer buffer, const PixelData& pixels )
850 : {
851 12 : Memory& memory = _impl->getMemory( buffer );
852 12 : memory.externalFormat = pixels.externalFormat;
853 12 : memory.internalFormat = pixels.internalFormat;
854 12 : memory.pixelSize = pixels.pixelSize;
855 12 : memory.pvp = pixels.pvp;
856 12 : memory.state = Memory::INVALID;
857 12 : memory.compressedData = pression::CompressorResult();
858 12 : memory.hasAlpha = false;
859 :
860 : const EqCompressorInfos& transferrers = _impl->findTransferers( buffer,
861 12 : 0 /*GLEW context*/ );
862 12 : if( transferrers.empty( ))
863 0 : LBWARN << "No upload engines found for given pixel data" << std::endl;
864 : else
865 : {
866 : memory.hasAlpha =
867 12 : transferrers.front().capabilities & EQ_COMPRESSOR_IGNORE_ALPHA;
868 : #ifndef NDEBUG
869 72 : for( EqCompressorInfosCIter i = transferrers.begin();
870 48 : i != transferrers.end(); ++i )
871 : {
872 12 : LBASSERTINFO( memory.hasAlpha ==
873 : bool( i->capabilities & EQ_COMPRESSOR_IGNORE_ALPHA ),
874 : "Uploaders don't agree on alpha state of external " <<
875 : "format: " << transferrers.front() << " != " << *i );
876 : }
877 : #endif
878 : }
879 :
880 12 : const uint32_t size = getPixelDataSize( buffer );
881 12 : LBASSERT( size > 0 );
882 12 : if( size == 0 )
883 0 : return;
884 :
885 12 : if( pixels.compressedData.compressor <= EQ_COMPRESSOR_NONE )
886 : {
887 12 : validatePixelData( buffer ); // alloc memory for pixels
888 :
889 12 : if( pixels.pixels )
890 : {
891 0 : memcpy( memory.pixels, pixels.pixels, size );
892 0 : memory.state = Memory::VALID;
893 : }
894 : else
895 : // no data in pixels, clear image buffer
896 12 : clearPixelData( buffer );
897 :
898 12 : return;
899 : }
900 :
901 0 : LBASSERT( !pixels.compressedData.chunks.empty( ));
902 0 : LBASSERT( pixels.compressedData.compressor != EQ_COMPRESSOR_AUTO );
903 :
904 0 : Attachment& attachment = _impl->getAttachment( buffer );
905 0 : if( !attachment.decompressor->setup( co::Global::getPluginRegistry(),
906 0 : pixels.compressedData.compressor ))
907 : {
908 0 : LBASSERTINFO( false,
909 : "Can't allocate decompressor " <<
910 : pixels.compressedData.compressor <<
911 : ", mismatched compression plugin installation?" );
912 0 : return;
913 : }
914 :
915 0 : const EqCompressorInfo& info = attachment.decompressor->getInfo();
916 0 : LBASSERTINFO( info.name == pixels.compressedData.compressor, info );
917 :
918 0 : if( memory.externalFormat != info.outputTokenType )
919 : {
920 : // decompressor output differs from compressor input
921 0 : memory.externalFormat = info.outputTokenType;
922 0 : memory.pixelSize = info.outputTokenSize;
923 : }
924 0 : validatePixelData( buffer ); // alloc memory for pixels
925 :
926 : uint64_t outDims[4];
927 0 : memory.pvp.convertToPlugin( outDims );
928 :
929 : attachment.decompressor->decompress( pixels.compressedData, memory.pixels,
930 0 : outDims, pixels.compressorFlags );
931 : }
932 :
933 : /** Find and activate a compression engine */
934 11 : bool Image::allocCompressor( const Frame::Buffer buffer, const uint32_t name )
935 : {
936 11 : Attachment& attachment = _impl->getAttachment( buffer );
937 11 : pression::Compressor& compressor = attachment.compressor[attachment.active];
938 11 : if( name <= EQ_COMPRESSOR_NONE )
939 : {
940 11 : attachment.memory.compressedData = pression::CompressorResult();
941 11 : compressor.clear();
942 11 : return true;
943 : }
944 :
945 0 : if( compressor.uses( name ))
946 0 : return true;
947 :
948 0 : attachment.memory.compressedData = pression::CompressorResult();
949 0 : compressor.setup( co::Global::getPluginRegistry(), name );
950 0 : LBLOG( LOG_PLUGIN ) << "Instantiated compressor of type 0x" << std::hex
951 0 : << name << std::dec << std::endl;
952 0 : return compressor.isGood();
953 : }
954 :
955 : /** Find and activate a compression engine */
956 0 : bool Image::allocDownloader( const Frame::Buffer buffer, const uint32_t name,
957 : const GLEWContext* gl )
958 : {
959 0 : LBASSERT( name > EQ_COMPRESSOR_NONE )
960 0 : LBASSERT( gl );
961 :
962 0 : Attachment& attachment = _impl->getAttachment( buffer );
963 0 : pression::Downloader& downloader = attachment.downloader[attachment.active];
964 :
965 0 : if( name <= EQ_COMPRESSOR_NONE )
966 : {
967 0 : downloader.clear();
968 0 : _setExternalFormat( buffer, EQ_COMPRESSOR_DATATYPE_NONE, 0, true );
969 0 : return false;
970 : }
971 :
972 :
973 0 : if( downloader.uses( name ))
974 0 : return true;
975 :
976 0 : if( !downloader.setup( co::Global::getPluginRegistry(), name, gl ))
977 0 : return false;
978 :
979 0 : const EqCompressorInfo& info = downloader.getInfo();
980 0 : attachment.memory.internalFormat = info.tokenType;
981 : _setExternalFormat( buffer, info.outputTokenType, info.outputTokenSize,
982 0 : !(info.capabilities & EQ_COMPRESSOR_IGNORE_ALPHA) );
983 0 : return true;
984 : }
985 :
986 0 : uint32_t Image::getDownloaderName( const Frame::Buffer buffer ) const
987 : {
988 0 : const Attachment& attachment = _impl->getAttachment( buffer );
989 : const pression::Downloader& downloader =
990 0 : attachment.downloader[attachment.active];
991 0 : if( downloader.isGood( ))
992 0 : return downloader.getInfo().name;
993 0 : return EQ_COMPRESSOR_INVALID;
994 : }
995 :
996 12 : void Image::useCompressor( const Frame::Buffer buffer, const uint32_t name )
997 : {
998 12 : _impl->getMemory( buffer ).compressorName = name;
999 12 : }
1000 :
1001 0 : const PixelData& Image::compressPixelData( const Frame::Buffer buffer )
1002 : {
1003 0 : LBASSERT( getPixelDataSize( buffer ) > 0 );
1004 :
1005 0 : Attachment& attachment = _impl->getAttachment( buffer );
1006 0 : Memory& memory = attachment.memory;
1007 0 : if( memory.compressedData.isCompressed() ||
1008 0 : memory.compressorName == EQ_COMPRESSOR_NONE )
1009 : {
1010 0 : LBASSERT( memory.compressorName != EQ_COMPRESSOR_AUTO );
1011 0 : return memory;
1012 : }
1013 :
1014 0 : pression::Compressor& compressor = attachment.compressor[attachment.active];
1015 :
1016 0 : if( !compressor.isGood() ||
1017 0 : compressor.getInfo().tokenType != getExternalFormat( buffer ) ||
1018 0 : memory.compressorName == EQ_COMPRESSOR_AUTO )
1019 : {
1020 0 : if( memory.compressorName == EQ_COMPRESSOR_AUTO )
1021 : {
1022 0 : const uint32_t tokenType = getExternalFormat( buffer );
1023 : const float downloadQuality =
1024 0 : attachment.downloader[ attachment.active ].getInfo().quality;
1025 0 : const float quality = attachment.quality / downloadQuality;
1026 :
1027 0 : compressor.setup( co::Global::getPluginRegistry(), tokenType,
1028 0 : quality, _impl->ignoreAlpha );
1029 : }
1030 : else
1031 0 : compressor.setup( co::Global::getPluginRegistry(),
1032 0 : memory.compressorName );
1033 :
1034 0 : if( !compressor.isGood( ))
1035 : {
1036 0 : LBWARN << "No compressor found for token type 0x" << std::hex
1037 0 : << getExternalFormat( buffer ) << std::dec << std::endl;
1038 0 : compressor.clear();
1039 : }
1040 : }
1041 :
1042 0 : memory.compressedData.compressor = compressor.getInfo().name;
1043 0 : LBASSERT( memory.compressedData.compressor != EQ_COMPRESSOR_AUTO );
1044 0 : LBASSERT( memory.compressedData.compressor != EQ_COMPRESSOR_INVALID );
1045 0 : if( memory.compressedData.compressor == EQ_COMPRESSOR_NONE )
1046 0 : return memory;
1047 :
1048 0 : memory.compressorFlags = EQ_COMPRESSOR_DATA_2D;
1049 0 : if( _impl->ignoreAlpha && memory.hasAlpha )
1050 : {
1051 0 : LBASSERT( buffer == Frame::BUFFER_COLOR );
1052 0 : memory.compressorFlags |= EQ_COMPRESSOR_IGNORE_ALPHA;
1053 : }
1054 :
1055 : uint64_t inDims[4];
1056 0 : memory.pvp.convertToPlugin( inDims );
1057 0 : compressor.compress( memory.pixels, inDims, memory.compressorFlags );
1058 0 : memory.compressedData = compressor.getResult();
1059 0 : return memory;
1060 : }
1061 :
1062 :
1063 : //---------------------------------------------------------------------------
1064 : // File IO
1065 : //---------------------------------------------------------------------------
1066 3 : bool Image::writeImages( const std::string& filenameTemplate ) const
1067 : {
1068 15 : return( writeImage( filenameTemplate + "_color.rgb", Frame::BUFFER_COLOR) &&
1069 12 : writeImage( filenameTemplate + "_depth.rgb", Frame::BUFFER_DEPTH ));
1070 : }
1071 :
1072 : namespace
1073 : {
1074 : #define SWAP_SHORT(v) ( v = (v&0xff) << 8 | (v&0xff00) >> 8 )
1075 : #define SWAP_INT(v) ( v = (v&0xff) << 24 | (v&0xff00) << 8 | \
1076 : (v&0xff0000) >> 8 | (v&0xff000000) >> 24)
1077 :
1078 : #ifdef _WIN32
1079 : # pragma pack(1)
1080 : #endif
1081 : /** @cond IGNORE */
1082 : struct RGBHeader
1083 : {
1084 23 : RGBHeader()
1085 : {
1086 23 : memset( this, 0, sizeof( RGBHeader ));
1087 23 : magic = 474;
1088 23 : bytesPerChannel = 1;
1089 23 : nDimensions = 3;
1090 23 : maxValue = 255;
1091 23 : }
1092 :
1093 : /**
1094 : * Convert to and from big endian by swapping bytes on little endian
1095 : * machines.
1096 : */
1097 36 : void convert()
1098 : {
1099 : #if defined(__i386__) || defined(__amd64__) || defined (__ia64) || \
1100 : defined(__x86_64) || defined(_WIN32)
1101 36 : SWAP_SHORT(magic);
1102 36 : SWAP_SHORT(nDimensions);
1103 36 : SWAP_SHORT(width);
1104 36 : SWAP_SHORT(height);
1105 36 : SWAP_SHORT(depth);
1106 36 : SWAP_INT(minValue);
1107 36 : SWAP_INT(maxValue);
1108 36 : SWAP_INT(colorMode);
1109 : #endif
1110 36 : }
1111 :
1112 : unsigned short magic;
1113 : char compression;
1114 : char bytesPerChannel;
1115 : unsigned short nDimensions;
1116 : unsigned short width;
1117 : unsigned short height;
1118 : unsigned short depth;
1119 : unsigned minValue;
1120 : unsigned maxValue;
1121 : char unused[4];
1122 : char filename[80];
1123 : unsigned colorMode;
1124 : char fill[404];
1125 : }
1126 : /** @endcond */
1127 : #ifndef _WIN32
1128 : __attribute__((packed))
1129 : #endif
1130 : ;
1131 :
1132 4608000 : void put32f( std::ostream& os, const char* ptr )
1133 : {
1134 : // cppcheck-suppress invalidPointerCast
1135 4608000 : const float& value = *reinterpret_cast< const float* >( ptr );
1136 4608000 : const uint8_t byte = uint8_t( value * 255.f );
1137 4608000 : os.write( (const char*)&byte, 1 );
1138 4608000 : }
1139 2304000 : void put16f( std::ostream& os, const char* ptr )
1140 : {
1141 2304000 : const uint16_t& value = *reinterpret_cast< const uint16_t* >(ptr);
1142 2304000 : const float f = half_to_float( value );
1143 2304000 : put32f( os, (const char*)&f );
1144 2304000 : }
1145 : }
1146 :
1147 11 : bool Image::writeImage( const std::string& filename,
1148 : const Frame::Buffer buffer ) const
1149 : {
1150 11 : const Memory& memory = _impl->getMemory( buffer );
1151 :
1152 11 : const PixelViewport& pvp = memory.pvp;
1153 11 : const size_t nPixels = pvp.w * pvp.h;
1154 :
1155 11 : if( nPixels == 0 || memory.state != Memory::VALID )
1156 2 : return false;
1157 :
1158 : const unsigned char* data =
1159 9 : reinterpret_cast<const unsigned char*>( getPixelPointer( buffer ));
1160 :
1161 9 : unsigned char* convertedData = nullptr;
1162 :
1163 : // glReadPixels with alpha has ARGB premultiplied format: post-divide alpha
1164 9 : if( _impl->hasPremultipliedAlpha &&
1165 0 : getExternalFormat( buffer ) == EQ_COMPRESSOR_DATATYPE_BGRA )
1166 : {
1167 0 : convertedData = new unsigned char[nPixels*4];
1168 :
1169 0 : const uint32_t* bgraData = reinterpret_cast< const uint32_t* >( data );
1170 0 : uint32_t* bgraConverted = reinterpret_cast< uint32_t* >( convertedData);
1171 0 : for( size_t i = 0; i < nPixels; ++i, ++bgraConverted, ++bgraData )
1172 : {
1173 0 : *bgraConverted = *bgraData;
1174 0 : uint32_t& pixel = *bgraConverted;
1175 0 : const uint32_t alpha = pixel >> 24;
1176 0 : if( alpha != 0 )
1177 : {
1178 0 : const uint32_t red = (pixel >> 16) & 0xff;
1179 0 : const uint32_t green = (pixel >> 8) & 0xff;
1180 0 : const uint32_t blue = pixel & 0xff;
1181 0 : *bgraConverted = (( alpha << 24 ) |
1182 0 : (((255 * red) / alpha ) << 16 ) |
1183 0 : (((255 * green) / alpha ) << 8 ) |
1184 0 : ((255 * blue) / alpha ));
1185 : }
1186 : }
1187 : }
1188 :
1189 : const bool retVal = _writeImage( filename, buffer,
1190 9 : convertedData ? convertedData : data );
1191 9 : delete [] convertedData;
1192 9 : return retVal;
1193 : }
1194 :
1195 9 : bool Image::_writeImage( const std::string& filename,
1196 : const Frame::Buffer buffer,
1197 : const unsigned char* data_ ) const
1198 : {
1199 9 : const Memory& memory = _impl->getMemory( buffer );
1200 9 : const PixelViewport& pvp = memory.pvp;
1201 9 : const size_t nPixels = pvp.w * pvp.h;
1202 :
1203 9 : RGBHeader header;
1204 9 : header.width = pvp.w;
1205 9 : header.height = pvp.h;
1206 :
1207 9 : switch( getExternalFormat( buffer ))
1208 : {
1209 : case EQ_COMPRESSOR_DATATYPE_RGB10_A2:
1210 1 : header.maxValue = 1023;
1211 : case EQ_COMPRESSOR_DATATYPE_BGRA:
1212 : case EQ_COMPRESSOR_DATATYPE_BGRA_UINT_8_8_8_8_REV:
1213 : case EQ_COMPRESSOR_DATATYPE_RGBA:
1214 : case EQ_COMPRESSOR_DATATYPE_RGBA_UINT_8_8_8_8_REV:
1215 6 : header.bytesPerChannel = 1;
1216 6 : header.depth = 4;
1217 6 : break;
1218 : case EQ_COMPRESSOR_DATATYPE_BGR:
1219 : case EQ_COMPRESSOR_DATATYPE_RGB:
1220 0 : header.bytesPerChannel = 1;
1221 0 : header.depth = 3;
1222 0 : break;
1223 : case EQ_COMPRESSOR_DATATYPE_BGRA32F:
1224 : case EQ_COMPRESSOR_DATATYPE_RGBA32F:
1225 1 : header.bytesPerChannel = 4;
1226 1 : header.depth = 4;
1227 1 : break;
1228 : case EQ_COMPRESSOR_DATATYPE_BGR32F:
1229 : case EQ_COMPRESSOR_DATATYPE_RGB32F:
1230 0 : header.bytesPerChannel = 4;
1231 0 : header.depth = 3;
1232 0 : break;
1233 : case EQ_COMPRESSOR_DATATYPE_BGRA16F:
1234 : case EQ_COMPRESSOR_DATATYPE_RGBA16F:
1235 1 : header.bytesPerChannel = 2;
1236 1 : header.depth = 4;
1237 1 : break;
1238 : case EQ_COMPRESSOR_DATATYPE_BGR16F:
1239 : case EQ_COMPRESSOR_DATATYPE_RGB16F:
1240 0 : header.bytesPerChannel = 2;
1241 0 : header.depth = 3;
1242 0 : break;
1243 : case EQ_COMPRESSOR_DATATYPE_DEPTH_UNSIGNED_INT:
1244 1 : header.bytesPerChannel = 4;
1245 1 : header.depth = 1;
1246 1 : break;
1247 :
1248 : default:
1249 0 : LBERROR << "Unknown image pixel data type" << std::endl;
1250 0 : return false;
1251 : }
1252 :
1253 9 : if( header.depth == 1 ) // depth
1254 : {
1255 1 : LBASSERT( (header.bytesPerChannel % 4) == 0 );
1256 1 : header.depth = 4;
1257 1 : header.bytesPerChannel /= 4;
1258 : }
1259 9 : LBASSERT( header.bytesPerChannel > 0 );
1260 :
1261 : // Swap red & blue where needed
1262 9 : bool swapRB = false;
1263 9 : switch( getExternalFormat( buffer ))
1264 : {
1265 : case EQ_COMPRESSOR_DATATYPE_RGB10_A2:
1266 : case EQ_COMPRESSOR_DATATYPE_RGBA:
1267 : case EQ_COMPRESSOR_DATATYPE_RGBA_UINT_8_8_8_8_REV:
1268 : case EQ_COMPRESSOR_DATATYPE_RGB:
1269 : case EQ_COMPRESSOR_DATATYPE_RGBA32F:
1270 : case EQ_COMPRESSOR_DATATYPE_RGB32F:
1271 : case EQ_COMPRESSOR_DATATYPE_RGBA16F:
1272 : case EQ_COMPRESSOR_DATATYPE_RGB16F:
1273 8 : swapRB = true;
1274 : }
1275 :
1276 9 : const uint8_t bpc = header.bytesPerChannel;
1277 9 : const uint16_t nChannels = header.depth;
1278 9 : const size_t depth = nChannels * bpc;
1279 :
1280 9 : const boost::filesystem::path path( filename );
1281 : #ifdef EQUALIZER_USE_OPENSCENEGRAPH
1282 9 : if( path.extension() != ".rgb" )
1283 : {
1284 0 : osg::ref_ptr<osg::Image> osgImage = new osg::Image();
1285 0 : osgImage->setImage( pvp.w, pvp.h, depth, getExternalFormat( buffer ),
1286 : swapRB ? GL_RGBA : GL_BGRA, GL_UNSIGNED_BYTE,
1287 : const_cast< unsigned char* >( data_ ),
1288 0 : osg::Image::NO_DELETE );
1289 0 : return osgDB::writeImageFile( *osgImage, filename );
1290 : }
1291 : #endif
1292 :
1293 18 : std::ofstream image( filename.c_str(), std::ios::out | std::ios::binary );
1294 9 : if( !image.is_open( ))
1295 : {
1296 0 : LBERROR << "Can't open " << filename << " for writing" << std::endl;
1297 0 : return false;
1298 : }
1299 :
1300 9 : const size_t nBytes = nPixels * depth;
1301 9 : if( header.bytesPerChannel > 2 )
1302 3 : LBWARN << static_cast< int >( header.bytesPerChannel )
1303 3 : << " bytes per channel not supported by RGB spec" << std::endl;
1304 :
1305 9 : strncpy( header.filename, filename.c_str(), 80 );
1306 9 : header.convert();
1307 9 : image.write( reinterpret_cast<const char *>( &header ), sizeof( header ));
1308 9 : header.convert();
1309 :
1310 9 : const char* data = reinterpret_cast< const char* >( data_ );
1311 :
1312 : // Each channel is saved separately
1313 9 : if( nChannels == 3 || nChannels == 4 )
1314 : {
1315 : // channel one is R or B
1316 9 : if ( swapRB )
1317 10278472 : for( size_t j = 0 * bpc; j < nBytes; j += depth )
1318 10278464 : image.write( &data[j], bpc );
1319 : else
1320 1310721 : for( size_t j = 2 * bpc; j < nBytes; j += depth )
1321 1310720 : image.write( &data[j], bpc );
1322 :
1323 : // channel two is G
1324 11589193 : for( size_t j = 1 * bpc; j < nBytes; j += depth )
1325 11589184 : image.write( &data[j], bpc );
1326 :
1327 : // channel three is B or G
1328 9 : if ( swapRB )
1329 10278472 : for( size_t j = 2 * bpc; j < nBytes; j += depth )
1330 10278464 : image.write( &data[j], bpc );
1331 : else
1332 1310721 : for( size_t j = 0; j < nBytes; j += depth )
1333 1310720 : image.write( &data[j], bpc );
1334 :
1335 : // channel four is Alpha
1336 9 : if( nChannels == 4 )
1337 11589193 : for( size_t j = 3 * bpc; j < nBytes; j += depth )
1338 11589193 : image.write( &data[j], bpc );
1339 : }
1340 : else
1341 : {
1342 0 : for( size_t i = 0; i < nChannels; i += bpc )
1343 0 : for( size_t j = i * bpc; j < nBytes; j += depth )
1344 0 : image.write(&data[j], bpc );
1345 : }
1346 9 : image.close();
1347 :
1348 9 : if( header.bytesPerChannel == 1 )
1349 7 : return true;
1350 : // else also write 8bpp version
1351 :
1352 4 : const std::string smallFilename = path.parent_path().string() + "/s_" +
1353 : #if BOOST_FILESYSTEM_VERSION == 3
1354 8 : path.filename().string();
1355 : #else
1356 : path.filename();
1357 : #endif
1358 2 : image.open( smallFilename.c_str(), std::ios::out | std::ios::binary );
1359 2 : if( !image.is_open( ))
1360 : {
1361 0 : LBERROR << "Can't open " << smallFilename << " for writing" <<std::endl;
1362 0 : return false;
1363 : }
1364 :
1365 2 : header.bytesPerChannel = 1;
1366 2 : header.maxValue = 255;
1367 2 : header.convert();
1368 2 : image.write( reinterpret_cast<const char *>( &header ), sizeof( header ));
1369 2 : header.convert();
1370 :
1371 2 : LBASSERTINFO( bpc == 2 || bpc == 4, bpc );
1372 2 : const bool twoBPC = bpc == 2;
1373 :
1374 2 : if( nChannels == 3 || nChannels == 4 )
1375 : {
1376 : // channel one is R or B
1377 2 : if ( swapRB )
1378 1152002 : for( size_t j = 0 * bpc; j < nBytes; j += depth )
1379 1152000 : twoBPC ? put16f( image, &data[j] ) : put32f( image, &data[j] );
1380 : else
1381 0 : for( size_t j = 2 * bpc; j < nBytes; j += depth )
1382 0 : twoBPC ? put16f( image, &data[j] ) : put32f( image, &data[j] );
1383 :
1384 : // channel two is G
1385 1152002 : for( size_t j = 1 * bpc; j < nBytes; j += depth )
1386 1152000 : twoBPC ? put16f( image, &data[j] ) : put32f( image, &data[j] );
1387 :
1388 : // channel three is B or G
1389 2 : if ( swapRB )
1390 1152002 : for( size_t j = 2 * bpc; j < nBytes; j += depth )
1391 1152000 : twoBPC ? put16f( image, &data[j] ) : put32f( image, &data[j] );
1392 : else
1393 0 : for( size_t j = 0; j < nBytes; j += depth )
1394 0 : twoBPC ? put16f( image, &data[j] ) : put32f( image, &data[j] );
1395 :
1396 : // channel four is Alpha
1397 2 : if( nChannels == 4 )
1398 1152002 : for( size_t j = 3 * bpc; j < nBytes; j += depth )
1399 1152002 : twoBPC ? put16f( image, &data[j] ) : put32f( image, &data[j] );
1400 : }
1401 : else
1402 : {
1403 0 : for( size_t i = 0; i < nChannels; i += bpc )
1404 0 : for( size_t j = i * bpc; j < nBytes; j += depth )
1405 0 : twoBPC ? put16f( image, &data[j] ) : put32f( image, &data[j] );
1406 : }
1407 2 : image.close();
1408 :
1409 11 : return true;
1410 : }
1411 :
1412 14 : bool Image::readImage( const std::string& filename, const Frame::Buffer buffer )
1413 : {
1414 14 : lunchbox::MemoryMap image;
1415 14 : const uint8_t* addr = static_cast< const uint8_t* >( image.map( filename ));
1416 :
1417 14 : if( !addr )
1418 : {
1419 0 : LBERROR << "Can't open " << filename << " for reading" << std::endl;
1420 0 : return false;
1421 : }
1422 :
1423 14 : const size_t size = image.getSize();
1424 14 : if( size < sizeof( RGBHeader ))
1425 : {
1426 0 : LBWARN << "Image " << filename << " too small" << std::endl;
1427 0 : return false;
1428 : }
1429 :
1430 14 : RGBHeader header;
1431 14 : memcpy( &header, addr, sizeof( header ));
1432 14 : addr += sizeof( header );
1433 :
1434 14 : header.convert();
1435 :
1436 14 : if( header.magic != 474)
1437 : {
1438 0 : LBERROR << "Bad magic number " << filename << std::endl;
1439 0 : return false;
1440 : }
1441 14 : if( header.width == 0 || header.height == 0 )
1442 : {
1443 0 : LBERROR << "Zero-sized image " << filename << std::endl;
1444 0 : return false;
1445 : }
1446 14 : if( header.compression != 0)
1447 : {
1448 0 : LBERROR << "Unsupported compression " << filename << std::endl;
1449 0 : return false;
1450 : }
1451 :
1452 14 : const unsigned nChannels = header.depth;
1453 :
1454 28 : if( header.nDimensions != 3 ||
1455 28 : header.minValue != 0 ||
1456 29 : ( header.maxValue != 255 && header.maxValue != 1023 ) ||
1457 28 : header.colorMode != 0 ||
1458 11 : ( buffer == Frame::BUFFER_COLOR && nChannels != 3 && nChannels != 4 ) ||
1459 3 : ( buffer == Frame::BUFFER_DEPTH && nChannels != 4 ))
1460 : {
1461 0 : LBERROR << "Unsupported image type " << filename << std::endl;
1462 0 : return false;
1463 : }
1464 :
1465 16 : if(( header.bytesPerChannel != 1 || nChannels == 1 ) &&
1466 2 : header.maxValue != 255 )
1467 : {
1468 0 : LBERROR << "Unsupported value range " << header.maxValue << std::endl;
1469 0 : return false;
1470 : }
1471 :
1472 14 : const uint8_t bpc = header.bytesPerChannel;
1473 14 : const size_t depth = nChannels * bpc;
1474 14 : const size_t nPixels = header.width * header.height;
1475 14 : const size_t nComponents = nPixels * nChannels;
1476 14 : const size_t nBytes = nComponents * bpc;
1477 :
1478 14 : if( size < sizeof( RGBHeader ) + nBytes )
1479 : {
1480 0 : LBERROR << "Image " << filename << " too small" << std::endl;
1481 0 : return false;
1482 : }
1483 14 : LBASSERTINFO( size == sizeof( RGBHeader ) + nBytes,
1484 : "delta " << size - sizeof( RGBHeader ) - nBytes );
1485 :
1486 14 : switch( buffer )
1487 : {
1488 : case Frame::BUFFER_DEPTH:
1489 3 : if( header.bytesPerChannel != 1 )
1490 : {
1491 0 : LBERROR << "Unsupported channel depth "
1492 0 : << static_cast< int >( header.bytesPerChannel )
1493 0 : << std::endl;
1494 0 : return false;
1495 : }
1496 : _setExternalFormat( Frame::BUFFER_DEPTH,
1497 : EQ_COMPRESSOR_DATATYPE_DEPTH_UNSIGNED_INT, 4,
1498 3 : false );
1499 : setInternalFormat( Frame::BUFFER_DEPTH,
1500 3 : EQ_COMPRESSOR_DATATYPE_DEPTH );
1501 3 : break;
1502 :
1503 : case Frame::BUFFER_COLOR:
1504 11 : switch( header.bytesPerChannel )
1505 : {
1506 : case 1:
1507 9 : if( header.maxValue == 1023 )
1508 : {
1509 1 : LBASSERT( nChannels==4 );
1510 : _setExternalFormat( Frame::BUFFER_COLOR,
1511 : EQ_COMPRESSOR_DATATYPE_RGB10_A2, 4,
1512 1 : true );
1513 : setInternalFormat( Frame::BUFFER_COLOR,
1514 1 : EQ_COMPRESSOR_DATATYPE_RGB10_A2 );
1515 : }
1516 : else
1517 : {
1518 8 : if( nChannels == 4 )
1519 : _setExternalFormat( Frame::BUFFER_COLOR,
1520 : EQ_COMPRESSOR_DATATYPE_RGBA,
1521 8 : 4, true );
1522 : else
1523 : {
1524 0 : LBASSERT( nChannels == 3 );
1525 : _setExternalFormat( Frame::BUFFER_COLOR,
1526 : EQ_COMPRESSOR_DATATYPE_RGB,
1527 0 : nChannels, false );
1528 : }
1529 : setInternalFormat( Frame::BUFFER_COLOR,
1530 8 : EQ_COMPRESSOR_DATATYPE_RGBA );
1531 : }
1532 9 : break;
1533 :
1534 : case 2:
1535 1 : if( nChannels == 4 )
1536 : _setExternalFormat( Frame::BUFFER_COLOR,
1537 : EQ_COMPRESSOR_DATATYPE_RGBA16F,
1538 1 : 8, true );
1539 : else
1540 : {
1541 0 : LBASSERT( nChannels == 3 );
1542 : _setExternalFormat( Frame::BUFFER_COLOR,
1543 : EQ_COMPRESSOR_DATATYPE_RGB16F,
1544 0 : nChannels * 2, false );
1545 : }
1546 :
1547 : setInternalFormat( Frame::BUFFER_COLOR,
1548 1 : EQ_COMPRESSOR_DATATYPE_RGBA16F );
1549 1 : break;
1550 :
1551 : case 4:
1552 1 : if( nChannels == 4 )
1553 : _setExternalFormat( Frame::BUFFER_COLOR,
1554 : EQ_COMPRESSOR_DATATYPE_RGBA32F,
1555 1 : 16, true );
1556 : else
1557 : {
1558 0 : LBASSERT( nChannels == 3 );
1559 : _setExternalFormat( Frame::BUFFER_COLOR,
1560 : EQ_COMPRESSOR_DATATYPE_RGBA32F,
1561 0 : nChannels *4, false );
1562 : }
1563 : setInternalFormat( Frame::BUFFER_COLOR,
1564 1 : EQ_COMPRESSOR_DATATYPE_RGBA32F );
1565 1 : break;
1566 :
1567 : default:
1568 0 : LBERROR << "Unsupported channel depth "
1569 0 : << static_cast< int >( header.bytesPerChannel )
1570 0 : << std::endl;
1571 0 : return false;
1572 : }
1573 11 : break;
1574 :
1575 : default:
1576 0 : LBUNREACHABLE;
1577 : }
1578 14 : Memory& memory = _impl->getMemory( buffer );
1579 14 : const PixelViewport pvp( 0, 0, header.width, header.height );
1580 14 : if( pvp != _impl->pvp )
1581 : {
1582 10 : setPixelViewport( pvp );
1583 : }
1584 :
1585 14 : if ( memory.pvp != pvp )
1586 : {
1587 13 : memory.pvp = pvp;
1588 13 : memory.state = Memory::INVALID;
1589 : }
1590 14 : validatePixelData( buffer );
1591 :
1592 14 : uint8_t* data = reinterpret_cast< uint8_t* >( memory.pixels );
1593 14 : LBASSERTINFO( nBytes <= getPixelDataSize( buffer ),
1594 : nBytes << " > " << getPixelDataSize( buffer ));
1595 : // Each channel is saved separately
1596 14 : switch( bpc )
1597 : {
1598 : case 1:
1599 60 : for( size_t i = 0; i < nChannels; ++i )
1600 75909424 : for( size_t j = i; j < nComponents; j += nChannels )
1601 : {
1602 75909376 : data[j] = *addr;
1603 75909376 : ++addr;
1604 : }
1605 12 : break;
1606 :
1607 : case 2:
1608 5 : for( size_t i = 0; i < nChannels; ++i )
1609 2304004 : for( size_t j = i; j < nComponents; j += nChannels )
1610 : {
1611 2304000 : reinterpret_cast< uint16_t* >( data )[ j ] =
1612 2304000 : *reinterpret_cast< const uint16_t* >( addr );
1613 2304000 : addr += bpc;
1614 : }
1615 1 : break;
1616 :
1617 : case 4:
1618 5 : for( size_t i = 0; i < nChannels; ++i )
1619 2304004 : for( size_t j = i; j < nComponents; j += nChannels )
1620 : {
1621 2304000 : reinterpret_cast< uint32_t* >( data )[ j ] =
1622 2304000 : *reinterpret_cast< const uint32_t* >( addr );
1623 2304000 : addr += bpc;
1624 : }
1625 1 : break;
1626 :
1627 : default:
1628 0 : for( size_t i = 0; i < depth; i += bpc )
1629 0 : for( size_t j = i * bpc; j < nBytes; j += depth )
1630 : {
1631 0 : memcpy( &data[j], addr, bpc );
1632 0 : addr += bpc;
1633 : }
1634 0 : break;
1635 : }
1636 14 : return true;
1637 : }
1638 :
1639 18 : uint32_t Image::getExternalFormat( const Frame::Buffer buffer ) const
1640 : {
1641 18 : return _impl->getMemory( buffer ).externalFormat;
1642 : }
1643 :
1644 42 : uint32_t Image::getPixelSize( const Frame::Buffer buffer ) const
1645 : {
1646 42 : return _impl->getMemory( buffer ).pixelSize;
1647 : }
1648 :
1649 6 : void Image::setStorageType( const Frame::Type type )
1650 : {
1651 6 : _impl->type = type;
1652 6 : }
1653 :
1654 63 : Frame::Type Image::getStorageType() const
1655 : {
1656 63 : return _impl->type;
1657 : }
1658 :
1659 126 : const PixelViewport& Image::getPixelViewport() const
1660 : {
1661 126 : return _impl->pvp;
1662 : }
1663 :
1664 0 : void Image::setZoom( const Zoom& zoom )
1665 : {
1666 0 : _impl->zoom = zoom;
1667 0 : }
1668 :
1669 63 : const Zoom& Image::getZoom() const
1670 : {
1671 63 : return _impl->zoom;
1672 : }
1673 :
1674 820 : void Image::setContext( const RenderContext& context )
1675 : {
1676 820 : _impl->context = context;
1677 820 : }
1678 :
1679 63 : const RenderContext& Image::getContext() const
1680 : {
1681 63 : return _impl->context;
1682 : }
1683 :
1684 535 : bool Image::hasPixelData( const Frame::Buffer buffer ) const
1685 : {
1686 535 : return _impl->getMemory( buffer ).state == Memory::VALID;
1687 : }
1688 :
1689 0 : bool Image::hasAsyncReadback( const Frame::Buffer buffer ) const
1690 : {
1691 0 : return _impl->getMemory( buffer ).state == Memory::DOWNLOAD;
1692 : }
1693 :
1694 0 : bool Image::hasAsyncReadback() const
1695 : {
1696 0 : return hasAsyncReadback( Frame::BUFFER_COLOR ) ||
1697 0 : hasAsyncReadback( Frame::BUFFER_DEPTH );
1698 : }
1699 :
1700 0 : bool Image::getAlphaUsage() const
1701 : {
1702 0 : return !_impl->ignoreAlpha;
1703 : }
1704 :
1705 0 : void Image::setOffset( int32_t x, int32_t y )
1706 : {
1707 0 : _impl->pvp.x = x;
1708 0 : _impl->pvp.y = y;
1709 0 : }
1710 :
1711 42 : }
|