diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c3b4e3354..a676b70b9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index bddb70aa02..00021268e3 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -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 diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index a06faa4d71..93d3530c0a 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -40,6 +40,8 @@ #include +#include + #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); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 1aef62df53..49ae92abc1 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -62,7 +62,7 @@ namespace OMW boost::filesystem::path mResDir; osg::ref_ptr mViewer; osg::ref_ptr mScreenCaptureHandler; - osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation; + osg::ref_ptr mScreenCaptureOperation; std::string mCellName; std::vector mContentFiles; std::vector mGroundcoverFiles; diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 6e3c615de3..f256cc387e 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -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, diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 5bd8ceb5ae..4907a247ff 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -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& buttons) : WindowModal(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "openmw_interactive_messagebox_notransp.layout" : "openmw_interactive_messagebox.layout") diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index aeb1b83002..26d26bac56 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -47,12 +47,15 @@ namespace MWGui void onButtonPressed(int button) { eventButtonPressed(button); eventButtonPressed.clear(); } + void setVisible(bool value); + private: std::vector 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; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 1694206504..544a0927e7 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -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(); + } } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index ef411a39df..8c7e365ec7 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -8,6 +8,7 @@ **/ #include +#include #include @@ -16,6 +17,7 @@ #include #include #include +#include #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> 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(); }; } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7860f492ce..43987d6c7b 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -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 diff --git a/components/sceneutil/screencapture.cpp b/components/sceneutil/screencapture.cpp new file mode 100644 index 0000000000..47119f3644 --- /dev/null +++ b/components/sceneutil/screencapture.cpp @@ -0,0 +1,137 @@ +#include "screencapture.hpp" + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +namespace +{ + class ScreenCaptureWorkItem : public SceneUtil::WorkItem + { + public: + ScreenCaptureWorkItem(const osg::ref_ptr& 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 mImpl; + const osg::ref_ptr 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 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 queue, + osg::ref_ptr 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)); + } +} diff --git a/components/sceneutil/screencapture.hpp b/components/sceneutil/screencapture.hpp new file mode 100644 index 0000000000..6395d989d8 --- /dev/null +++ b/components/sceneutil/screencapture.hpp @@ -0,0 +1,49 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_SCREENCAPTURE_H +#define OPENMW_COMPONENTS_SCENEUTIL_SCREENCAPTURE_H + +#include +#include + +#include + +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 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 mCallback; + }; + + class AsyncScreenCaptureOperation : public osgViewer::ScreenCaptureHandler::CaptureOperation + { + public: + AsyncScreenCaptureOperation(osg::ref_ptr queue, + osg::ref_ptr impl); + + void operator()(const osg::Image& image, const unsigned int context_id) override; + + private: + const osg::ref_ptr mQueue; + const osg::ref_ptr mImpl; + }; +} + +#endif diff --git a/components/sceneutil/workqueue.cpp b/components/sceneutil/workqueue.cpp index 3f8ed8aaf2..3c1df80ac4 100644 --- a/components/sceneutil/workqueue.cpp +++ b/components/sceneutil/workqueue.cpp @@ -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(*this)); + start(workerThreads); } WorkQueue::~WorkQueue() +{ + stop(); +} + +void WorkQueue::start(std::size_t workerThreads) +{ + while (mThreads.size() < workerThreads) + mThreads.emplace_back(std::make_unique(*this)); + mIsReleased = false; +} + +void WorkQueue::stop() { { std::unique_lock lock(mMutex); diff --git a/components/sceneutil/workqueue.hpp b/components/sceneutil/workqueue.hpp index 5b51c59e59..6eb6a36a3e 100644 --- a/components/sceneutil/workqueue.hpp +++ b/components/sceneutil/workqueue.hpp @@ -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. diff --git a/docs/source/reference/modding/settings/general.rst b/docs/source/reference/modding/settings/general.rst index c885f77aa1..f0ebe4f972 100644 --- a/docs/source/reference/modding/settings/general.rst +++ b/docs/source/reference/modding/settings/general.rst @@ -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. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 88a6b48126..522762555d 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -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. diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 8abe8f59a4..f72150df22 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -1121,7 +1121,7 @@ True: In non-combat mode camera is positioned behind the character's shoulder. C - + Screenshot Format @@ -1149,6 +1149,13 @@ True: In non-combat mode camera is positioned behind the character's shoulder. C + + + + Notify on saved screenshot + + +