# HG changeset patch # User Michael Lutz # Date 2021-05-01 17:39:03 # Node ID aeaaaf1c6dce61419e9293875aca4908f32955ca # Parent c9c5292315620e6a3193ca099561db9d7c950801 Codechange: Generalise the delayed blitter change to a generic video driver command queue. diff --git a/src/gfxinit.cpp b/src/gfxinit.cpp --- a/src/gfxinit.cpp +++ b/src/gfxinit.cpp @@ -244,6 +244,28 @@ static void LoadSpriteTables() } +static void RealChangeBlitter(const char *repl_blitter) +{ + const char *cur_blitter = BlitterFactory::GetCurrentBlitter()->GetName(); + if (strcmp(cur_blitter, repl_blitter) == 0) return; + + DEBUG(driver, 1, "Switching blitter from '%s' to '%s'... ", cur_blitter, repl_blitter); + Blitter *new_blitter = BlitterFactory::SelectBlitter(repl_blitter); + if (new_blitter == nullptr) NOT_REACHED(); + DEBUG(driver, 1, "Successfully switched to %s.", repl_blitter); + + if (!VideoDriver::GetInstance()->AfterBlitterChange()) { + /* Failed to switch blitter, let's hope we can return to the old one. */ + if (BlitterFactory::SelectBlitter(cur_blitter) == nullptr || !VideoDriver::GetInstance()->AfterBlitterChange()) usererror("Failed to reinitialize video driver. Specify a fixed blitter in the config"); + } + + /* Clear caches that might have sprites for another blitter. */ + VideoDriver::GetInstance()->ClearSystemSprites(); + ClearFontCache(); + GfxClearSpriteCache(); + ReInitAllWindows(false); +} + /** * Check blitter needed by NewGRF config and switch if needed. * @return False when nothing changed, true otherwise. @@ -309,7 +331,7 @@ static bool SwitchNewGRFBlitter() if (BlitterFactory::GetBlitterFactory(repl_blitter) == nullptr) continue; /* Inform the video driver we want to switch blitter as soon as possible. */ - VideoDriver::GetInstance()->ChangeBlitter(repl_blitter); + VideoDriver::GetInstance()->QueueOnMainThread(std::bind(&RealChangeBlitter, repl_blitter)); break; } diff --git a/src/video/video_driver.cpp b/src/video/video_driver.cpp --- a/src/video/video_driver.cpp +++ b/src/video/video_driver.cpp @@ -97,27 +97,6 @@ void VideoDriver::StopGameThread() this->game_thread.join(); } -void VideoDriver::RealChangeBlitter(const char *repl_blitter) -{ - const char *cur_blitter = BlitterFactory::GetCurrentBlitter()->GetName(); - - DEBUG(driver, 1, "Switching blitter from '%s' to '%s'... ", cur_blitter, repl_blitter); - Blitter *new_blitter = BlitterFactory::SelectBlitter(repl_blitter); - if (new_blitter == nullptr) NOT_REACHED(); - DEBUG(driver, 1, "Successfully switched to %s.", repl_blitter); - - if (!this->AfterBlitterChange()) { - /* Failed to switch blitter, let's hope we can return to the old one. */ - if (BlitterFactory::SelectBlitter(cur_blitter) == nullptr || !this->AfterBlitterChange()) usererror("Failed to reinitialize video driver. Specify a fixed blitter in the config"); - } - - /* Clear caches that might have sprites for another blitter. */ - this->ClearSystemSprites(); - ClearFontCache(); - GfxClearSpriteCache(); - ReInitAllWindows(false); -} - void VideoDriver::Tick() { if (!this->is_game_threaded && std::chrono::steady_clock::now() >= this->next_game_tick) { @@ -159,10 +138,7 @@ void VideoDriver::Tick() this->LockVideoBuffer(); - if (this->change_blitter != nullptr) { - this->RealChangeBlitter(this->change_blitter); - this->change_blitter = nullptr; - } + this->DrainCommandQueue(); while (this->PollEvent()) {} ::InputLoop(); diff --git a/src/video/video_driver.hpp b/src/video/video_driver.hpp --- a/src/video/video_driver.hpp +++ b/src/video/video_driver.hpp @@ -22,6 +22,7 @@ #include #include #include +#include extern std::string _ini_videodriver; extern std::vector _resolutions; @@ -36,7 +37,7 @@ class VideoDriver : public Driver { const uint DEFAULT_WINDOW_HEIGHT = 480u; ///< Default window height. public: - VideoDriver() : fast_forward_key_pressed(false), fast_forward_via_key(false), is_game_threaded(true), change_blitter(nullptr) {} + VideoDriver() : fast_forward_key_pressed(false), fast_forward_via_key(false), is_game_threaded(true) {} /** * Mark a particular area dirty. @@ -178,12 +179,16 @@ public: } /** - * Queue a request to change the blitter. This is not executed immediately, - * but instead on the next draw-tick. + * Queue a function to be called on the main thread with game state + * lock held and video buffer locked. Queued functions will be + * executed on the next draw tick. + * @param func Function to call. */ - void ChangeBlitter(const char *new_blitter) + void QueueOnMainThread(std::function &&func) { - this->change_blitter = new_blitter; + std::lock_guard lock(this->cmd_queue_mutex); + + this->cmd_queue.emplace_back(std::forward>(func)); } void GameLoopPause(); @@ -328,11 +333,29 @@ protected: static void GameThreadThunk(VideoDriver *drv); private: + std::mutex cmd_queue_mutex; + std::vector> cmd_queue; + + /** Execute all queued commands. */ + void DrainCommandQueue() + { + std::vector> cmds{}; + + { + /* Exchange queue with an empty one to limit the time we + * hold the mutex. This also ensures that queued functions can + * add new functions to the queue without everything blocking. */ + std::lock_guard lock(this->cmd_queue_mutex); + cmds.swap(this->cmd_queue); + } + + for (auto &f : cmds) { + f(); + } + } + void GameLoop(); void GameThread(); - void RealChangeBlitter(const char *repl_blitter); - - const char *change_blitter; ///< Request to change the blitter. nullptr if no pending request. }; #endif /* VIDEO_VIDEO_DRIVER_HPP */