diff options
author | Michael Lutz <michi@icosahedron.de> | 2021-01-16 16:43:11 +0100 |
---|---|---|
committer | Michael Lutz <michi@icosahedron.de> | 2021-02-22 22:16:07 +0100 |
commit | d6b67758881ba24d532d42e211a5fddcc1cdd309 (patch) | |
tree | 7fe12be86517d23cd4dbc6d4f9def52186e30d0c /src | |
parent | 73ed748deb65f14f280b8cefebb0a6beff16a4a5 (diff) | |
download | openttd-d6b67758881ba24d532d42e211a5fddcc1cdd309.tar.xz |
Change: Lock the video buffer when drawing inside the game loop to properly account for threaded drawing.
Diffstat (limited to 'src')
-rw-r--r-- | src/screenshot.cpp | 3 | ||||
-rw-r--r-- | src/video/video_driver.hpp | 19 | ||||
-rw-r--r-- | src/video/win32_v.cpp | 86 | ||||
-rw-r--r-- | src/video/win32_v.h | 17 |
4 files changed, 75 insertions, 50 deletions
diff --git a/src/screenshot.cpp b/src/screenshot.cpp index ef76b9f0e..8c64115a4 100644 --- a/src/screenshot.cpp +++ b/src/screenshot.cpp @@ -25,6 +25,7 @@ #include "window_func.h" #include "tile_map.h" #include "landscape.h" +#include "video/video_driver.hpp" #include "table/strings.h" @@ -881,6 +882,8 @@ void MakeScreenshotWithConfirm(ScreenshotType t) */ bool MakeScreenshot(ScreenshotType t, const char *name) { + VideoDriver::VideoBufferLocker lock; + if (t == SC_VIEWPORT) { /* First draw the dirty parts of the screen and only then change the name * of the screenshot. This way the screenshot will always show the name diff --git a/src/video/video_driver.hpp b/src/video/video_driver.hpp index c52b36029..cc74df202 100644 --- a/src/video/video_driver.hpp +++ b/src/video/video_driver.hpp @@ -127,6 +127,25 @@ public: return static_cast<VideoDriver*>(*DriverFactoryBase::GetActiveDriver(Driver::DT_VIDEO)); } + /** + * Helper struct to ensure the video buffer is locked and ready for drawing. The destructor + * will make sure the buffer is unlocked no matter how the scope is exited. + */ + struct VideoBufferLocker { + VideoBufferLocker() + { + this->unlock = VideoDriver::GetInstance()->LockVideoBuffer(); + } + + ~VideoBufferLocker() + { + if (this->unlock) VideoDriver::GetInstance()->UnlockVideoBuffer(); + } + + private: + bool unlock; ///< Stores if the lock did anything that has to be undone. + }; + protected: const uint ALLOWED_DRIFT = 5; ///< How many times videodriver can miss deadlines without it being overly compensated. diff --git a/src/video/win32_v.cpp b/src/video/win32_v.cpp index f66f2d9f8..0315dfd04 100644 --- a/src/video/win32_v.cpp +++ b/src/video/win32_v.cpp @@ -26,8 +26,6 @@ #include "win32_v.h" #include <windows.h> #include <imm.h> -#include <mutex> -#include <condition_variable> #include "../safeguards.h" @@ -53,14 +51,6 @@ bool _window_maximize; static Dimension _bck_resolution; DWORD _imm_props; -/** Whether the drawing is/may be done in a separate thread. */ -static bool _draw_threaded; -/** Mutex to keep the access to the shared memory controlled. */ -static std::recursive_mutex *_draw_mutex = nullptr; -/** Signal to draw the next frame. */ -static std::condition_variable_any *_draw_signal = nullptr; -/** Should we keep continue drawing? */ -static volatile bool _draw_continue; /** Local copy of the palette for use in the drawing thread. */ static Palette _local_palette; @@ -934,34 +924,34 @@ void VideoDriver_Win32Base::MainLoop() std::thread draw_thread; - if (_draw_threaded) { + if (this->draw_threaded) { /* Initialise the mutex first, because that's the thing we *need* * directly in the newly created thread. */ try { - _draw_signal = new std::condition_variable_any(); - _draw_mutex = new std::recursive_mutex(); + this->draw_signal = new std::condition_variable_any(); + this->draw_mutex = new std::recursive_mutex(); } catch (...) { - _draw_threaded = false; + this->draw_threaded = false; } - if (_draw_threaded) { - this->draw_lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex); + if (this->draw_threaded) { + this->draw_lock = std::unique_lock<std::recursive_mutex>(*this->draw_mutex); - _draw_continue = true; - _draw_threaded = StartNewThread(&draw_thread, "ottd:draw-win32", &VideoDriver_Win32Base::PaintThreadThunk, this); + this->draw_continue = true; + this->draw_threaded = StartNewThread(&draw_thread, "ottd:draw-win32", &VideoDriver_Win32Base::PaintThreadThunk, this); /* Free the mutex if we won't be able to use it. */ - if (!_draw_threaded) { + if (!this->draw_threaded) { this->draw_lock.unlock(); this->draw_lock.release(); - delete _draw_mutex; - delete _draw_signal; - _draw_mutex = nullptr; - _draw_signal = nullptr; + delete this->draw_mutex; + delete this->draw_signal; + this->draw_mutex = nullptr; + this->draw_signal = nullptr; } else { DEBUG(driver, 1, "Threaded drawing enabled"); /* Wait till the draw thread has started itself. */ - _draw_signal->wait(*_draw_mutex); + this->draw_signal->wait(*this->draw_mutex); } } } @@ -982,8 +972,8 @@ void VideoDriver_Win32Base::MainLoop() GdiFlush(); if (this->Tick()) { - if (_draw_mutex != nullptr && !HasModalProgress()) { - _draw_signal->notify_one(); + if (this->draw_mutex != nullptr && !HasModalProgress()) { + this->draw_signal->notify_one(); } else { this->Paint(); } @@ -991,19 +981,19 @@ void VideoDriver_Win32Base::MainLoop() this->SleepTillNextTick(); } - if (_draw_threaded) { - _draw_continue = false; + if (this->draw_threaded) { + this->draw_continue = false; /* Sending signal if there is no thread blocked * is very valid and results in noop */ - _draw_signal->notify_all(); + this->draw_signal->notify_all(); if (this->draw_lock.owns_lock()) this->draw_lock.unlock(); this->draw_lock.release(); draw_thread.join(); - delete _draw_mutex; - delete _draw_signal; + delete this->draw_mutex; + delete this->draw_signal; - _draw_mutex = nullptr; + this->draw_mutex = nullptr; } } @@ -1025,7 +1015,7 @@ void VideoDriver_Win32Base::ClientSizeChanged(int w, int h) bool VideoDriver_Win32Base::ChangeResolution(int w, int h) { std::unique_lock<std::recursive_mutex> lock; - if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex); + if (this->draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*this->draw_mutex); if (_window_maximize) ShowWindow(this->main_wnd, SW_SHOWNORMAL); @@ -1038,25 +1028,25 @@ bool VideoDriver_Win32Base::ChangeResolution(int w, int h) bool VideoDriver_Win32Base::ToggleFullscreen(bool full_screen) { std::unique_lock<std::recursive_mutex> lock; - if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex); + if (this->draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*this->draw_mutex); return this->MakeWindow(full_screen); } void VideoDriver_Win32Base::AcquireBlitterLock() { - if (_draw_mutex != nullptr) _draw_mutex->lock(); + if (this->draw_mutex != nullptr) this->draw_mutex->lock(); } void VideoDriver_Win32Base::ReleaseBlitterLock() { - if (_draw_mutex != nullptr) _draw_mutex->unlock(); + if (this->draw_mutex != nullptr) this->draw_mutex->unlock(); } void VideoDriver_Win32Base::EditBoxLostFocus() { std::unique_lock<std::recursive_mutex> lock; - if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex); + if (this->draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*this->draw_mutex); CancelIMEComposition(this->main_wnd); SetCompositionPos(this->main_wnd); @@ -1110,7 +1100,10 @@ float VideoDriver_Win32Base::GetDPIScale() bool VideoDriver_Win32Base::LockVideoBuffer() { - if (_draw_threaded) this->draw_lock.lock(); + if (this->buffer_locked) return false; + this->buffer_locked = true; + + if (this->draw_threaded) this->draw_lock.lock(); _screen.dst_ptr = this->GetVideoPointer(); @@ -1119,7 +1112,8 @@ bool VideoDriver_Win32Base::LockVideoBuffer() void VideoDriver_Win32Base::UnlockVideoBuffer() { - if (_draw_threaded) this->draw_lock.unlock(); + if (this->draw_threaded) this->draw_lock.unlock(); + this->buffer_locked = false; } @@ -1137,7 +1131,7 @@ const char *VideoDriver_Win32GDI::Start(const StringList ¶m) MarkWholeScreenDirty(); - _draw_threaded = !GetDriverParam(param, "no_threads") && !GetDriverParam(param, "no_thread") && std::thread::hardware_concurrency() > 1; + this->draw_threaded = !GetDriverParam(param, "no_threads") && !GetDriverParam(param, "no_thread") && std::thread::hardware_concurrency() > 1; return nullptr; } @@ -1284,19 +1278,19 @@ void VideoDriver_Win32GDI::Paint() void VideoDriver_Win32GDI::PaintThread() { /* First tell the main thread we're started */ - std::unique_lock<std::recursive_mutex> lock(*_draw_mutex); - _draw_signal->notify_one(); + std::unique_lock<std::recursive_mutex> lock(*this->draw_mutex); + this->draw_signal->notify_one(); /* Now wait for the first thing to draw! */ - _draw_signal->wait(*_draw_mutex); + this->draw_signal->wait(*this->draw_mutex); - while (_draw_continue) { + while (this->draw_continue) { this->Paint(); /* Flush GDI buffer to ensure drawing here doesn't conflict with any GDI usage in the main WndProc. */ GdiFlush(); - _draw_signal->wait(*_draw_mutex); + this->draw_signal->wait(*this->draw_mutex); } } @@ -1357,7 +1351,7 @@ const char *VideoDriver_Win32OpenGL::Start(const StringList ¶m) this->ClientSizeChanged(_wnd.width, _wnd.height); - _draw_threaded = false; + this->draw_threaded = false; MarkWholeScreenDirty(); return nullptr; diff --git a/src/video/win32_v.h b/src/video/win32_v.h index 6b62ea0ee..72736ec84 100644 --- a/src/video/win32_v.h +++ b/src/video/win32_v.h @@ -11,11 +11,13 @@ #define VIDEO_WIN32_H #include "video_driver.hpp" +#include <mutex> +#include <condition_variable> /** Base class for Windows video drivers. */ class VideoDriver_Win32Base : public VideoDriver { public: - VideoDriver_Win32Base() : main_wnd(nullptr), fullscreen(false) {} + VideoDriver_Win32Base() : main_wnd(nullptr), fullscreen(false), draw_mutex(nullptr), draw_signal(nullptr) {} void Stop() override; @@ -36,9 +38,16 @@ public: void EditBoxLostFocus() override; protected: - HWND main_wnd; ///< Handle to system window. - bool fullscreen; ///< Whether to use (true) fullscreen mode. - Rect dirty_rect; ///< Region of the screen that needs redrawing. + HWND main_wnd; ///< Handle to system window. + bool fullscreen; ///< Whether to use (true) fullscreen mode. + Rect dirty_rect; ///< Region of the screen that needs redrawing. + + bool draw_threaded; ///< Whether the drawing is/may be done in a separate thread. + bool buffer_locked; ///< Video buffer was locked by the main thread. + volatile bool draw_continue; ///< Should we keep continue drawing? + + std::recursive_mutex *draw_mutex; ///< Mutex to keep the access to the shared memory controlled. + std::condition_variable_any *draw_signal; ///< Signal to draw the next frame. Dimension GetScreenSize() const override; float GetDPIScale() override; |