From 46796978fdbc207e22bf4d23ff720e62019de64e Mon Sep 17 00:00:00 2001 From: Robert Osfield Date: Thu, 17 Jul 2008 16:12:39 +0000 Subject: [PATCH] From Jean-Sebastien Guay and Robert Osfiled, osgViewer::ScreenCaptureHandler for taking screenshots --- applications/osgviewer/osgviewer.cpp | 3 + include/osgViewer/ViewerEventHandlers | 151 ++++++ src/osgViewer/CMakeLists.txt | 1 + src/osgViewer/ScreenCaptureHandler.cpp | 653 +++++++++++++++++++++++++ 4 files changed, 808 insertions(+) create mode 100644 src/osgViewer/ScreenCaptureHandler.cpp diff --git a/applications/osgviewer/osgviewer.cpp b/applications/osgviewer/osgviewer.cpp index 82186ca7c..7492a977f 100644 --- a/applications/osgviewer/osgviewer.cpp +++ b/applications/osgviewer/osgviewer.cpp @@ -109,6 +109,9 @@ int main(int argc, char** argv) // add the LOD Scale handler viewer.addEventHandler(new osgViewer::LODScaleHandler); + // add the LOD Scale handler + viewer.addEventHandler(new osgViewer::ScreenCaptureHandler); + // load the data osg::ref_ptr loadedModel = osgDB::readNodeFiles(arguments); if (!loadedModel) diff --git a/include/osgViewer/ViewerEventHandlers b/include/osgViewer/ViewerEventHandlers index 1d98359cf..08b02b180 100644 --- a/include/osgViewer/ViewerEventHandlers +++ b/include/osgViewer/ViewerEventHandlers @@ -290,6 +290,157 @@ class OSGVIEWER_EXPORT LODScaleHandler : public osgGA::GUIEventHandler }; +/** Abstract base class for what to do when a screen capture happens. */ +class OSGVIEWER_EXPORT CaptureOperation : public osg::Referenced +{ + public: + virtual void operator()(const osg::Image& image, const unsigned int context_id) = 0; +}; + +/** Concrete implementation of a CaptureOperation that writes the screen capture to a file. */ +class OSGVIEWER_EXPORT WriteToFileCaptureOperation : public osgViewer::CaptureOperation +{ + public: + enum SavePolicy + { + OVERWRITE, + SEQUENTIAL_NUMBER + // ... any others? + }; + + WriteToFileCaptureOperation(const std::string& filename, const std::string& extension, SavePolicy savePolicy = OVERWRITE); + + virtual void operator()(const osg::Image& image, const unsigned int context_id); + + void setSavePolicy(SavePolicy savePolicy) { _savePolicy = savePolicy; } + SavePolicy getSavePolicy() const { return _savePolicy; } + + protected: + const std::string _filename; + const std::string _extension; + + SavePolicy _savePolicy; + + std::vector _contextSaveCounter; +}; + +// From osgscreencapture example +/** Callback which will be added to a viewer's camera to do the actual screen capture. */ +class OSGVIEWER_EXPORT WindowCaptureCallback : public osg::Camera::DrawCallback +{ + public: + + enum Mode + { + READ_PIXELS, + SINGLE_PBO, + DOUBLE_PBO, + TRIPLE_PBO + }; + + enum FramePosition + { + START_FRAME, + END_FRAME + }; + + struct ContextData : public osg::Referenced + { + static unsigned int COUNTER; + + ContextData(osg::GraphicsContext* gc, Mode mode, GLenum readBuffer); + + void getSize(osg::GraphicsContext* gc, int& width, int& height); + + void updateTimings(osg::Timer_t tick_start, + osg::Timer_t tick_afterReadPixels, + osg::Timer_t tick_afterMemCpy, + unsigned int dataSize); + + void read(); + void readPixels(); + void singlePBO(osg::BufferObject::Extensions* ext); + void multiPBO(osg::BufferObject::Extensions* ext); + + typedef std::vector< osg::ref_ptr > ImageBuffer; + typedef std::vector< GLuint > PBOBuffer; + + osg::GraphicsContext* _gc; + unsigned int _index; + Mode _mode; + GLenum _readBuffer; + + GLenum _pixelFormat; + GLenum _type; + int _width; + int _height; + + unsigned int _currentImageIndex; + ImageBuffer _imageBuffer; + + unsigned int _currentPboIndex; + PBOBuffer _pboBuffer; + + unsigned int _reportTimingFrequency; + unsigned int _numTimeValuesRecorded; + double _timeForReadPixels; + double _timeForFullCopy; + double _timeForMemCpy; + osg::Timer_t _previousFrameTick; + + osg::ref_ptr _captureOperation; + }; + + WindowCaptureCallback(Mode mode, FramePosition position, GLenum readBuffer); + + FramePosition getFramePosition() const { return _position; } + + ContextData* createContextData(osg::GraphicsContext* gc) const; + ContextData* getContextData(osg::GraphicsContext* gc) const; + + void setCaptureOperation(CaptureOperation* operation); + CaptureOperation* getCaptureOperation() { return _contextDataMap.begin()->second->_captureOperation.get(); } + + virtual void operator () (osg::RenderInfo& renderInfo) const; + + typedef std::map > ContextDataMap; + + Mode _mode; + FramePosition _position; + GLenum _readBuffer; + mutable OpenThreads::Mutex _mutex; + mutable ContextDataMap _contextDataMap; + +}; + +/** Event handler that will capture the screen on key press. */ +class OSGVIEWER_EXPORT ScreenCaptureHandler : public osgGA::GUIEventHandler +{ + public: + ScreenCaptureHandler(); + + void setKeyEventTakeScreenShot(int key) { _keyEventTakeScreenShot = key; } + int getKeyEventTakeScreenShot() const { return _keyEventTakeScreenShot; } + + void setCaptureOperation(CaptureOperation* operation) { _callback->setCaptureOperation(operation); } + CaptureOperation* getCaptureOperation() const { return _callback->getCaptureOperation(); } + + // aa will point to an osgViewer::View, so we will take a screenshot + // of that view's graphics contexts. + bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa); + + /** Get the keyboard and mouse usage of this manipulator.*/ + virtual void getUsage(osg::ApplicationUsage& usage) const; + + protected: + int _keyEventTakeScreenShot; + // there could be a key to start taking screenshots every new frame + + osg::ref_ptr _callback; + + void addCallbackToViewer(osgViewer::ViewerBase& viewer); +}; + } #endif diff --git a/src/osgViewer/CMakeLists.txt b/src/osgViewer/CMakeLists.txt index 915124bdb..14dda55b3 100644 --- a/src/osgViewer/CMakeLists.txt +++ b/src/osgViewer/CMakeLists.txt @@ -26,6 +26,7 @@ SET(LIB_COMMON_FILES HelpHandler.cpp Renderer.cpp Scene.cpp + ScreenCaptureHandler.cpp StatsHandler.cpp Version.cpp View.cpp diff --git a/src/osgViewer/ScreenCaptureHandler.cpp b/src/osgViewer/ScreenCaptureHandler.cpp new file mode 100644 index 000000000..ca7d18e7f --- /dev/null +++ b/src/osgViewer/ScreenCaptureHandler.cpp @@ -0,0 +1,653 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield +* +* This library is open source and may be redistributed and/or modified under +* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or +* (at your option) any later version. The full license is in LICENSE file +* included with this distribution, and on the openscenegraph.org website. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* OpenSceneGraph Public License for more details. +*/ + +#include + +#include + +#include +#include + +namespace osgViewer +{ + + +WriteToFileCaptureOperation::WriteToFileCaptureOperation(const std::string& filename, + const std::string& extension, + SavePolicy savePolicy) + : _filename(filename), _extension(extension), _savePolicy(savePolicy) +{ +} + +void WriteToFileCaptureOperation::operator () (const osg::Image& image, const unsigned int context_id) +{ + if (_savePolicy == SEQUENTIAL_NUMBER) + { + if (_contextSaveCounter.size() <= context_id) + { + _contextSaveCounter.resize(context_id + 1); + _contextSaveCounter[context_id] = 0; + } + } + + std::stringstream filename; + filename << _filename << "_" << context_id; + + if (_savePolicy == SEQUENTIAL_NUMBER) + filename << "_" << _contextSaveCounter[context_id]; + + filename << "." << _extension; + + osgDB::writeImageFile(image, filename.str()); + + osg::notify(osg::INFO)<<"Taking a screenshot, saved as '"<tick(); + + osg::NotifySeverity level = osg::INFO; + + if (gc->getTraits()) + { + if (gc->getTraits()->alpha) + { + osg::notify(level)<<"Select GL_RGBA read back format"<getTraits()) + { + width = gc->getTraits()->width; + height = gc->getTraits()->height; + } +} + +void WindowCaptureCallback::ContextData::updateTimings(osg::Timer_t tick_start, + osg::Timer_t tick_afterReadPixels, + osg::Timer_t tick_afterMemCpy, + unsigned int dataSize) +{ +/* + if (!_reportTimingFrequency) return; + + double timeForReadPixels = osg::Timer::instance()->delta_s(tick_start, tick_afterReadPixels); + double timeForFullCopy = osg::Timer::instance()->delta_s(tick_start, tick_afterMemCpy); + double timeForMemCpy = osg::Timer::instance()->delta_s(tick_afterReadPixels, tick_afterMemCpy); + + _timeForReadPixels += timeForReadPixels; + _timeForFullCopy += timeForFullCopy; + _timeForMemCpy += timeForMemCpy; + + ++_numTimeValuesRecorded; + + if (_numTimeValuesRecorded==_reportTimingFrequency) + { + timeForReadPixels = _timeForReadPixels/double(_numTimeValuesRecorded); + timeForFullCopy = _timeForFullCopy/double(_numTimeValuesRecorded); + timeForMemCpy = _timeForMemCpy/double(_numTimeValuesRecorded); + + double averageFrameTime = osg::Timer::instance()->delta_s(_previousFrameTick, tick_afterMemCpy)/double(_numTimeValuesRecorded); + double fps = 1.0/averageFrameTime; + _previousFrameTick = tick_afterMemCpy; + + _timeForReadPixels = 0.0; + _timeForFullCopy = 0.0; + _timeForMemCpy = 0.0; + + _numTimeValuesRecorded = 0; + + double numMPixels = double(_width * _height) / 1000000.0; + double numMb = double(dataSize) / (1024*1024); + + int prec = osg::notify(osg::NOTICE).precision(5); + + if (timeForMemCpy==0.0) + { + osg::notify(osg::NOTICE)<<"fps = "<delta_s(tick_start, tick_afterReadPixels); + _timeForFullCopy = osg::Timer::instance()->delta_s(tick_start, tick_afterMemCpy); + _timeForMemCpy = osg::Timer::instance()->delta_s(tick_afterReadPixels, tick_afterMemCpy); +} + +void WindowCaptureCallback::ContextData::read() +{ + osg::BufferObject::Extensions* ext = osg::BufferObject::getExtensions(_gc->getState()->getContextID(),true); + + if (ext->isPBOSupported() && !_pboBuffer.empty()) + { + if (_pboBuffer.size()==1) + { + singlePBO(ext); + } + else + { + multiPBO(ext); + } + } + else + { + readPixels(); + } +} + + +void WindowCaptureCallback::ContextData::readPixels() +{ + // std::cout<<"readPixels("<<_fileName<<" image "<<_currentImageIndex<<" "<<_currentPboIndex<tick(); + +#if 1 + image->readPixels(0,0,_width,_height, + _pixelFormat,_type); +#endif + + osg::Timer_t tick_afterReadPixels = osg::Timer::instance()->tick(); + + updateTimings(tick_start, tick_afterReadPixels, tick_afterReadPixels, image->getTotalSizeInBytes()); + + if (_captureOperation.valid()) + { + (*_captureOperation)(*image, _index); + } + + _currentImageIndex = nextImageIndex; + _currentPboIndex = nextPboIndex; +} + +void WindowCaptureCallback::ContextData::singlePBO(osg::BufferObject::Extensions* ext) +{ + // std::cout<<"singelPBO( "<<_fileName<<" image "<<_currentImageIndex<<" "<<_currentPboIndex<s() != _width || + image->t() != _height) + { + //osg::notify(osg::NOTICE)<<"Allocating image "<allocateImage(_width, _height, 1, _pixelFormat, _type); + + if (pbo!=0) + { + //osg::notify(osg::NOTICE)<<"deleting pbo "<glDeleteBuffers (1, &pbo); + pbo = 0; + } + } + + + if (pbo==0) + { + ext->glGenBuffers(1, &pbo); + ext->glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, pbo); + ext->glBufferData(GL_PIXEL_PACK_BUFFER_ARB, image->getTotalSizeInBytes(), 0, GL_STREAM_READ); + + //osg::notify(osg::NOTICE)<<"Generating pbo "<glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, pbo); + } + + osg::Timer_t tick_start = osg::Timer::instance()->tick(); + +#if 1 + glReadPixels(0, 0, _width, _height, _pixelFormat, _type, 0); +#endif + + osg::Timer_t tick_afterReadPixels = osg::Timer::instance()->tick(); + + GLubyte* src = (GLubyte*)ext->glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, + GL_READ_ONLY_ARB); + if(src) + { + memcpy(image->data(), src, image->getTotalSizeInBytes()); + ext->glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB); + } + + ext->glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, 0); + + osg::Timer_t tick_afterMemCpy = osg::Timer::instance()->tick(); + + updateTimings(tick_start, tick_afterReadPixels, tick_afterMemCpy, image->getTotalSizeInBytes()); + + if (_captureOperation.valid()) + { + (*_captureOperation)(*image, _index); + } + + + _currentImageIndex = nextImageIndex; +} + +void WindowCaptureCallback::ContextData::multiPBO(osg::BufferObject::Extensions* ext) +{ + // std::cout<<"multiPBO( "<<_fileName<<" image "<<_currentImageIndex<<" "<<_currentPboIndex<s() != _width || + image->t() != _height) + { + //osg::notify(osg::NOTICE)<<"Allocating image "<allocateImage(_width, _height, 1, _pixelFormat, _type); + + if (read_pbo!=0) + { + //osg::notify(osg::NOTICE)<<"deleting pbo "<glDeleteBuffers (1, &read_pbo); + read_pbo = 0; + } + + if (copy_pbo!=0) + { + //osg::notify(osg::NOTICE)<<"deleting pbo "<glDeleteBuffers (1, ©_pbo); + copy_pbo = 0; + } + } + + + bool doCopy = copy_pbo!=0; + if (copy_pbo==0) + { + ext->glGenBuffers(1, ©_pbo); + ext->glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, copy_pbo); + ext->glBufferData(GL_PIXEL_PACK_BUFFER_ARB, image->getTotalSizeInBytes(), 0, GL_STREAM_READ); + + //osg::notify(osg::NOTICE)<<"Generating pbo "<glGenBuffers(1, &read_pbo); + ext->glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, read_pbo); + ext->glBufferData(GL_PIXEL_PACK_BUFFER_ARB, image->getTotalSizeInBytes(), 0, GL_STREAM_READ); + + //osg::notify(osg::NOTICE)<<"Generating pbo "<glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, read_pbo); + } + + osg::Timer_t tick_start = osg::Timer::instance()->tick(); + +#if 1 + glReadPixels(0, 0, _width, _height, _pixelFormat, _type, 0); +#endif + + osg::Timer_t tick_afterReadPixels = osg::Timer::instance()->tick(); + + if (doCopy) + { + + ext->glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, copy_pbo); + + GLubyte* src = (GLubyte*)ext->glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, + GL_READ_ONLY_ARB); + if(src) + { + memcpy(image->data(), src, image->getTotalSizeInBytes()); + ext->glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB); + } + + if (_captureOperation.valid()) + { + (*_captureOperation)(*image, _index); + } + } + + ext->glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, 0); + + osg::Timer_t tick_afterMemCpy = osg::Timer::instance()->tick(); + + updateTimings(tick_start, tick_afterReadPixels, tick_afterMemCpy, image->getTotalSizeInBytes()); + + _currentImageIndex = nextImageIndex; + _currentPboIndex = nextPboIndex; +} + + +WindowCaptureCallback::WindowCaptureCallback(Mode mode, FramePosition position, GLenum readBuffer) + : _mode(mode), + _position(position), + _readBuffer(readBuffer) +{ +} + +WindowCaptureCallback::ContextData* WindowCaptureCallback::createContextData(osg::GraphicsContext* gc) const +{ + std::stringstream filename; + filename << "screen_shot_"<<_contextDataMap.size(); + WindowCaptureCallback::ContextData* cd = new WindowCaptureCallback::ContextData(gc, _mode, _readBuffer); + cd->_captureOperation = new WriteToFileCaptureOperation(filename.str(), "jpg", WriteToFileCaptureOperation::SEQUENTIAL_NUMBER); + return cd; +} + +WindowCaptureCallback::ContextData* WindowCaptureCallback::getContextData(osg::GraphicsContext* gc) const +{ + OpenThreads::ScopedLock lock(_mutex); + osg::ref_ptr& data = _contextDataMap[gc]; + if (!data) data = createContextData(gc); + + return data.get(); +} + +void WindowCaptureCallback::setCaptureOperation(CaptureOperation* operation) +{ + // Set the capture operation for each ContextData. + for (ContextDataMap::iterator it = _contextDataMap.begin(); it != _contextDataMap.end(); ++it) + { + it->second->_captureOperation = operation; + } +} + + +void WindowCaptureCallback::operator () (osg::RenderInfo& renderInfo) const +{ + glReadBuffer(_readBuffer); + + osg::GraphicsContext* gc = renderInfo.getState()->getGraphicsContext(); + osg::ref_ptr cd = getContextData(gc); + cd->read(); + + // Since we just want to take one screenshot, the callback must remove + // itself when it's done. + if (_position == START_FRAME) + renderInfo.getCurrentCamera()->setInitialDrawCallback(0); + if (_position == END_FRAME) + renderInfo.getCurrentCamera()->setFinalDrawCallback(0); + + int prec = osg::notify(osg::INFO).precision(5); + osg::notify(osg::INFO)<<"Took a screenshot in " << (cd->_timeForFullCopy*1000.0f) << "ms" << std::endl; + osg::notify(osg::INFO).precision(prec); + + cd->_timeForFullCopy = 0; +} + + +ScreenCaptureHandler::ScreenCaptureHandler() + : _keyEventTakeScreenShot('c'), + _callback(new WindowCaptureCallback( + WindowCaptureCallback::READ_PIXELS, +// WindowCaptureCallback::SINGLE_PBO, +// WindowCaptureCallback::DOUBLE_PBO, +// WindowCaptureCallback::TRIPLE_PBO, + WindowCaptureCallback::END_FRAME, GL_BACK)) +{ +} + +void ScreenCaptureHandler::addCallbackToViewer(osgViewer::ViewerBase& viewer) +{ + // Select either the first or the last active camera, depending on the + // frame position set in the callback. + // One case where testing the node mask is important is when the stats + // handler has been initialized, but stats are not displayed. In that + // case, there is a post render camera on the viewer, but its node mask + // is zero, so the callback added to that camera would never be called. + + if (_callback->getFramePosition() == WindowCaptureCallback::START_FRAME) + { + osgViewer::ViewerBase::Windows windows; + viewer.getWindows(windows); + for(osgViewer::ViewerBase::Windows::iterator itr = windows.begin(); + itr != windows.end(); + ++itr) + { + osgViewer::GraphicsWindow* window = *itr; + osg::GraphicsContext::Cameras& cameras = window->getCameras(); + osg::Camera* firstCamera = 0; + for(osg::GraphicsContext::Cameras::iterator cam_itr = cameras.begin(); + cam_itr != cameras.end(); + ++cam_itr) + { + if (firstCamera) + { + if ((*cam_itr)->getRenderOrder() < firstCamera->getRenderOrder()) + { + if ((*cam_itr)->getNodeMask() != 0x0) + firstCamera = (*cam_itr); + } + if ((*cam_itr)->getRenderOrder() == firstCamera->getRenderOrder() && + (*cam_itr)->getRenderOrderNum() < firstCamera->getRenderOrderNum()) + { + if ((*cam_itr)->getNodeMask() != 0x0) + firstCamera = (*cam_itr); + } + } + else + { + if ((*cam_itr)->getNodeMask() != 0x0) + firstCamera = *cam_itr; + } + } + + if (firstCamera) + { + //osg::notify(osg::NOTICE)<<"First camera "<setInitialDrawCallback(_callback.get()); + } + else + { + osg::notify(osg::NOTICE)<<"No camera found"<getCameras(); + osg::Camera* lastCamera = 0; + for(osg::GraphicsContext::Cameras::iterator cam_itr = cameras.begin(); + cam_itr != cameras.end(); + ++cam_itr) + { + if (lastCamera) + { + if ((*cam_itr)->getRenderOrder() > lastCamera->getRenderOrder()) + { + if ((*cam_itr)->getNodeMask() != 0x0) + lastCamera = (*cam_itr); + } + if ((*cam_itr)->getRenderOrder() == lastCamera->getRenderOrder() && + (*cam_itr)->getRenderOrderNum() >= lastCamera->getRenderOrderNum()) + { + if ((*cam_itr)->getNodeMask() != 0x0) + lastCamera = (*cam_itr); + } + } + else + { + if ((*cam_itr)->getNodeMask() != 0x0) + lastCamera = *cam_itr; + } + } + + if (lastCamera) + { + //osg::notify(osg::NOTICE)<<"Last camera "<setFinalDrawCallback(_callback.get()); + } + else + { + osg::notify(osg::NOTICE)<<"No camera found"<(&aa)->getViewerBase(); + if (!viewer) return false; + + if (ea.getHandled()) return false; + + switch(ea.getEventType()) + { + case(osgGA::GUIEventAdapter::KEYUP): + { + if (ea.getKey() == _keyEventTakeScreenShot) + { + addCallbackToViewer(*viewer); + return true; + } + + break; + } + default: + break; + } + + return false; +} + +/** Get the keyboard and mouse usage of this manipulator.*/ +void ScreenCaptureHandler::getUsage(osg::ApplicationUsage& usage) const +{ + { + std::ostringstream ostr; + ostr<