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