1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-03-29 22:20:33 +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:
psi29a 2021-07-08 22:13:57 +00:00
commit 7f3a4315aa
17 changed files with 314 additions and 65 deletions

View File

@ -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

View File

@ -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

View File

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

View File

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

View File

@ -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,

View File

@ -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")

View File

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

View File

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

View File

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

View File

@ -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

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

View 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

View File

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

View File

@ -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.

View File

@ -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.

View 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.

View File

@ -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>