mirror of
https://github.com/OpenMW/openmw.git
synced 2025-10-28 12:56:39 +00:00
Merge branch 'async_screenshot' into 'master'
Write screenshots to file asynchronously (#6143) Closes #6143 See merge request OpenMW/openmw!907
This commit is contained in:
commit
7f3a4315aa
17 changed files with 314 additions and 65 deletions
|
|
@ -17,6 +17,7 @@
|
||||||
Bug #6129: Player avatar not displayed correctly for large window sizes when GUI scaling active
|
Bug #6129: Player avatar not displayed correctly for large window sizes when GUI scaling active
|
||||||
Bug #6131: Item selection in the avatar window not working correctly for large window sizes
|
Bug #6131: Item selection in the avatar window not working correctly for large window sizes
|
||||||
Bug #6133: Cannot reliably sneak or steal in the sight of the NPCs siding with player
|
Bug #6133: Cannot reliably sneak or steal in the sight of the NPCs siding with player
|
||||||
|
Bug #6143: Capturing a screenshot makes engine to be a temporary unresponsive
|
||||||
Feature #2780: A way to see current OpenMW version in the console
|
Feature #2780: A way to see current OpenMW version in the console
|
||||||
Feature #5489: MCP: Telekinesis fix for activators
|
Feature #5489: MCP: Telekinesis fix for activators
|
||||||
Feature #6017: Separate persistent and temporary cell references when saving
|
Feature #6017: Separate persistent and temporary cell references when saving
|
||||||
|
|
|
||||||
|
|
@ -213,6 +213,8 @@ bool Launcher::AdvancedPage::loadSettings()
|
||||||
if (screenshotFormatComboBox->findText(screenshotFormatString) == -1)
|
if (screenshotFormatComboBox->findText(screenshotFormatString) == -1)
|
||||||
screenshotFormatComboBox->addItem(screenshotFormatString);
|
screenshotFormatComboBox->addItem(screenshotFormatString);
|
||||||
screenshotFormatComboBox->setCurrentIndex(screenshotFormatComboBox->findText(screenshotFormatString));
|
screenshotFormatComboBox->setCurrentIndex(screenshotFormatComboBox->findText(screenshotFormatString));
|
||||||
|
|
||||||
|
loadSettingBool(notifyOnSavedScreenshotCheckBox, "notify on saved screenshot", "General");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Testing
|
// Testing
|
||||||
|
|
@ -376,6 +378,8 @@ void Launcher::AdvancedPage::saveSettings()
|
||||||
std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString();
|
std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString();
|
||||||
if (screenshotFormatString != Settings::Manager::getString("screenshot format", "General"))
|
if (screenshotFormatString != Settings::Manager::getString("screenshot format", "General"))
|
||||||
Settings::Manager::setString("screenshot format", "General", screenshotFormatString);
|
Settings::Manager::setString("screenshot format", "General", screenshotFormatString);
|
||||||
|
|
||||||
|
saveSettingBool(notifyOnSavedScreenshotCheckBox, "notify on saved screenshot", "General");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Testing
|
// Testing
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@
|
||||||
|
|
||||||
#include <components/misc/frameratelimiter.hpp>
|
#include <components/misc/frameratelimiter.hpp>
|
||||||
|
|
||||||
|
#include <components/sceneutil/screencapture.hpp>
|
||||||
|
|
||||||
#include "mwinput/inputmanagerimp.hpp"
|
#include "mwinput/inputmanagerimp.hpp"
|
||||||
|
|
||||||
#include "mwgui/windowmanagerimp.hpp"
|
#include "mwgui/windowmanagerimp.hpp"
|
||||||
|
|
@ -216,6 +218,19 @@ namespace
|
||||||
if (Settings::Manager::getInt("async num threads", "Physics") == 0)
|
if (Settings::Manager::getInt("async num threads", "Physics") == 0)
|
||||||
profiler.removeUserStatsLine(" -Async");
|
profiler.removeUserStatsLine(" -Async");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ScheduleNonDialogMessageBox
|
||||||
|
{
|
||||||
|
void operator()(std::string message) const
|
||||||
|
{
|
||||||
|
MWBase::Environment::get().getWindowManager()->scheduleMessageBox(std::move(message), MWGui::ShowInDialogueMode_Never);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IgnoreString
|
||||||
|
{
|
||||||
|
void operator()(std::string) const {}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void OMW::Engine::executeLocalScripts()
|
void OMW::Engine::executeLocalScripts()
|
||||||
|
|
@ -405,6 +420,8 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
|
||||||
|
|
||||||
OMW::Engine::~Engine()
|
OMW::Engine::~Engine()
|
||||||
{
|
{
|
||||||
|
mWorkQueue->stop();
|
||||||
|
|
||||||
mEnvironment.cleanup();
|
mEnvironment.cleanup();
|
||||||
|
|
||||||
delete mScriptContext;
|
delete mScriptContext;
|
||||||
|
|
@ -668,6 +685,21 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
||||||
throw std::runtime_error("Invalid setting: 'preload num threads' must be >0");
|
throw std::runtime_error("Invalid setting: 'preload num threads' must be >0");
|
||||||
mWorkQueue = new SceneUtil::WorkQueue(numThreads);
|
mWorkQueue = new SceneUtil::WorkQueue(numThreads);
|
||||||
|
|
||||||
|
mScreenCaptureOperation = new SceneUtil::AsyncScreenCaptureOperation(
|
||||||
|
mWorkQueue,
|
||||||
|
new SceneUtil::WriteScreenshotToFileOperation(
|
||||||
|
mCfgMgr.getScreenshotPath().string(),
|
||||||
|
Settings::Manager::getString("screenshot format", "General"),
|
||||||
|
Settings::Manager::getBool("notify on saved screenshot", "General")
|
||||||
|
? std::function(ScheduleNonDialogMessageBox {})
|
||||||
|
: std::function(IgnoreString {})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation);
|
||||||
|
|
||||||
|
mViewer->addEventHandler(mScreenCaptureHandler);
|
||||||
|
|
||||||
// Create input and UI first to set up a bootstrapping environment for
|
// Create input and UI first to set up a bootstrapping environment for
|
||||||
// showing a loading screen and keeping the window responsive while doing so
|
// showing a loading screen and keeping the window responsive while doing so
|
||||||
|
|
||||||
|
|
@ -781,54 +813,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class WriteScreenshotToFileOperation : public osgViewer::ScreenCaptureHandler::CaptureOperation
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
WriteScreenshotToFileOperation(const std::string& screenshotPath, const std::string& screenshotFormat)
|
|
||||||
: mScreenshotPath(screenshotPath)
|
|
||||||
, mScreenshotFormat(screenshotFormat)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void operator()(const osg::Image& image, const unsigned int context_id) override
|
|
||||||
{
|
|
||||||
// Count screenshots.
|
|
||||||
int shotCount = 0;
|
|
||||||
|
|
||||||
// Find the first unused filename with a do-while
|
|
||||||
std::ostringstream stream;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
// Reset the stream
|
|
||||||
stream.str("");
|
|
||||||
stream.clear();
|
|
||||||
|
|
||||||
stream << mScreenshotPath << "/screenshot" << std::setw(3) << std::setfill('0') << shotCount++ << "." << mScreenshotFormat;
|
|
||||||
|
|
||||||
} while (boost::filesystem::exists(stream.str()));
|
|
||||||
|
|
||||||
boost::filesystem::ofstream outStream;
|
|
||||||
outStream.open(boost::filesystem::path(stream.str()), std::ios::binary);
|
|
||||||
|
|
||||||
osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension(mScreenshotFormat);
|
|
||||||
if (!readerwriter)
|
|
||||||
{
|
|
||||||
Log(Debug::Error) << "Error: Can't write screenshot, no '" << mScreenshotFormat << "' readerwriter found";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(image, outStream);
|
|
||||||
if (!result.success())
|
|
||||||
{
|
|
||||||
Log(Debug::Error) << "Error: Can't write screenshot: " << result.message() << " code " << result.status();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string mScreenshotPath;
|
|
||||||
std::string mScreenshotFormat;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialise and enter main loop.
|
// Initialise and enter main loop.
|
||||||
void OMW::Engine::go()
|
void OMW::Engine::go()
|
||||||
{
|
{
|
||||||
|
|
@ -860,14 +844,6 @@ void OMW::Engine::go()
|
||||||
mViewer->setUseConfigureAffinity(false);
|
mViewer->setUseConfigureAffinity(false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
mScreenCaptureOperation = new WriteScreenshotToFileOperation(
|
|
||||||
mCfgMgr.getScreenshotPath().string(),
|
|
||||||
Settings::Manager::getString("screenshot format", "General"));
|
|
||||||
|
|
||||||
mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation);
|
|
||||||
|
|
||||||
mViewer->addEventHandler(mScreenCaptureHandler);
|
|
||||||
|
|
||||||
mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video"));
|
mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video"));
|
||||||
|
|
||||||
prepareEngine (settings);
|
prepareEngine (settings);
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ namespace OMW
|
||||||
boost::filesystem::path mResDir;
|
boost::filesystem::path mResDir;
|
||||||
osg::ref_ptr<osgViewer::Viewer> mViewer;
|
osg::ref_ptr<osgViewer::Viewer> mViewer;
|
||||||
osg::ref_ptr<osgViewer::ScreenCaptureHandler> mScreenCaptureHandler;
|
osg::ref_ptr<osgViewer::ScreenCaptureHandler> mScreenCaptureHandler;
|
||||||
osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation;
|
osg::ref_ptr<osgViewer::ScreenCaptureHandler::CaptureOperation> mScreenCaptureOperation;
|
||||||
std::string mCellName;
|
std::string mCellName;
|
||||||
std::vector<std::string> mContentFiles;
|
std::vector<std::string> mContentFiles;
|
||||||
std::vector<std::string> mGroundcoverFiles;
|
std::vector<std::string> mGroundcoverFiles;
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,8 @@ namespace MWBase
|
||||||
virtual void exitCurrentGuiMode() = 0;
|
virtual void exitCurrentGuiMode() = 0;
|
||||||
|
|
||||||
virtual void messageBox (const std::string& message, enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) = 0;
|
virtual void messageBox (const std::string& message, enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) = 0;
|
||||||
|
/// Puts message into a queue to show on the next update. Thread safe alternative for messageBox.
|
||||||
|
virtual void scheduleMessageBox(std::string message, enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) = 0;
|
||||||
virtual void staticMessageBox(const std::string& message) = 0;
|
virtual void staticMessageBox(const std::string& message) = 0;
|
||||||
virtual void removeStaticMessageBox() = 0;
|
virtual void removeStaticMessageBox() = 0;
|
||||||
virtual void interactiveMessageBox (const std::string& message,
|
virtual void interactiveMessageBox (const std::string& message,
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,8 @@ namespace MWGui
|
||||||
if(stat)
|
if(stat)
|
||||||
mStaticMessageBox = box;
|
mStaticMessageBox = box;
|
||||||
|
|
||||||
|
box->setVisible(mVisible);
|
||||||
|
|
||||||
mMessageBoxes.push_back(box);
|
mMessageBoxes.push_back(box);
|
||||||
|
|
||||||
if(mMessageBoxes.size() > 3) {
|
if(mMessageBoxes.size() > 3) {
|
||||||
|
|
@ -167,8 +169,12 @@ namespace MWGui
|
||||||
return pressed;
|
return pressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MessageBoxManager::setVisible(bool value)
|
||||||
|
{
|
||||||
|
mVisible = value;
|
||||||
|
for (MessageBox* messageBox : mMessageBoxes)
|
||||||
|
messageBox->setVisible(value);
|
||||||
|
}
|
||||||
|
|
||||||
MessageBox::MessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message)
|
MessageBox::MessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message)
|
||||||
: Layout("openmw_messagebox.layout")
|
: Layout("openmw_messagebox.layout")
|
||||||
|
|
@ -201,7 +207,10 @@ namespace MWGui
|
||||||
return mMainWidget->getHeight()+mNextBoxPadding;
|
return mMainWidget->getHeight()+mNextBoxPadding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MessageBox::setVisible(bool value)
|
||||||
|
{
|
||||||
|
mMainWidget->setVisible(value);
|
||||||
|
}
|
||||||
|
|
||||||
InteractiveMessageBox::InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message, const std::vector<std::string>& buttons)
|
InteractiveMessageBox::InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message, const std::vector<std::string>& buttons)
|
||||||
: WindowModal(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "openmw_interactive_messagebox_notransp.layout" : "openmw_interactive_messagebox.layout")
|
: WindowModal(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "openmw_interactive_messagebox_notransp.layout" : "openmw_interactive_messagebox.layout")
|
||||||
|
|
|
||||||
|
|
@ -47,12 +47,15 @@ namespace MWGui
|
||||||
|
|
||||||
void onButtonPressed(int button) { eventButtonPressed(button); eventButtonPressed.clear(); }
|
void onButtonPressed(int button) { eventButtonPressed(button); eventButtonPressed.clear(); }
|
||||||
|
|
||||||
|
void setVisible(bool value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<MessageBox*> mMessageBoxes;
|
std::vector<MessageBox*> mMessageBoxes;
|
||||||
InteractiveMessageBox* mInterMessageBoxe;
|
InteractiveMessageBox* mInterMessageBoxe;
|
||||||
MessageBox* mStaticMessageBox;
|
MessageBox* mStaticMessageBox;
|
||||||
float mMessageBoxSpeed;
|
float mMessageBoxSpeed;
|
||||||
int mLastButtonPressed;
|
int mLastButtonPressed;
|
||||||
|
bool mVisible = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MessageBox : public Layout
|
class MessageBox : public Layout
|
||||||
|
|
@ -62,6 +65,7 @@ namespace MWGui
|
||||||
void setMessage (const std::string& message);
|
void setMessage (const std::string& message);
|
||||||
int getHeight ();
|
int getHeight ();
|
||||||
void update (int height);
|
void update (int height);
|
||||||
|
void setVisible(bool value);
|
||||||
|
|
||||||
float mCurrentTime;
|
float mCurrentTime;
|
||||||
float mMaxTime;
|
float mMaxTime;
|
||||||
|
|
|
||||||
|
|
@ -749,6 +749,11 @@ namespace MWGui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WindowManager::scheduleMessageBox(std::string message, enum MWGui::ShowInDialogueMode showInDialogueMode)
|
||||||
|
{
|
||||||
|
mScheduledMessageBoxes.lock()->emplace_back(std::move(message), showInDialogueMode);
|
||||||
|
}
|
||||||
|
|
||||||
void WindowManager::staticMessageBox(const std::string& message)
|
void WindowManager::staticMessageBox(const std::string& message)
|
||||||
{
|
{
|
||||||
mMessageBoxManager->createMessageBox(message, true);
|
mMessageBoxManager->createMessageBox(message, true);
|
||||||
|
|
@ -803,6 +808,8 @@ namespace MWGui
|
||||||
|
|
||||||
void WindowManager::update (float frameDuration)
|
void WindowManager::update (float frameDuration)
|
||||||
{
|
{
|
||||||
|
handleScheduledMessageBoxes();
|
||||||
|
|
||||||
bool gameRunning = MWBase::Environment::get().getStateManager()->getState()!=
|
bool gameRunning = MWBase::Environment::get().getStateManager()->getState()!=
|
||||||
MWBase::StateManager::State_NoGame;
|
MWBase::StateManager::State_NoGame;
|
||||||
|
|
||||||
|
|
@ -1492,6 +1499,7 @@ namespace MWGui
|
||||||
{
|
{
|
||||||
mHudEnabled = !mHudEnabled;
|
mHudEnabled = !mHudEnabled;
|
||||||
updateVisible();
|
updateVisible();
|
||||||
|
mMessageBoxManager->setVisible(mHudEnabled);
|
||||||
return mHudEnabled;
|
return mHudEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2204,4 +2212,12 @@ namespace MWGui
|
||||||
{
|
{
|
||||||
return mVersionDescription;
|
return mVersionDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WindowManager::handleScheduledMessageBoxes()
|
||||||
|
{
|
||||||
|
const auto scheduledMessageBoxes = mScheduledMessageBoxes.lock();
|
||||||
|
for (const ScheduledMessageBox& v : *scheduledMessageBoxes)
|
||||||
|
messageBox(v.mMessage, v.mShowInDialogueMode);
|
||||||
|
scheduledMessageBoxes->clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
**/
|
**/
|
||||||
|
|
||||||
#include <stack>
|
#include <stack>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <osg/ref_ptr>
|
#include <osg/ref_ptr>
|
||||||
|
|
||||||
|
|
@ -16,6 +17,7 @@
|
||||||
#include <components/sdlutil/events.hpp>
|
#include <components/sdlutil/events.hpp>
|
||||||
#include <components/settings/settings.hpp>
|
#include <components/settings/settings.hpp>
|
||||||
#include <components/to_utf8/to_utf8.hpp>
|
#include <components/to_utf8/to_utf8.hpp>
|
||||||
|
#include <components/misc/guarded.hpp>
|
||||||
|
|
||||||
#include "mapwindow.hpp"
|
#include "mapwindow.hpp"
|
||||||
#include "statswatcher.hpp"
|
#include "statswatcher.hpp"
|
||||||
|
|
@ -264,6 +266,7 @@ namespace MWGui
|
||||||
void exitCurrentGuiMode() override;
|
void exitCurrentGuiMode() override;
|
||||||
|
|
||||||
void messageBox (const std::string& message, enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) override;
|
void messageBox (const std::string& message, enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) override;
|
||||||
|
void scheduleMessageBox (std::string message, enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) override;
|
||||||
void staticMessageBox(const std::string& message) override;
|
void staticMessageBox(const std::string& message) override;
|
||||||
void removeStaticMessageBox() override;
|
void removeStaticMessageBox() override;
|
||||||
void interactiveMessageBox (const std::string& message,
|
void interactiveMessageBox (const std::string& message,
|
||||||
|
|
@ -524,6 +527,17 @@ namespace MWGui
|
||||||
|
|
||||||
float mScalingFactor;
|
float mScalingFactor;
|
||||||
|
|
||||||
|
struct ScheduledMessageBox
|
||||||
|
{
|
||||||
|
std::string mMessage;
|
||||||
|
MWGui::ShowInDialogueMode mShowInDialogueMode;
|
||||||
|
|
||||||
|
ScheduledMessageBox(std::string&& message, MWGui::ShowInDialogueMode showInDialogueMode)
|
||||||
|
: mMessage(std::move(message)), mShowInDialogueMode(showInDialogueMode) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
Misc::ScopeGuarded<std::vector<ScheduledMessageBox>> mScheduledMessageBoxes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when MyGUI tries to retrieve a tag's value. Tags must be denoted in #{tag} notation and will be replaced upon setting a user visible text/property.
|
* Called when MyGUI tries to retrieve a tag's value. Tags must be denoted in #{tag} notation and will be replaced upon setting a user visible text/property.
|
||||||
* Supported syntax:
|
* Supported syntax:
|
||||||
|
|
@ -555,6 +569,8 @@ namespace MWGui
|
||||||
void updatePinnedWindows();
|
void updatePinnedWindows();
|
||||||
|
|
||||||
void enableScene(bool enable);
|
void enableScene(bool enable);
|
||||||
|
|
||||||
|
void handleScheduledMessageBoxes();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ add_component_dir (sceneutil
|
||||||
clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller
|
clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller
|
||||||
lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer
|
lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer
|
||||||
actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller
|
actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller
|
||||||
|
screencapture
|
||||||
)
|
)
|
||||||
|
|
||||||
add_component_dir (nif
|
add_component_dir (nif
|
||||||
|
|
|
||||||
137
components/sceneutil/screencapture.cpp
Normal file
137
components/sceneutil/screencapture.cpp
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
#include "screencapture.hpp"
|
||||||
|
|
||||||
|
#include <components/debug/debuglog.hpp>
|
||||||
|
#include <components/sceneutil/workqueue.hpp>
|
||||||
|
|
||||||
|
#include <osg/ref_ptr>
|
||||||
|
#include <osg/Image>
|
||||||
|
#include <osgDB/ReaderWriter>
|
||||||
|
#include <osgDB/Registry>
|
||||||
|
|
||||||
|
#include <boost/filesystem/fstream.hpp>
|
||||||
|
#include <boost/filesystem/operations.hpp>
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
class ScreenCaptureWorkItem : public SceneUtil::WorkItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ScreenCaptureWorkItem(const osg::ref_ptr<osgViewer::ScreenCaptureHandler::CaptureOperation>& impl,
|
||||||
|
const osg::Image& image, unsigned int contextId)
|
||||||
|
: mImpl(impl),
|
||||||
|
mImage(new osg::Image(image)),
|
||||||
|
mContextId(contextId)
|
||||||
|
{
|
||||||
|
assert(mImpl != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void doWork() override
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
(*mImpl)(*mImage, mContextId);
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
Log(Debug::Error) << "ScreenCaptureWorkItem exception: " << e.what();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const osg::ref_ptr<osgViewer::ScreenCaptureHandler::CaptureOperation> mImpl;
|
||||||
|
const osg::ref_ptr<const osg::Image> mImage;
|
||||||
|
const unsigned int mContextId;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace SceneUtil
|
||||||
|
{
|
||||||
|
std::string writeScreenshotToFile(const std::string& screenshotPath, const std::string& screenshotFormat,
|
||||||
|
const osg::Image& image)
|
||||||
|
{
|
||||||
|
// Count screenshots.
|
||||||
|
int shotCount = 0;
|
||||||
|
|
||||||
|
// Find the first unused filename with a do-while
|
||||||
|
std::ostringstream stream;
|
||||||
|
std::string lastFileName;
|
||||||
|
std::string lastFilePath;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// Reset the stream
|
||||||
|
stream.str("");
|
||||||
|
stream.clear();
|
||||||
|
|
||||||
|
stream << "screenshot" << std::setw(3) << std::setfill('0') << shotCount++ << "." << screenshotFormat;
|
||||||
|
|
||||||
|
lastFileName = stream.str();
|
||||||
|
lastFilePath = screenshotPath + "/" + lastFileName;
|
||||||
|
|
||||||
|
} while (boost::filesystem::exists(lastFilePath));
|
||||||
|
|
||||||
|
boost::filesystem::ofstream outStream;
|
||||||
|
outStream.open(boost::filesystem::path(std::move(lastFilePath)), std::ios::binary);
|
||||||
|
|
||||||
|
osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension(screenshotFormat);
|
||||||
|
if (!readerwriter)
|
||||||
|
{
|
||||||
|
Log(Debug::Error) << "Error: Can't write screenshot, no '" << screenshotFormat << "' readerwriter found";
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
|
||||||
|
osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(image, outStream);
|
||||||
|
if (!result.success())
|
||||||
|
{
|
||||||
|
Log(Debug::Error) << "Error: Can't write screenshot: " << result.message() << " code " << result.status();
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteScreenshotToFileOperation::WriteScreenshotToFileOperation(const std::string& screenshotPath,
|
||||||
|
const std::string& screenshotFormat,
|
||||||
|
std::function<void (std::string)> callback)
|
||||||
|
: mScreenshotPath(screenshotPath)
|
||||||
|
, mScreenshotFormat(screenshotFormat)
|
||||||
|
, mCallback(callback)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteScreenshotToFileOperation::operator()(const osg::Image& image, const unsigned int /*context_id*/)
|
||||||
|
{
|
||||||
|
std::string fileName;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fileName = writeScreenshotToFile(mScreenshotPath, mScreenshotFormat, image);
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
Log(Debug::Error) << "Failed to write screenshot to file with path=\"" << mScreenshotPath
|
||||||
|
<< "\", format=\"" << mScreenshotFormat << "\": " << e.what();
|
||||||
|
}
|
||||||
|
if (fileName.empty())
|
||||||
|
mCallback("Failed to save screenshot");
|
||||||
|
else
|
||||||
|
mCallback(fileName + " has been saved");
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncScreenCaptureOperation::AsyncScreenCaptureOperation(osg::ref_ptr<WorkQueue> queue,
|
||||||
|
osg::ref_ptr<CaptureOperation> impl)
|
||||||
|
: mQueue(std::move(queue)),
|
||||||
|
mImpl(std::move(impl))
|
||||||
|
{
|
||||||
|
assert(mQueue != nullptr);
|
||||||
|
assert(mImpl != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncScreenCaptureOperation::operator()(const osg::Image& image, const unsigned int context_id)
|
||||||
|
{
|
||||||
|
mQueue->addWorkItem(new ScreenCaptureWorkItem(mImpl, image, context_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
49
components/sceneutil/screencapture.hpp
Normal file
49
components/sceneutil/screencapture.hpp
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
#ifndef OPENMW_COMPONENTS_SCENEUTIL_SCREENCAPTURE_H
|
||||||
|
#define OPENMW_COMPONENTS_SCENEUTIL_SCREENCAPTURE_H
|
||||||
|
|
||||||
|
#include <osg/ref_ptr>
|
||||||
|
#include <osgViewer/ViewerEventHandlers>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace osg
|
||||||
|
{
|
||||||
|
class Image;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace SceneUtil
|
||||||
|
{
|
||||||
|
class WorkQueue;
|
||||||
|
|
||||||
|
std::string writeScreenshotToFile(const std::string& screenshotPath, const std::string& screenshotFormat,
|
||||||
|
const osg::Image& image);
|
||||||
|
|
||||||
|
class WriteScreenshotToFileOperation : public osgViewer::ScreenCaptureHandler::CaptureOperation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WriteScreenshotToFileOperation(const std::string& screenshotPath, const std::string& screenshotFormat,
|
||||||
|
std::function<void (std::string)> callback);
|
||||||
|
|
||||||
|
void operator()(const osg::Image& image, const unsigned int context_id) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::string mScreenshotPath;
|
||||||
|
const std::string mScreenshotFormat;
|
||||||
|
const std::function<void (std::string)> mCallback;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AsyncScreenCaptureOperation : public osgViewer::ScreenCaptureHandler::CaptureOperation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AsyncScreenCaptureOperation(osg::ref_ptr<SceneUtil::WorkQueue> queue,
|
||||||
|
osg::ref_ptr<osgViewer::ScreenCaptureHandler::CaptureOperation> impl);
|
||||||
|
|
||||||
|
void operator()(const osg::Image& image, const unsigned int context_id) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const osg::ref_ptr<SceneUtil::WorkQueue> mQueue;
|
||||||
|
const osg::ref_ptr<osgViewer::ScreenCaptureHandler::CaptureOperation> mImpl;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -33,14 +33,25 @@ bool WorkItem::isDone() const
|
||||||
return mDone;
|
return mDone;
|
||||||
}
|
}
|
||||||
|
|
||||||
WorkQueue::WorkQueue(int workerThreads)
|
WorkQueue::WorkQueue(std::size_t workerThreads)
|
||||||
: mIsReleased(false)
|
: mIsReleased(false)
|
||||||
{
|
{
|
||||||
for (int i=0; i<workerThreads; ++i)
|
start(workerThreads);
|
||||||
mThreads.emplace_back(std::make_unique<WorkThread>(*this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WorkQueue::~WorkQueue()
|
WorkQueue::~WorkQueue()
|
||||||
|
{
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WorkQueue::start(std::size_t workerThreads)
|
||||||
|
{
|
||||||
|
while (mThreads.size() < workerThreads)
|
||||||
|
mThreads.emplace_back(std::make_unique<WorkThread>(*this));
|
||||||
|
mIsReleased = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WorkQueue::stop()
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(mMutex);
|
std::unique_lock<std::mutex> lock(mMutex);
|
||||||
|
|
|
||||||
|
|
@ -44,9 +44,13 @@ namespace SceneUtil
|
||||||
class WorkQueue : public osg::Referenced
|
class WorkQueue : public osg::Referenced
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
WorkQueue(int numWorkerThreads=1);
|
WorkQueue(std::size_t workerThreads);
|
||||||
~WorkQueue();
|
~WorkQueue();
|
||||||
|
|
||||||
|
void start(std::size_t workerThreads);
|
||||||
|
|
||||||
|
void stop();
|
||||||
|
|
||||||
/// Add a new work item to the back of the queue.
|
/// Add a new work item to the back of the queue.
|
||||||
/// @par The work item's waitTillDone() method may be used by the caller to wait until the work is complete.
|
/// @par The work item's waitTillDone() method may be used by the caller to wait until the work is complete.
|
||||||
/// @param front If true, add item to the front of the queue. If false (default), add to the back.
|
/// @param front If true, add item to the front of the queue. If false (default), add to the back.
|
||||||
|
|
|
||||||
|
|
@ -59,3 +59,12 @@ texture mipmap
|
||||||
Set the texture mipmap type to control the method mipmaps are created.
|
Set the texture mipmap type to control the method mipmaps are created.
|
||||||
Mipmapping is a way of reducing the processing power needed during minification
|
Mipmapping is a way of reducing the processing power needed during minification
|
||||||
by pregenerating a series of smaller textures.
|
by pregenerating a series of smaller textures.
|
||||||
|
|
||||||
|
notify on saved screenshot
|
||||||
|
--------------
|
||||||
|
|
||||||
|
:Type: boolean
|
||||||
|
:Range: True/False
|
||||||
|
:Default: False
|
||||||
|
|
||||||
|
Show message box when screenshot is saved to a file.
|
||||||
|
|
|
||||||
|
|
@ -386,6 +386,9 @@ texture min filter = linear
|
||||||
# Texture mipmap type. (none, nearest, or linear).
|
# Texture mipmap type. (none, nearest, or linear).
|
||||||
texture mipmap = nearest
|
texture mipmap = nearest
|
||||||
|
|
||||||
|
# Show message box when screenshot is saved to a file.
|
||||||
|
notify on saved screenshot = false
|
||||||
|
|
||||||
[Shaders]
|
[Shaders]
|
||||||
|
|
||||||
# Force rendering with shaders. By default, only bump-mapped objects will use shaders.
|
# Force rendering with shaders. By default, only bump-mapped objects will use shaders.
|
||||||
|
|
|
||||||
|
|
@ -1121,7 +1121,7 @@ True: In non-combat mode camera is positioned behind the character's shoulder. C
|
||||||
<layout class="QVBoxLayout" name="otherGroupVerticalLayout">
|
<layout class="QVBoxLayout" name="otherGroupVerticalLayout">
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="screenshotFormatLayout">
|
<layout class="QHBoxLayout" name="screenshotFormatLayout">
|
||||||
<item alignment="Qt::AlignRight">
|
<item>
|
||||||
<widget class="QLabel" name="screenshotFormatLabel">
|
<widget class="QLabel" name="screenshotFormatLabel">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Screenshot Format</string>
|
<string>Screenshot Format</string>
|
||||||
|
|
@ -1149,6 +1149,13 @@ True: In non-combat mode camera is positioned behind the character's shoulder. C
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="notifyOnSavedScreenshotCheckBox">
|
||||||
|
<property name="text">
|
||||||
|
<string>Notify on saved screenshot</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue