From 9440d9fa3177e599b45fc4d12be01a8304c05df2 Mon Sep 17 00:00:00 2001 From: Daniel Nachbaur Date: Wed, 4 Jan 2017 11:14:08 +0100 Subject: [PATCH] Implement view screenshot feature --- doc/Changelog.md | 2 + eq/channel.cpp | 3 +- eq/config.cpp | 14 ++++- eq/detail/channel.ipp | 40 ++++++++++---- eq/fabric/eventType.h | 4 +- eq/fabric/view.h | 27 ++++++---- eq/fabric/view.ipp | 23 +++++++- eq/image.cpp | 111 ++++++++++++++++++++++++++++++++++++-- eq/image.h | 25 +++++++-- eq/node.h | 1 + eq/pixelData.cpp | 17 ++++-- eq/pixelData.h | 14 +++-- eq/transferFinder.h | 6 +-- eq/view.cpp | 91 ++++++++++++++++++++++++++++++- eq/view.h | 33 +++++++++++- examples/eqPly/config.cpp | 32 ++++++++++- examples/eqPly/config.h | 3 +- examples/eqPly/eqPly.cpp | 7 +-- 18 files changed, 398 insertions(+), 55 deletions(-) diff --git a/doc/Changelog.md b/doc/Changelog.md index 08703a5a08..6cfdf4ccee 100644 --- a/doc/Changelog.md +++ b/doc/Changelog.md @@ -6,6 +6,8 @@ Changelog {#Changelog} * [608](https://github.com/Eyescale/Equalizer/pull/608): Make Frame::Buffer enum an enum class to ease bitmask handling; enum values are now Frame::Buffer::value instead of Frame::BUFFER_VALUE +* [607](https://github.com/Eyescale/Equalizer/pull/607): + Implement view screenshot feature with eq::View::enableScreenshot() * [606](https://github.com/Eyescale/Equalizer/pull/606): Remove unused big-endian/byteswap support * [601](https://github.com/Eyescale/Equalizer/pull/601): diff --git a/eq/channel.cpp b/eq/channel.cpp index 923a3bad69..9c2785a56f 100644 --- a/eq/channel.cpp +++ b/eq/channel.cpp @@ -1439,8 +1439,7 @@ void Channel::_transmitImage( const co::ObjectVersion& frameDataVersion, if( isCompressed ) { - BOOST_FOREACH( const pression::CompressorChunk& chunk, - data->compressedData.chunks ) + for( const auto& chunk : data->compressedData.chunks ) { const uint64_t dataSize = chunk.getNumBytes(); diff --git a/eq/config.cpp b/eq/config.cpp index 171f67d010..6e97587813 100644 --- a/eq/config.cpp +++ b/eq/config.cpp @@ -1,5 +1,5 @@ -/* Copyright (c) 2005-2016, Stefan Eilemann +/* Copyright (c) 2005-2017, Stefan Eilemann * Daniel Nachbaur * Cedric Stalder * @@ -36,9 +36,9 @@ #include "view.h" #include "window.h" -#include #include #include +#include #include #include #include @@ -668,6 +668,16 @@ bool Config::handleEvent( EventICommand command ) return false; } + case EVENT_VIEW_SCREENSHOT: + { + const uint128_t& originator = command.read< uint128_t >(); + LBASSERT( originator != 0 ); + View* view = find< View >( originator ); + if( view ) + return view->handleEvent( command ); + return false; + } + case EVENT_NODE_TIMEOUT: case EVENT_WINDOW_SCREENSAVER: diff --git a/eq/detail/channel.ipp b/eq/detail/channel.ipp index 0003c8228a..5b147c3289 100644 --- a/eq/detail/channel.ipp +++ b/eq/detail/channel.ipp @@ -22,8 +22,6 @@ #include "../resultImageListener.h" #include "fileFrameWriter.h" -#include - #ifdef EQUALIZER_USE_DEFLECT # include "../deflect/proxy.h" #endif @@ -101,29 +99,49 @@ public: } #endif - if( resultImageListeners.empty( )) + eq::View* view = channel.getView(); + eq::Frame::Buffer buffers = view->getScreenshotBuffers(); + if( !resultImageListeners.empty( )) + buffers |= eq::Frame::Buffer::color; + + if( buffers == eq::Frame::Buffer::none ) return; - downloadFramebuffer( channel ); - BOOST_FOREACH( ResultImageListener* listener, resultImageListeners ) + downloadFramebuffer( channel, buffers ); + for( ResultImageListener* listener : resultImageListeners ) listener->notifyNewImage( channel, framebufferImage ); + + if( view->getScreenshotBuffers() != eq::Frame::Buffer::none ) + { + view->sendScreenshotEvent( channel.getViewport(), + channel.getPipe()->getCurrentFrame(), + framebufferImage ); + } } - void downloadFramebuffer( eq::Channel& channel ) + void downloadFramebuffer( eq::Channel& channel, + const eq::Frame::Buffer buffers ) { framebufferImage.setAlphaUsage( true ); framebufferImage.setQuality( eq::Frame::Buffer::color, 1.0f ); framebufferImage.setStorageType( eq::Frame::TYPE_MEMORY ); framebufferImage.setInternalFormat( eq::Frame::Buffer::color, GL_RGBA ); - if( framebufferImage.startReadback( eq::Frame::Buffer::color, + if( buffers & eq::Frame::Buffer::color ) + framebufferImage.startReadback( eq::Frame::Buffer::color, channel.getPixelViewport(), channel.getContext(), channel.getZoom(), - channel.getObjectManager( ))) - { - framebufferImage.finishReadback( channel.glewGetContext( )); - } + channel.getObjectManager( )); + + if( buffers & eq::Frame::Buffer::depth ) + framebufferImage.startReadback( eq::Frame::Buffer::depth, + channel.getPixelViewport(), + channel.getContext(), + channel.getZoom(), + channel.getObjectManager( )); + + framebufferImage.finishReadback( channel.glewGetContext( )); } /** The configInit/configExit state. */ diff --git a/eq/fabric/eventType.h b/eq/fabric/eventType.h index f0b422f148..ca67f2dc91 100644 --- a/eq/fabric/eventType.h +++ b/eq/fabric/eventType.h @@ -1,5 +1,5 @@ -/* Copyright (c) 2016, Stefan.Eilemann@epfl.ch +/* Copyright (c) 2016-2017, Stefan.Eilemann@epfl.ch * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 2.1 as published @@ -83,6 +83,8 @@ enum EventType // Also update string table in event.cpp EVENT_WINDOW_ERROR, //!< Window error event. @sa CONFIG_ERROR EVENT_CHANNEL_ERROR, //!< Channel error event. @sa CONFIG_ERROR + EVENT_VIEW_SCREENSHOT, //!< Viewport, frameNumber, Image with requested buffers + // todo EVENT_NODE_TIMEOUT, //!< Node has timed out EVENT_WINDOW_SCREENSAVER, //!< A window screensaver request (Win32 only) diff --git a/eq/fabric/view.h b/eq/fabric/view.h index 925d59566d..20eabbaf39 100644 --- a/eq/fabric/view.h +++ b/eq/fabric/view.h @@ -1,5 +1,5 @@ -/* Copyright (c) 2008-2015, Stefan Eilemann +/* Copyright (c) 2008-2017, Stefan Eilemann * Daniel Nachbaur * Cedric Stalder * @@ -21,11 +21,12 @@ #define EQFABRIC_VIEW_H #include -#include // member -#include // base class -#include // base class +#include // member +#include // enum Frame::Buffer +#include // base class +#include // base class #include -#include // member +#include // member #define EQ_MM 1000.f #define EQ_CM 100.f @@ -208,6 +209,12 @@ class View : public Object, public Frustum * @version 1.0 */ EQFABRIC_INL uint64_t getCapabilities() const; + + /** + * @return the currently set buffers for screenshot feature + * @version 2.1 + */ + EQFABRIC_INL Frame::Buffer getScreenshotBuffers() const; //@} void setCapabilities( const uint64_t bitmask ); //!< @internal @@ -228,11 +235,12 @@ class View : public Object, public Frustum DIRTY_EQUALIZERS = Object::DIRTY_CUSTOM << 9, DIRTY_MODELUNIT = Object::DIRTY_CUSTOM << 10, DIRTY_ATTRIBUTES = Object::DIRTY_CUSTOM << 11, + DIRTY_SCREENSHOT = Object::DIRTY_CUSTOM << 12, DIRTY_VIEW_BITS = DIRTY_VIEWPORT | DIRTY_OBSERVER | DIRTY_OVERDRAW | DIRTY_FRUSTUM | DIRTY_MODE | DIRTY_MINCAPS | DIRTY_MAXCAPS | DIRTY_CAPABILITIES | DIRTY_OBJECT_BITS | DIRTY_EQUALIZER | - DIRTY_EQUALIZERS | DIRTY_MODELUNIT | DIRTY_ATTRIBUTES + DIRTY_EQUALIZERS | DIRTY_MODELUNIT | DIRTY_ATTRIBUTES | DIRTY_SCREENSHOT }; /** String attributes. */ @@ -295,6 +303,9 @@ class View : public Object, public Frustum /** @internal */ virtual void notifyFrustumChanged() { setDirty( DIRTY_FRUSTUM ); } + /** @internal */ + void _setScreenshotBuffers( Frame::Buffer buffers ); + private: /** Parent layout (application-side). */ L* const _layout; @@ -311,6 +322,7 @@ class View : public Object, public Frustum uint64_t _capabilities; //!< intersection of all active channel caps uint32_t _equalizers; //!< Active Equalizers float _modelUnit; //!< Scaling of scene in this view + Frame::Buffer _screenshotBuffers; struct BackupData { @@ -325,9 +337,6 @@ class View : public Object, public Frustum /** String attributes. */ std::string _sAttributes[SATTR_ALL]; - - struct Private; - Private* _private; // placeholder for binary-compatible changes }; template< class L, class V, class O > diff --git a/eq/fabric/view.ipp b/eq/fabric/view.ipp index 856b49a476..042f2fa72f 100644 --- a/eq/fabric/view.ipp +++ b/eq/fabric/view.ipp @@ -1,5 +1,5 @@ -/* Copyright (c) 2008-2016, Stefan Eilemann +/* Copyright (c) 2008-2017, Stefan Eilemann * Daniel Nachbaur * Cedric Stalder * @@ -49,6 +49,7 @@ View< L, V, O >::View( L* layout ) , _capabilities( LB_BIT_ALL_64 ) , _equalizers( EQUALIZER_ALL ) , _modelUnit( EQ_M ) + , _screenshotBuffers( Frame::Buffer::none ) { // Note: Views are an exception to the strong structuring, since render // client views are multi-buffered (once per pipe) and do not have a parent @@ -98,6 +99,8 @@ void View< L, V, O >::serialize( co::DataOStream& os, const uint64_t dirtyBits ) os << _modelUnit; if( dirtyBits & DIRTY_ATTRIBUTES ) os << co::Array< std::string >( _sAttributes, SATTR_ALL ); + if( dirtyBits & DIRTY_SCREENSHOT ) + os << _screenshotBuffers; } template< class L, class V, class O > @@ -168,6 +171,8 @@ void View< L, V, O >::deserialize( co::DataIStream& is, is >> _modelUnit; if( dirtyBits & DIRTY_ATTRIBUTES ) is >> co::Array< std::string >( _sAttributes, SATTR_ALL ); + if( dirtyBits & DIRTY_SCREENSHOT ) + is >> _screenshotBuffers; } template< class L, class V, class O > @@ -364,6 +369,22 @@ uint64_t View< L, V, O >::getCapabilities() const return _capabilities; } +template< class L, class V, class O > +void View< L, V, O >::_setScreenshotBuffers( const Frame::Buffer buffers ) +{ + if( _screenshotBuffers == buffers ) + return; + + _screenshotBuffers = buffers; + setDirty( DIRTY_SCREENSHOT ); +} + +template< class L, class V, class O > +Frame::Buffer View< L, V, O >::getScreenshotBuffers() const +{ + return _screenshotBuffers; +} + template< class L, class V, class O > const std::string& View< L, V, O >::getSAttribute( const SAttribute attr ) const { diff --git a/eq/image.cpp b/eq/image.cpp index e089d4ec68..6307d17277 100644 --- a/eq/image.cpp +++ b/eq/image.cpp @@ -20,11 +20,10 @@ #include "image.h" -#include "gl.h" #include "half.h" #include "log.h" #include "pixelData.h" -#include "windowSystem.h" +#include "transferFinder.h" #include #include @@ -47,12 +46,13 @@ # include #endif -#include "transferFinder.h" - #ifdef EQUALIZER_USE_OPENSCENEGRAPH # include #endif +#include +#include + namespace eq { namespace @@ -66,6 +66,21 @@ struct Memory : public PixelData , hasAlpha( true ) {} + Memory( const Memory& rhs ) + : PixelData( rhs ) + , state( rhs.state ) + , localBuffer( rhs.localBuffer ) + , hasAlpha( rhs.hasAlpha ) + { + if( rhs.localBuffer.isEmpty( )) + { + const size_t size = rhs.pvp.w * rhs.pvp.h * rhs.pixelSize; + localBuffer.resize( size ); + memcpy( localBuffer.getData(), rhs.pixels, size ); + } + pixels = localBuffer.getData(); + } + void flush() { PixelData::reset(); @@ -102,6 +117,33 @@ struct Memory : public PixelData bool hasAlpha; //!< The uncompressed pixels contain alpha }; +co::DataOStream& operator << ( co::DataOStream& os, const Memory& mem ) +{ + if( !mem.pixels ) + return os << false; + const size_t size = mem.pvp.w * mem.pvp.h * mem.pixelSize; + return os << true << mem.hasAlpha << mem.state << mem.externalFormat + << mem.internalFormat << mem.pixelSize + << size << co::Array< void >( mem.pixels, size ) << mem.pvp; +} + +co::DataIStream& operator >> ( co::DataIStream& is, Memory& mem ) +{ + size_t size = 0; + bool haveData = false; + is >> haveData; + if( !haveData ) + return is; + + is >> mem.hasAlpha >> mem.state >> mem.externalFormat >> mem.internalFormat + >> mem.pixelSize >> size; + + mem.localBuffer.resize( size ); + is >> co::Array< void >( mem.localBuffer.getData(), size ) >> mem.pvp; + mem.pixels = mem.localBuffer.getData(); + return is; +} + enum ActivePlugin { PLUGIN_FULL, @@ -131,7 +173,15 @@ struct Attachment : active( PLUGIN_FULL ) , quality( 1.f ) , texture( GL_TEXTURE_RECTANGLE_ARB ) - {} + {} + + Attachment( const Attachment& rhs ) + : active( rhs.active ) + , quality( rhs.quality ) + , texture( GL_TEXTURE_RECTANGLE_ARB ) + , memory( rhs.memory ) + , zoom( rhs.zoom ) + {} ~Attachment() { @@ -160,6 +210,16 @@ struct Attachment downloader[ PLUGIN_LOSSY ].clear(); } }; + +co::DataOStream& operator << ( co::DataOStream& os, const Attachment& at ) +{ + return os << at.active << at.memory << at.quality << at.zoom; +} + +co::DataIStream& operator >> ( co::DataIStream& is, Attachment& at ) +{ + return is >> at.active >> at.memory >> at.quality >> at.zoom; +} } namespace detail @@ -173,6 +233,17 @@ class Image , hasPremultipliedAlpha( false ) {} + Image( const Image& rhs ) + : pvp( rhs.pvp ) + , context( rhs.context ) + , zoom( rhs.zoom ) + , type( rhs.type ) + , color( rhs.color ) + , depth( rhs.depth ) + , ignoreAlpha( rhs.ignoreAlpha ) + , hasPremultipliedAlpha( rhs.hasPremultipliedAlpha ) + {} + /** The rectangle of the current pixel data. */ PixelViewport pvp; @@ -245,6 +316,19 @@ Image::Image() reset(); } +Image::Image( const Image& rhs ) + : _impl( new detail::Image( *rhs._impl )) +{ +} + +Image& Image::operator=( Image&& rhs ) +{ + _impl = std::move( rhs._impl ); + rhs._impl = new detail::Image; + rhs.reset(); + return *this; +} + Image::~Image() { delete _impl; @@ -1700,4 +1784,21 @@ void Image::setOffset( int32_t x, int32_t y ) _impl->pvp.y = y; } +co::DataOStream& operator << ( co::DataOStream& os, const Image& image ) +{ + os << image._impl->color << image._impl->context << image._impl->depth + << image._impl->hasPremultipliedAlpha << image._impl->ignoreAlpha + << image._impl->pvp << image._impl->type << image._impl->zoom; + return os; +} + +co::DataIStream& operator >> ( co::DataIStream& is, Image& image ) +{ + is >> image._impl->color >> image._impl->context >> image._impl->depth + >> image._impl->hasPremultipliedAlpha >> image._impl->ignoreAlpha + >> image._impl->pvp >> image._impl->type >> image._impl->zoom; + return is; } + +} + diff --git a/eq/image.h b/eq/image.h index f504e45ca8..4600f50773 100644 --- a/eq/image.h +++ b/eq/image.h @@ -39,6 +39,12 @@ class Image /** Construct a new Image. @version 1.0 */ EQ_API Image(); + /** Copy-construct a new Image. @version 2.1 */ + EQ_API Image( const Image& ); + + /** Move-assign a new Image. @version 2.1 */ + EQ_API Image& operator=( Image&& rhs ); + /** Destruct the Image. @version 1.0 */ EQ_API virtual ~Image(); @@ -384,9 +390,11 @@ class Image //@} private: - Image( const Image& ) = delete; - Image& operator=( const Image& ) = delete; - detail::Image* const _impl; + Image& operator=( const Image& ) = delete; + detail::Image* _impl; + + friend co::DataOStream& operator << ( co::DataOStream& os, const Image& ); + friend co::DataIStream& operator >> ( co::DataIStream& is, Image& ); /** @return a unique key for the frame buffer attachment. */ const void* _getBufferKey( const Frame::Buffer buffer ) const; @@ -420,5 +428,14 @@ class Image bool _writeImage( const std::string& filename, const Frame::Buffer buffer, const unsigned char* data ) const; }; -}; + +/** eq::Image serializer. @version 2.1 */ +EQ_API co::DataOStream& operator << ( co::DataOStream& os, const Image& ); + +/** eq::Image deserializer. @version 2.1 */ +EQ_API co::DataIStream& operator >> ( co::DataIStream& is, Image& ); + +} + + #endif // EQ_IMAGE_H diff --git a/eq/node.h b/eq/node.h index d70ca6e4c8..b0509ccc2b 100644 --- a/eq/node.h +++ b/eq/node.h @@ -133,6 +133,7 @@ class Node : public fabric::Node< Config, Node, Pipe, NodeVisitor > * The task of this method is to update the node as necessary, and send it * to the application using Config::sendEvent(). * + * @param type unused * @param event the received event. * @return true if the event was handled, false if not. * @version 1.5.2 diff --git a/eq/pixelData.cpp b/eq/pixelData.cpp index 70d1f45853..378a6d93bf 100644 --- a/eq/pixelData.cpp +++ b/eq/pixelData.cpp @@ -1,7 +1,7 @@ -/* Copyright (c) 2006-2014, Stefan Eilemann - * 2011, Daniel Nachbaur - * 2010, Cedric Stalder +/* Copyright (c) 2006-2017, Stefan Eilemann + * Daniel Nachbaur + * Cedric Stalder * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 2.1 as published @@ -28,6 +28,17 @@ PixelData::PixelData() reset(); } +PixelData::PixelData( const PixelData& rhs ) + : internalFormat( rhs.internalFormat ) + , externalFormat( rhs.externalFormat ) + , pixelSize( rhs.pixelSize ) + , pvp( rhs.pvp ) + , compressedData( rhs.compressedData ) + , compressorName( rhs.compressorName ) + , compressorFlags( rhs.compressorFlags ) +{ +} + PixelData::~PixelData() { reset(); diff --git a/eq/pixelData.h b/eq/pixelData.h index 92970761e5..0a09f89ccb 100644 --- a/eq/pixelData.h +++ b/eq/pixelData.h @@ -1,7 +1,7 @@ -/* Copyright (c) 2006-2014, Stefan Eilemann - * 2011, Daniel Nachbaur - * 2010, Cedric Stalder +/* Copyright (c) 2006-2017, Stefan Eilemann + * Daniel Nachbaur + * Cedric Stalder * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 2.1 as published @@ -35,6 +35,9 @@ struct PixelData /** Construct new pixel data. @version 1.0 */ EQ_API PixelData(); + /** Copy-construct new pixel data. @version 2.1 */ + EQ_API PixelData( const PixelData& rhs ); + /** Destruct the pixel data. @version 1.0 */ EQ_API ~PixelData(); @@ -93,8 +96,9 @@ struct PixelData uint32_t compressorFlags; //!< Flags used for compression. @version 1.0 private: - PixelData( const PixelData& ) = delete; PixelData& operator=( const PixelData& ) = delete; }; -}; + +} + #endif // EQ_PIXELDATA_H diff --git a/eq/transferFinder.h b/eq/transferFinder.h index e6e4dabe35..3f3eb607ba 100644 --- a/eq/transferFinder.h +++ b/eq/transferFinder.h @@ -1,5 +1,5 @@ -/* Copyright (c) 2013, Stefan.Eilemann@epfl.ch +/* Copyright (c) 2013-2017, Stefan.Eilemann@epfl.ch * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 2.1 as published @@ -42,8 +42,8 @@ class TransferFinder : public pression::ConstPluginVisitor virtual ~TransferFinder() {} - virtual fabric::VisitorResult visit( const pression::Plugin& plugin, - const EqCompressorInfo& info ) + fabric::VisitorResult visit( const pression::Plugin& plugin, + const EqCompressorInfo& info ) final { if(( (info.capabilities & caps_) == caps_ ) && ( internal_ == EQ_COMPRESSOR_DATATYPE_NONE || diff --git a/eq/view.cpp b/eq/view.cpp index a1d8b92678..2cae4950f1 100644 --- a/eq/view.cpp +++ b/eq/view.cpp @@ -1,5 +1,5 @@ -/* Copyright (c) 2008-2015, Stefan Eilemann +/* Copyright (c) 2008-2017, Stefan Eilemann * Daniel Nachbaur * * This library is free software; you can redistribute it and/or modify it under @@ -18,10 +18,14 @@ #include "view.h" +#include "compositor.h" #include "config.h" -#include "pipe.h" +#include "eventICommand.h" +#include "image.h" +#include "imageOp.h" #include "layout.h" #include "observer.h" +#include "pipe.h" #include "server.h" #include @@ -43,6 +47,42 @@ class View /** Unmodified, baseline view frustum data, used for resizing. */ Frustum baseFrustum; + + struct Screenshot + { + std::unique_ptr< eq::Image > _image; + Viewport _viewport; + + bool isComplete() const + { + return _viewport == Viewport::FULL; + } + + void composite( const Viewport& viewport, const eq::Image& image ) + { + if( !_image ) + { + _viewport = viewport; + _image.reset( new eq::Image( image )); + return; + } + + ImageOps ops; + ImageOp current; + current.image = _image.get(); + ImageOp newImage; + newImage.image = ℑ + + ops.push_back( current ); + ops.push_back( newImage ); + _image.reset( + new eq::Image( *Compositor::mergeImagesCPU( ops, false ))); + _viewport.unite( viewport ); + } + }; + + std::map< uint32_t, Screenshot > screenshot; + eq::View::ScreenshotFunc screenshotFunc; }; } @@ -161,6 +201,53 @@ bool View::handleEvent( const EventType type, const SizeEvent& event ) } } +void View::enableScreenshot( Frame::Buffer buffers, const ScreenshotFunc& func ) +{ + Super::_setScreenshotBuffers( buffers ); + _impl->screenshotFunc = func; +} + +void View::disableScreenshot() +{ + Super::_setScreenshotBuffers( Frame::Buffer::none ); +} + +void View::sendScreenshotEvent( const Viewport& viewport, + const uint32_t frameNumber, const Image& image ) +{ + getConfig()->sendEvent( EVENT_VIEW_SCREENSHOT ) << getID() << viewport + << frameNumber << image; +} + +bool View::handleEvent( EventICommand& command ) +{ + switch( command.getEventType( )) + { + case EVENT_VIEW_SCREENSHOT: + return _handleScreenshot( command ); + } + return false; +} + +bool View::_handleScreenshot( EventICommand& command ) +{ + const auto& vp = command.read< Viewport >(); + const auto frameNumber = command.read< uint32_t >(); + const auto& newImage = command.read< Image >(); + + auto& screenshot = _impl->screenshot[ frameNumber ]; + screenshot.composite( vp, newImage ); + + if( screenshot.isComplete( )) + { + _impl->screenshotFunc( frameNumber, *screenshot.image ); + _impl->screenshot.erase( frameNumber ); + _impl->screenshotFunc = ScreenshotFunc(); + } + + return true; +} + } #include diff --git a/eq/view.h b/eq/view.h index a790f576c5..bd2f4772d4 100644 --- a/eq/view.h +++ b/eq/view.h @@ -1,5 +1,5 @@ -/* Copyright (c) 2008-2016, Stefan Eilemann +/* Copyright (c) 2008-2017, Stefan Eilemann * Daniel Nachbaur * * This library is free software; you can redistribute it and/or modify it under @@ -21,6 +21,7 @@ #include #include // member +#include // enum #include // enum #include // base class @@ -87,6 +88,34 @@ class View : public fabric::View< Layout, View, Observer > * @version 1.0 */ EQ_API virtual bool handleEvent( EventType type, const SizeEvent& event ); + + /** + * Callback function called during eq::Config::handleEvents() after + * capturing a complete screenshot. + * + * @version 2.1 + */ + using ScreenshotFunc = std::function< void( uint32_t, const Image& ) >; + + /** + * Enable recording of given buffers for screenshot feature. + * + * @param buffers bitmask of buffers to capture in screenshot image + * @param func callback function with frame number and screenshot image + * @version 2.1 + */ + EQ_API void enableScreenshot( Frame::Buffer buffers, + const ScreenshotFunc& func ); + + /** Stop recording of screenshots. @version 2.1 */ + EQ_API void disableScreenshot(); + + /** @internal */ + bool handleEvent( EventICommand& command ); + + /** @internal */ + void sendScreenshotEvent( const Viewport& viewport, + const uint32_t frameNumber, const Image& image ); //@} protected: @@ -120,6 +149,8 @@ class View : public fabric::View< Layout, View, Observer > private: detail::View* const _impl; + bool _handleScreenshot( EventICommand& command ); + Pipe* _pipe; // for render-client views friend class Pipe; }; diff --git a/examples/eqPly/config.cpp b/examples/eqPly/config.cpp index 1a9344e646..3c991c3ed3 100644 --- a/examples/eqPly/config.cpp +++ b/examples/eqPly/config.cpp @@ -1,5 +1,5 @@ -/* Copyright (c) 2006-2016, Stefan Eilemann +/* Copyright (c) 2006-2017, Stefan Eilemann * Daniel Nachbaur * Cedric Stalder * @@ -233,7 +233,13 @@ uint32_t Config::startFrame() const eq::uint128_t& version = _frameData.commit(); _redraw = false; - return eq::Config::startFrame( version ); + const auto frameNumber = eq::Config::startFrame( version ); + + View* view = _getCurrentView(); + if( view ) + view->disableScreenshot(); + + return frameNumber; } void Config::_updateData() @@ -401,6 +407,10 @@ bool Config::handleEvent( const eq::EventType type, const eq::KeyEvent& event ) _switchLayoutSize(); return true; + case eq::KC_F11: + _screenshot(); + return true; + case 'd': case 'D': _frameData.toggleColorMode(); @@ -928,6 +938,24 @@ void Config::_adjustModelScale( const float factor ) _setMessage( stream.str( )); } +void Config::_screenshot() +{ + View* view = _getCurrentView(); + if( !view ) + return; + + view->enableScreenshot( eq::Frame::Buffer::color, + [&]( const uint32_t frameNumber, const eq::Image& image ) + { + const std::string screenshotPath = "./eqPly_" + + std::to_string( frameNumber ) + ".png"; + image.writeImage( screenshotPath, eq::Frame::Buffer::color ); + std::ostringstream os; + os << "Screenshot written to " << screenshotPath; + _setMessage( os.str( )); + }); +} + void Config::_switchLayout( int32_t increment ) { if( !_currentCanvas ) diff --git a/examples/eqPly/config.h b/examples/eqPly/config.h index 3f4211d4ba..ff19286041 100644 --- a/examples/eqPly/config.h +++ b/examples/eqPly/config.h @@ -1,5 +1,5 @@ -/* Copyright (c) 2006-2016, Stefan Eilemann +/* Copyright (c) 2006-2017, Stefan Eilemann * Daniel Nachbaur * Cedric Stalder * @@ -133,6 +133,7 @@ class Config : public eq::Config void _adjustTileSize( const int delta ); void _adjustResistance( const int delta ); void _adjustModelScale( const float factor ); + void _screenshot(); void _switchLayout( int32_t increment ); void _switchLayoutSize(); void _toggleEqualizer(); diff --git a/examples/eqPly/eqPly.cpp b/examples/eqPly/eqPly.cpp index 7ad8a327f7..aa44b73103 100644 --- a/examples/eqPly/eqPly.cpp +++ b/examples/eqPly/eqPly.cpp @@ -1,6 +1,6 @@ -/* Copyright (c) 2005-2013, Stefan Eilemann - * 2012, Daniel Nachbaur +/* Copyright (c) 2005-2017, Stefan Eilemann + * Daniel Nachbaur * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -68,7 +68,8 @@ static const std::string _help( std::string( "\t\tp: Add passive stereo window\n" ) + std::string( "\t\tx: Remove window\n" ) + std::string( "\t\ty, Y: Adjust model unit\n" ) + - std::string( "\t\tz, Z: Adjust eye base\n" )); + std::string( "\t\tz, Z: Adjust eye base\n" ) + + std::string( "\t\tF11: Screenshot to $PWD/eqPly_NUM.png\n" )); } const std::string& EqPly::getHelp()