Merge branch 'async_screenshot' into 'master'

Write screenshots to file asynchronously (#6143)

Closes #6143

See merge request OpenMW/openmw!907
dont-compose-content
psi29a 4 years ago
commit 7f3a4315aa

@ -17,6 +17,7 @@
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 #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 #5489: MCP: Telekinesis fix for activators
Feature #6017: Separate persistent and temporary cell references when saving

@ -213,6 +213,8 @@ bool Launcher::AdvancedPage::loadSettings()
if (screenshotFormatComboBox->findText(screenshotFormatString) == -1)
screenshotFormatComboBox->addItem(screenshotFormatString);
screenshotFormatComboBox->setCurrentIndex(screenshotFormatComboBox->findText(screenshotFormatString));
loadSettingBool(notifyOnSavedScreenshotCheckBox, "notify on saved screenshot", "General");
}
// Testing
@ -376,6 +378,8 @@ void Launcher::AdvancedPage::saveSettings()
std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString();
if (screenshotFormatString != Settings::Manager::getString("screenshot format", "General"))
Settings::Manager::setString("screenshot format", "General", screenshotFormatString);
saveSettingBool(notifyOnSavedScreenshotCheckBox, "notify on saved screenshot", "General");
}
// Testing

@ -40,6 +40,8 @@
#include <components/misc/frameratelimiter.hpp>
#include <components/sceneutil/screencapture.hpp>
#include "mwinput/inputmanagerimp.hpp"
#include "mwgui/windowmanagerimp.hpp"
@ -216,6 +218,19 @@ namespace
if (Settings::Manager::getInt("async num threads", "Physics") == 0)
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()
@ -405,6 +420,8 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
OMW::Engine::~Engine()
{
mWorkQueue->stop();
mEnvironment.cleanup();
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");
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
// 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.
void OMW::Engine::go()
{
@ -860,14 +844,6 @@ void OMW::Engine::go()
mViewer->setUseConfigureAffinity(false);
#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"));
prepareEngine (settings);

@ -62,7 +62,7 @@ namespace OMW
boost::filesystem::path mResDir;
osg::ref_ptr<osgViewer::Viewer> mViewer;
osg::ref_ptr<osgViewer::ScreenCaptureHandler> mScreenCaptureHandler;
osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation;
osg::ref_ptr<osgViewer::ScreenCaptureHandler::CaptureOperation> mScreenCaptureOperation;
std::string mCellName;
std::vector<std::string> mContentFiles;
std::vector<std::string> mGroundcoverFiles;

@ -228,6 +228,8 @@ namespace MWBase
virtual void exitCurrentGuiMode() = 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 removeStaticMessageBox() = 0;
virtual void interactiveMessageBox (const std::string& message,

@ -101,6 +101,8 @@ namespace MWGui
if(stat)
mStaticMessageBox = box;
box->setVisible(mVisible);
mMessageBoxes.push_back(box);
if(mMessageBoxes.size() > 3) {
@ -167,8 +169,12 @@ namespace MWGui
return pressed;
}
void MessageBoxManager::setVisible(bool value)
{
mVisible = value;
for (MessageBox* messageBox : mMessageBoxes)
messageBox->setVisible(value);
}
MessageBox::MessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message)
: Layout("openmw_messagebox.layout")
@ -201,7 +207,10 @@ namespace MWGui
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)
: 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 setVisible(bool value);
private:
std::vector<MessageBox*> mMessageBoxes;
InteractiveMessageBox* mInterMessageBoxe;
MessageBox* mStaticMessageBox;
float mMessageBoxSpeed;
int mLastButtonPressed;
bool mVisible = true;
};
class MessageBox : public Layout
@ -62,6 +65,7 @@ namespace MWGui
void setMessage (const std::string& message);
int getHeight ();
void update (int height);
void setVisible(bool value);
float mCurrentTime;
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)
{
mMessageBoxManager->createMessageBox(message, true);
@ -803,6 +808,8 @@ namespace MWGui
void WindowManager::update (float frameDuration)
{
handleScheduledMessageBoxes();
bool gameRunning = MWBase::Environment::get().getStateManager()->getState()!=
MWBase::StateManager::State_NoGame;
@ -1492,6 +1499,7 @@ namespace MWGui
{
mHudEnabled = !mHudEnabled;
updateVisible();
mMessageBoxManager->setVisible(mHudEnabled);
return mHudEnabled;
}
@ -2204,4 +2212,12 @@ namespace MWGui
{
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 <vector>
#include <osg/ref_ptr>
@ -16,6 +17,7 @@
#include <components/sdlutil/events.hpp>
#include <components/settings/settings.hpp>
#include <components/to_utf8/to_utf8.hpp>
#include <components/misc/guarded.hpp>
#include "mapwindow.hpp"
#include "statswatcher.hpp"
@ -264,6 +266,7 @@ namespace MWGui
void exitCurrentGuiMode() 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 removeStaticMessageBox() override;
void interactiveMessageBox (const std::string& message,
@ -524,6 +527,17 @@ namespace MWGui
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.
* Supported syntax:
@ -555,6 +569,8 @@ namespace MWGui
void updatePinnedWindows();
void enableScene(bool enable);
void handleScheduledMessageBoxes();
};
}

@ -53,6 +53,7 @@ add_component_dir (sceneutil
clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller
lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer
actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller
screencapture
)
add_component_dir (nif

@ -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));
}
}

@ -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;
}
WorkQueue::WorkQueue(int workerThreads)
WorkQueue::WorkQueue(std::size_t workerThreads)
: mIsReleased(false)
{
for (int i=0; i<workerThreads; ++i)
mThreads.emplace_back(std::make_unique<WorkThread>(*this));
start(workerThreads);
}
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);

@ -44,9 +44,13 @@ namespace SceneUtil
class WorkQueue : public osg::Referenced
{
public:
WorkQueue(int numWorkerThreads=1);
WorkQueue(std::size_t workerThreads);
~WorkQueue();
void start(std::size_t workerThreads);
void stop();
/// 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.
/// @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.
Mipmapping is a way of reducing the processing power needed during minification
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 = nearest
# Show message box when screenshot is saved to a file.
notify on saved screenshot = false
[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">
<item>
<layout class="QHBoxLayout" name="screenshotFormatLayout">
<item alignment="Qt::AlignRight">
<item>
<widget class="QLabel" name="screenshotFormatLabel">
<property name="text">
<string>Screenshot Format</string>
@ -1149,6 +1149,13 @@ True: In non-combat mode camera is positioned behind the character's shoulder. C
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="notifyOnSavedScreenshotCheckBox">
<property name="text">
<string>Notify on saved screenshot</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

Loading…
Cancel
Save