Merge branch 'master' into jgrpp

# Conflicts:
#	src/dock_gui.cpp
#	src/rail_gui.cpp
#	src/road_gui.cpp
#	src/terraform_gui.cpp
#	src/vehicle.cpp
#	src/video/allegro_v.cpp
#	src/video/cocoa/cocoa_v.mm
#	src/video/dedicated_v.cpp
#	src/video/sdl2_v.cpp
#	src/video/sdl_v.cpp
#	src/video/win32_v.cpp
This commit is contained in:
Jonathan G Rennison
2021-02-21 11:49:54 +00:00
42 changed files with 885 additions and 1005 deletions

View File

@@ -31,5 +31,6 @@ add_files(
dedicated_v.h
null_v.cpp
null_v.h
video_driver.cpp
video_driver.hpp
)

View File

@@ -56,7 +56,7 @@ void VideoDriver_Allegro::MakeDirty(int left, int top, int width, int height)
_num_dirty_rects++;
}
static void DrawSurfaceToScreen()
void VideoDriver_Allegro::Paint()
{
PerformanceMeasurer framerate(PFE_VIDEO);
@@ -95,7 +95,7 @@ static void InitPalette()
UpdatePalette(0, 256);
}
static void CheckPaletteAnim()
void VideoDriver_Allegro::CheckPaletteAnim()
{
if (_cur_palette.count_dirty != 0) {
Blitter *blitter = BlitterFactory::GetCurrentBlitter();
@@ -447,95 +447,49 @@ void VideoDriver_Allegro::Stop()
if (--_allegro_instance_count == 0) allegro_exit();
}
void VideoDriver_Allegro::InputLoop()
{
bool old_ctrl_pressed = _ctrl_pressed;
_ctrl_pressed = !!(key_shifts & KB_CTRL_FLAG) != _invert_ctrl;
_shift_pressed = !!(key_shifts & KB_SHIFT_FLAG) != _invert_shift;
#if defined(_DEBUG)
if (_shift_pressed)
#else
/* Speedup when pressing tab, except when using ALT+TAB
* to switch to another application. */
if (key[KEY_TAB] && (key_shifts & KB_ALT_FLAG) == 0)
#endif
{
if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2;
} else if (_fast_forward & 2) {
_fast_forward = 0;
}
/* Determine which directional keys are down. */
_dirkeys =
(key[KEY_LEFT] ? 1 : 0) |
(key[KEY_UP] ? 2 : 0) |
(key[KEY_RIGHT] ? 4 : 0) |
(key[KEY_DOWN] ? 8 : 0);
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
if (old_shift_pressed != _shift_pressed) HandleShiftChanged();
}
void VideoDriver_Allegro::MainLoop()
{
auto cur_ticks = std::chrono::steady_clock::now();
auto last_realtime_tick = cur_ticks;
auto next_game_tick = cur_ticks;
auto next_draw_tick = cur_ticks;
CheckPaletteAnim();
for (;;) {
InteractiveRandom(); // randomness
PollEvent();
if (_exit_game) return;
#if defined(_DEBUG)
if (_shift_pressed)
#else
/* Speedup when pressing tab, except when using ALT+TAB
* to switch to another application */
if (key[KEY_TAB] && (key_shifts & KB_ALT_FLAG) == 0)
#endif
{
if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2;
} else if (_fast_forward & 2) {
_fast_forward = 0;
}
cur_ticks = std::chrono::steady_clock::now();
/* If more than a millisecond has passed, increase the _realtime_tick. */
if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) {
auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(cur_ticks - last_realtime_tick);
IncreaseRealtimeTick(delta.count());
last_realtime_tick += delta;
}
if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) {
if (_fast_forward && !_pause_mode) {
next_game_tick = cur_ticks + this->GetGameInterval();
} else {
next_game_tick += this->GetGameInterval();
/* Avoid next_game_tick getting behind more and more if it cannot keep up. */
if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks;
}
GameLoop();
GameLoopPaletteAnimations();
}
/* Prevent drawing when switching mode, as windows can be removed when they should still appear. */
if (cur_ticks >= next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) {
next_draw_tick += this->GetDrawInterval();
/* Avoid next_draw_tick getting behind more and more if it cannot keep up. */
if (next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) next_draw_tick = cur_ticks;
bool old_ctrl_pressed = _ctrl_pressed;
bool old_shift_pressed = _shift_pressed;
_ctrl_pressed = !!(key_shifts & KB_CTRL_FLAG) != _invert_ctrl;
_shift_pressed = !!(key_shifts & KB_SHIFT_FLAG) != _invert_shift;
/* determine which directional keys are down */
_dirkeys =
(key[KEY_LEFT] ? 1 : 0) |
(key[KEY_UP] ? 2 : 0) |
(key[KEY_RIGHT] ? 4 : 0) |
(key[KEY_DOWN] ? 8 : 0);
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
if (old_shift_pressed != _shift_pressed) HandleShiftChanged();
InputLoop();
UpdateWindows();
CheckPaletteAnim();
DrawSurfaceToScreen();
}
/* If we are not in fast-forward, create some time between calls to ease up CPU usage. */
if (!_fast_forward || _pause_mode) {
/* See how much time there is till we have to process the next event, and try to hit that as close as possible. */
auto next_tick = std::min(next_draw_tick, next_game_tick);
auto now = std::chrono::steady_clock::now();
if (next_tick > now) {
std::this_thread::sleep_for(next_tick - now);
}
if (this->Tick()) {
this->Paint();
}
this->SleepTillNextTick();
}
}

View File

@@ -32,6 +32,11 @@ public:
bool ClaimMousePointer() override;
const char *GetName() const override { return "allegro"; }
protected:
void InputLoop() override;
void Paint() override;
void CheckPaletteAnim() override;
};
/** Factory for the allegro video driver. */

View File

@@ -32,10 +32,8 @@ private:
void *pixel_buffer; ///< used for direct pixel access
void *window_buffer; ///< Colour translation from palette to screen
static const int MAX_DIRTY_RECTS = 100;
Rect dirty_rect; ///< Region of the screen that needs redrawing.
Rect dirty_rects[MAX_DIRTY_RECTS]; ///< dirty rectangles
uint num_dirty_rects; ///< Number of dirty rectangles
uint32 palette[256]; ///< Colour Palette
public:
@@ -74,6 +72,9 @@ public:
protected:
Dimension GetScreenSize() const override;
float GetDPIScale() override;
void InputLoop() override;
void Paint() override;
void CheckPaletteAnim() override;
private:
bool PollEvent();
@@ -86,9 +87,7 @@ private:
bool MakeWindow(int width, int height);
void UpdatePalette(uint first_color, uint num_colors);
void CheckPaletteAnim();
void Draw(bool force_update = false);
void BlitIndexedToView32(int left, int top, int right, int bottom);
};

View File

@@ -21,12 +21,13 @@
#define Rect OTTDRect
#define Point OTTDPoint
#import <Cocoa/Cocoa.h>
#import <QuartzCore/QuartzCore.h>
#undef Rect
#undef Point
#include "../../openttd.h"
#include "../../debug.h"
#include "../../core/geometry_type.hpp"
#include "../../core/geometry_func.hpp"
#include "../../core/math_func.hpp"
#include "cocoa_v.h"
#include "cocoa_wnd.h"
@@ -125,7 +126,7 @@ VideoDriver_Cocoa::VideoDriver_Cocoa()
this->color_space = nullptr;
this->cgcontext = nullptr;
this->num_dirty_rects = lengthof(this->dirty_rects);
this->dirty_rect = {};
}
/** Stop Cocoa video driver. */
@@ -192,13 +193,8 @@ const char *VideoDriver_Cocoa::Start(const StringList &parm)
*/
void VideoDriver_Cocoa::MakeDirty(int left, int top, int width, int height)
{
if (this->num_dirty_rects < lengthof(this->dirty_rects)) {
dirty_rects[this->num_dirty_rects].left = left;
dirty_rects[this->num_dirty_rects].top = top;
dirty_rects[this->num_dirty_rects].right = left + width;
dirty_rects[this->num_dirty_rects].bottom = top + height;
}
this->num_dirty_rects++;
Rect r = {left, top, left + width, top + height};
this->dirty_rect = BoundingRect(this->dirty_rect, r);
}
/**
@@ -465,49 +461,39 @@ void VideoDriver_Cocoa::BlitIndexedToView32(int left, int top, int right, int bo
}
/**
* Draw window.
* Paint window.
* @param force_update Whether to redraw unconditionally
*/
void VideoDriver_Cocoa::Draw(bool force_update)
void VideoDriver_Cocoa::Paint()
{
PerformanceMeasurer framerate(PFE_VIDEO);
/* Check if we need to do anything */
if (this->num_dirty_rects == 0 || [ this->window isMiniaturized ]) return;
if (IsEmptyRect(this->dirty_rect) || [ this->window isMiniaturized ]) return;
if (this->num_dirty_rects >= lengthof(this->dirty_rects)) {
this->num_dirty_rects = 1;
this->dirty_rects[0].left = 0;
this->dirty_rects[0].top = 0;
this->dirty_rects[0].right = this->window_width;
this->dirty_rects[0].bottom = this->window_height;
/* We only need to blit in indexed mode since in 32bpp mode the game draws directly to the image. */
if (this->buffer_depth == 8) {
BlitIndexedToView32(
this->dirty_rect.left,
this->dirty_rect.top,
this->dirty_rect.right,
this->dirty_rect.bottom
);
}
/* Build the region of dirty rectangles */
for (uint i = 0; i < this->num_dirty_rects; i++) {
/* We only need to blit in indexed mode since in 32bpp mode the game draws directly to the image. */
if (this->buffer_depth == 8) {
BlitIndexedToView32(
this->dirty_rects[i].left,
this->dirty_rects[i].top,
this->dirty_rects[i].right,
this->dirty_rects[i].bottom
);
}
NSRect dirtyrect;
dirtyrect.origin.x = this->dirty_rect.left;
dirtyrect.origin.y = this->window_height - this->dirty_rect.bottom;
dirtyrect.size.width = this->dirty_rect.right - this->dirty_rect.left;
dirtyrect.size.height = this->dirty_rect.bottom - this->dirty_rect.top;
NSRect dirtyrect;
dirtyrect.origin.x = this->dirty_rects[i].left;
dirtyrect.origin.y = this->window_height - this->dirty_rects[i].bottom;
dirtyrect.size.width = this->dirty_rects[i].right - this->dirty_rects[i].left;
dirtyrect.size.height = this->dirty_rects[i].bottom - this->dirty_rects[i].top;
/* Notify OS X that we have new content to show. */
[ this->cocoaview setNeedsDisplayInRect:[ this->cocoaview getVirtualRect:dirtyrect ] ];
/* Normally drawRect will be automatically called by Mac OS X during next update cycle,
* and then blitting will occur. If force_update is true, it will be done right now. */
[ this->cocoaview setNeedsDisplayInRect:[ this->cocoaview getVirtualRect:dirtyrect ] ];
if (force_update) [ this->cocoaview displayIfNeeded ];
}
/* Tell the OS to get our contents to screen as soon as possible. */
[ CATransaction flush ];
this->num_dirty_rects = 0;
this->dirty_rect = {};
}
/** Update the palette. */
@@ -523,7 +509,7 @@ void VideoDriver_Cocoa::UpdatePalette(uint first_color, uint num_colors)
this->palette[i] = clr;
}
this->num_dirty_rects = lengthof(this->dirty_rects);
this->MakeDirty(0, 0, _screen.width, _screen.height);
}
/** Clear buffer to opaque black. */
@@ -581,8 +567,8 @@ void VideoDriver_Cocoa::AllocateBackingStore()
}
/* Redraw screen */
this->num_dirty_rects = lengthof(this->dirty_rects);
this->GameSizeChanged();
this->MakeDirty(0, 0, _screen.width, _screen.height);
}
/** Check if palette updates need to be performed. */
@@ -632,14 +618,33 @@ bool VideoDriver_Cocoa::PollEvent()
return true;
}
void VideoDriver_Cocoa::InputLoop()
{
NSUInteger cur_mods = [ NSEvent modifierFlags ];
bool old_ctrl_pressed = _ctrl_pressed;
bool old_shift_pressed = _shift_pressed;
_ctrl_pressed = ((cur_mods & ( _settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? NSControlKeyMask : NSCommandKeyMask)) != 0) != _invert_ctrl;
_shift_pressed = ((cur_mods & NSShiftKeyMask) != 0) != _invert_shift;
#if defined(_DEBUG)
if (_shift_pressed) {
#else
if (_tab_is_down) {
#endif
if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2;
} else if (_fast_forward & 2) {
_fast_forward = 0;
}
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
if (old_shift_pressed != _shift_pressed) HandleShiftChanged();
}
/** Main game loop. */
void VideoDriver_Cocoa::GameLoop()
{
auto cur_ticks = std::chrono::steady_clock::now();
auto last_realtime_tick = cur_ticks;
auto next_game_tick = cur_ticks;
auto next_draw_tick = cur_ticks;
for (;;) {
@autoreleasepool {
@@ -653,69 +658,10 @@ void VideoDriver_Cocoa::GameLoop()
break;
}
NSUInteger cur_mods = [ NSEvent modifierFlags ];
#if defined(_DEBUG)
if (cur_mods & NSShiftKeyMask) {
#else
if (_tab_is_down) {
#endif
if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2;
} else if (_fast_forward & 2) {
_fast_forward = 0;
}
cur_ticks = std::chrono::steady_clock::now();
/* If more than a millisecond has passed, increase the _realtime_tick. */
if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) {
auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(cur_ticks - last_realtime_tick);
IncreaseRealtimeTick(delta.count());
last_realtime_tick += delta;
}
if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) {
if (_fast_forward && !_pause_mode) {
next_game_tick = cur_ticks + this->GetGameInterval();
} else {
next_game_tick += this->GetGameInterval();
/* Avoid next_game_tick getting behind more and more if it cannot keep up. */
if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks;
}
::GameLoop();
}
/* Prevent drawing when switching mode, as windows can be removed when they should still appear. */
if (cur_ticks >= next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) {
next_draw_tick += this->GetDrawInterval();
/* Avoid next_draw_tick getting behind more and more if it cannot keep up. */
if (next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) next_draw_tick = cur_ticks;
bool old_ctrl_pressed = _ctrl_pressed;
_ctrl_pressed = (cur_mods & ( _settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? NSControlKeyMask : NSCommandKeyMask)) != 0;
_shift_pressed = (cur_mods & NSShiftKeyMask) != 0;
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
InputLoop();
UpdateWindows();
this->CheckPaletteAnim();
this->Draw();
}
/* If we are not in fast-forward, create some time between calls to ease up CPU usage. */
if (!_fast_forward || _pause_mode) {
/* See how much time there is till we have to process the next event, and try to hit that as close as possible. */
auto next_tick = std::min(next_draw_tick, next_game_tick);
auto now = std::chrono::steady_clock::now();
if (next_tick > now) {
std::this_thread::sleep_for(next_tick - now);
}
if (this->Tick()) {
this->Paint();
}
this->SleepTillNextTick();
}
}
}
@@ -733,6 +679,7 @@ void VideoDriver_Cocoa::GameLoop()
self.wantsLayer = YES;
self.layer.magnificationFilter = kCAFilterNearest;
self.layer.opaque = YES;
}
return self;
}

View File

@@ -236,10 +236,6 @@ static void DedicatedHandleKeyInput()
void VideoDriver_Dedicated::MainLoop()
{
auto cur_ticks = std::chrono::steady_clock::now();
auto last_realtime_tick = cur_ticks;
auto next_game_tick = cur_ticks;
/* Signal handlers */
#if defined(UNIX)
signal(SIGTERM, DedicatedSignalHandler);
@@ -283,44 +279,8 @@ void VideoDriver_Dedicated::MainLoop()
if (!_dedicated_forks) DedicatedHandleKeyInput();
cur_ticks = std::chrono::steady_clock::now();
/* If more than a millisecond has passed, increase the _realtime_tick. */
if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) {
auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(cur_ticks - last_realtime_tick);
IncreaseRealtimeTick(delta.count());
last_realtime_tick += delta;
}
if (cur_ticks >= next_game_tick || _ddc_fastforward) {
if (_ddc_fastforward) {
next_game_tick = cur_ticks + this->GetGameInterval();
} else {
next_game_tick += this->GetGameInterval();
/* Avoid next_game_tick getting behind more and more if it cannot keep up. */
if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks;
}
GameLoop();
GameLoopPaletteAnimations();
InputLoop();
UpdateWindows();
}
/* Don't sleep when fast forwarding (for desync debugging) */
if (!_ddc_fastforward) {
/* Sleep longer on a dedicated server, if the game is paused and no clients connected.
* That can allow the CPU to better use deep sleep states. */
if (_pause_mode != 0 && !HasClients()) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
} else {
/* See how much time there is till we have to process the next event, and try to hit that as close as possible. */
auto now = std::chrono::steady_clock::now();
if (next_game_tick > now) {
std::this_thread::sleep_for(next_game_tick - now);
}
}
}
_fast_forward = _ddc_fastforward;
this->Tick();
this->SleepTillNextTick();
}
}

View File

@@ -338,7 +338,7 @@ void VideoDriver_SDL::CheckPaletteAnim()
this->MakeDirty(0, 0, _screen.width, _screen.height);
}
static void Paint()
void VideoDriver_SDL::Paint()
{
PerformanceMeasurer framerate(PFE_VIDEO);
@@ -372,10 +372,10 @@ static void Paint()
}
SDL_UpdateWindowSurfaceRects(_sdl_window, &r, 1);
MemSetT(&_dirty_rect, 0);
_dirty_rect = {};
}
static void PaintThread()
void VideoDriver_SDL::PaintThread()
{
/* First tell the main thread we're started */
std::unique_lock<std::recursive_mutex> lock(*_draw_mutex);
@@ -386,11 +386,16 @@ static void PaintThread()
while (_draw_continue) {
/* Then just draw and wait till we stop */
Paint();
this->Paint();
_draw_signal->wait(lock);
}
}
/* static */ void VideoDriver_SDL::PaintThreadThunk(VideoDriver_SDL *drv)
{
drv->PaintThread();
}
static const Dimension default_resolutions[] = {
{ 640, 480 },
{ 800, 600 },
@@ -556,7 +561,7 @@ bool VideoDriver_SDL::CreateMainSurface(uint w, uint h, bool resize)
* gotten smaller, reset our dirty rects. GameSizeChanged() a bit lower
* will mark the whole screen dirty again anyway, but this time with the
* new dimensions. */
MemSetT(&_dirty_rect, 0);
_dirty_rect = {};
_screen.width = _sdl_surface->w;
_screen.height = _sdl_surface->h;
@@ -1059,11 +1064,43 @@ void VideoDriver_SDL::Stop()
}
}
void VideoDriver_SDL::InputLoop()
{
uint32 mod = SDL_GetModState();
const Uint8 *keys = SDL_GetKeyboardState(NULL);
bool old_ctrl_pressed = _ctrl_pressed;
bool old_shift_pressed = _shift_pressed;
_ctrl_pressed = !!(mod & KMOD_CTRL) != _invert_ctrl;
_shift_pressed = !!(mod & KMOD_SHIFT) != _invert_shift;
#if defined(_DEBUG)
if (_shift_pressed)
#else
/* Speedup when pressing tab, except when using ALT+TAB
* to switch to another application. */
if (keys[SDL_SCANCODE_TAB] && (mod & KMOD_ALT) == 0)
#endif /* defined(_DEBUG) */
{
if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2;
} else if (_fast_forward & 2) {
_fast_forward = 0;
}
/* Determine which directional keys are down. */
_dirkeys =
(keys[SDL_SCANCODE_LEFT] ? 1 : 0) |
(keys[SDL_SCANCODE_UP] ? 2 : 0) |
(keys[SDL_SCANCODE_RIGHT] ? 4 : 0) |
(keys[SDL_SCANCODE_DOWN] ? 8 : 0);
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
if (old_shift_pressed != _shift_pressed) HandleShiftChanged();
}
void VideoDriver_SDL::LoopOnce()
{
uint32 mod;
int numkeys;
const Uint8 *keys;
InteractiveRandom(); // randomness
while (PollEvent() == -1) {}
@@ -1081,106 +1118,23 @@ void VideoDriver_SDL::LoopOnce()
return;
}
mod = SDL_GetModState();
keys = SDL_GetKeyboardState(&numkeys);
#if defined(_DEBUG)
if (_shift_pressed)
#else
/* Speedup when pressing tab, except when using ALT+TAB
* to switch to another application */
if (keys[SDL_SCANCODE_TAB] && (mod & KMOD_ALT) == 0)
#endif /* defined(_DEBUG) */
{
if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2;
} else if (_fast_forward & 2) {
_fast_forward = 0;
}
cur_ticks = std::chrono::steady_clock::now();
/* If more than a millisecond has passed, increase the _realtime_tick. */
if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) {
auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(cur_ticks - last_realtime_tick);
IncreaseRealtimeTick(delta.count());
last_realtime_tick += delta;
}
if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) {
if (_fast_forward && !_pause_mode) {
next_game_tick = cur_ticks + this->GetGameInterval();
} else {
next_game_tick += this->GetGameInterval();
/* Avoid next_game_tick getting behind more and more if it cannot keep up. */
if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks;
}
/* The gameloop is the part that can run asynchronously. The rest
* except sleeping can't. */
if (_draw_mutex != nullptr) draw_lock.unlock();
GameLoop();
if (_draw_mutex != nullptr) draw_lock.lock();
GameLoopPaletteAnimations();
}
/* Prevent drawing when switching mode, as windows can be removed when they should still appear. */
if (cur_ticks >= next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) {
next_draw_tick += this->GetDrawInterval();
/* Avoid next_draw_tick getting behind more and more if it cannot keep up. */
if (next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) next_draw_tick = cur_ticks;
bool old_ctrl_pressed = _ctrl_pressed;
bool old_shift_pressed = _shift_pressed;
_ctrl_pressed = !!(mod & KMOD_CTRL) != _invert_ctrl;
_shift_pressed = !!(mod & KMOD_SHIFT) != _invert_shift;
/* determine which directional keys are down */
_dirkeys =
(keys[SDL_SCANCODE_LEFT] ? 1 : 0) |
(keys[SDL_SCANCODE_UP] ? 2 : 0) |
(keys[SDL_SCANCODE_RIGHT] ? 4 : 0) |
(keys[SDL_SCANCODE_DOWN] ? 8 : 0);
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
if (old_shift_pressed != _shift_pressed) HandleShiftChanged();
InputLoop();
UpdateWindows();
this->CheckPaletteAnim();
if (VideoDriver::Tick()) {
if (_draw_mutex != nullptr && !HasModalProgress()) {
_draw_signal->notify_one();
} else {
Paint();
this->Paint();
}
}
/* Emscripten is running an event-based mainloop; there is already some
* downtime between each iteration, so no need to sleep. */
#ifndef __EMSCRIPTEN__
/* If we are not in fast-forward, create some time between calls to ease up CPU usage. */
if (!_fast_forward || _pause_mode) {
/* See how much time there is till we have to process the next event, and try to hit that as close as possible. */
auto next_tick = std::min(next_draw_tick, next_game_tick);
auto now = std::chrono::steady_clock::now();
if (next_tick > now) {
if (_draw_mutex != nullptr) draw_lock.unlock();
std::this_thread::sleep_for(next_tick - now);
if (_draw_mutex != nullptr) draw_lock.lock();
}
}
this->SleepTillNextTick();
#endif
}
void VideoDriver_SDL::MainLoop()
{
cur_ticks = std::chrono::steady_clock::now();
last_realtime_tick = cur_ticks;
next_game_tick = cur_ticks;
this->CheckPaletteAnim();
if (_draw_threaded) {
/* Initialise the mutex first, because that's the thing we *need*
* directly in the newly created thread. */
@@ -1192,7 +1146,7 @@ void VideoDriver_SDL::MainLoop()
_draw_signal = new std::condition_variable_any();
_draw_continue = true;
_draw_threaded = StartNewThread(&draw_thread, "ottd:draw-sdl", &PaintThread);
_draw_threaded = StartNewThread(&draw_thread, "ottd:draw-sdl", &VideoDriver_SDL::PaintThreadThunk, this);
/* Free the mutex if we won't be able to use it. */
if (!_draw_threaded) {
@@ -1317,3 +1271,14 @@ Dimension VideoDriver_SDL::GetScreenSize() const
return { static_cast<uint>(mode.w), static_cast<uint>(mode.h) };
}
bool VideoDriver_SDL::LockVideoBuffer()
{
if (_draw_threaded) this->draw_lock.lock();
return true;
}
void VideoDriver_SDL::UnlockVideoBuffer()
{
if (_draw_threaded) this->draw_lock.unlock();
}

View File

@@ -43,6 +43,12 @@ public:
protected:
Dimension GetScreenSize() const override;
void InputLoop() override;
bool LockVideoBuffer() override;
void UnlockVideoBuffer() override;
void Paint() override;
void PaintThread() override;
void CheckPaletteAnim() override;
private:
int PollEvent();
@@ -50,7 +56,6 @@ private:
void MainLoopCleanup();
bool CreateMainSurface(uint w, uint h, bool resize);
bool CreateMainWindow(uint w, uint h);
void CheckPaletteAnim();
#ifdef __EMSCRIPTEN__
/* Convert a constant pointer back to a non-constant pointer to a member function. */
@@ -62,14 +67,11 @@ private:
*/
bool edit_box_focused;
std::chrono::steady_clock::time_point cur_ticks;
std::chrono::steady_clock::time_point last_realtime_tick;
std::chrono::steady_clock::time_point next_game_tick;
std::chrono::steady_clock::time_point next_draw_tick;
int startup_display;
std::thread draw_thread;
std::unique_lock<std::recursive_mutex> draw_lock;
static void PaintThreadThunk(VideoDriver_SDL *drv);
};
/** Factory for the SDL video driver. */

View File

@@ -128,8 +128,10 @@ static void InitPalette()
UpdatePalette(true);
}
static void CheckPaletteAnim()
void VideoDriver_SDL::CheckPaletteAnim()
{
_local_palette = _cur_palette;
if (_cur_palette.count_dirty != 0) {
Blitter *blitter = BlitterFactory::GetCurrentBlitter();
@@ -152,7 +154,7 @@ static void CheckPaletteAnim()
}
}
static void DrawSurfaceToScreen()
void VideoDriver_SDL::Paint()
{
PerformanceMeasurer framerate(PFE_VIDEO);
@@ -178,7 +180,7 @@ static void DrawSurfaceToScreen()
}
}
static void DrawSurfaceToScreenThread()
void VideoDriver_SDL::PaintThread()
{
/* First tell the main thread we're started */
std::unique_lock<std::recursive_mutex> lock(*_draw_mutex);
@@ -188,13 +190,17 @@ static void DrawSurfaceToScreenThread()
_draw_signal->wait(*_draw_mutex);
while (_draw_continue) {
CheckPaletteAnim();
/* Then just draw and wait till we stop */
DrawSurfaceToScreen();
this->Paint();
_draw_signal->wait(lock);
}
}
/* static */ void VideoDriver_SDL::PaintThreadThunk(VideoDriver_SDL *drv)
{
drv->PaintThread();
}
static const Dimension _default_resolutions[] = {
{ 640, 480},
{ 800, 600},
@@ -410,11 +416,7 @@ bool VideoDriver_SDL::ClaimMousePointer()
}
struct SDLVkMapping {
#if SDL_VERSION_ATLEAST(1, 3, 0)
SDL_Keycode vk_from;
#else
uint16 vk_from;
#endif
byte vk_count;
byte map_to;
};
@@ -653,20 +655,45 @@ void VideoDriver_SDL::Stop()
}
}
void VideoDriver_SDL::InputLoop()
{
uint32 mod = SDL_GetModState();
int numkeys;
Uint8 *keys = SDL_GetKeyState(&numkeys);
bool old_ctrl_pressed = _ctrl_pressed;
bool old_shift_pressed = _shift_pressed;
_ctrl_pressed = !!(mod & KMOD_CTRL) != _invert_ctrl;
_shift_pressed = !!(mod & KMOD_SHIFT) != _invert_shift;
#if defined(_DEBUG)
if (_shift_pressed)
#else
/* Speedup when pressing tab, except when using ALT+TAB
* to switch to another application. */
if (keys[SDLK_TAB] && (mod & KMOD_ALT) == 0)
#endif /* defined(_DEBUG) */
{
if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2;
} else if (_fast_forward & 2) {
_fast_forward = 0;
}
/* Determine which directional keys are down. */
_dirkeys =
(keys[SDLK_LEFT] ? 1 : 0) |
(keys[SDLK_UP] ? 2 : 0) |
(keys[SDLK_RIGHT] ? 4 : 0) |
(keys[SDLK_DOWN] ? 8 : 0);
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
if (old_shift_pressed != _shift_pressed) HandleShiftChanged();
}
void VideoDriver_SDL::MainLoop()
{
auto cur_ticks = std::chrono::steady_clock::now();
auto last_realtime_tick = cur_ticks;
auto next_game_tick = cur_ticks;
auto next_draw_tick = cur_ticks;
uint32 mod;
int numkeys;
Uint8 *keys;
CheckPaletteAnim();
std::thread draw_thread;
std::unique_lock<std::recursive_mutex> draw_lock;
if (_draw_threaded) {
/* Initialise the mutex first, because that's the thing we *need*
* directly in the newly created thread. */
@@ -674,16 +701,16 @@ void VideoDriver_SDL::MainLoop()
if (_draw_mutex == nullptr) {
_draw_threaded = false;
} else {
draw_lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
this->draw_lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
_draw_signal = new std::condition_variable_any();
_draw_continue = true;
_draw_threaded = StartNewThread(&draw_thread, "ottd:draw-sdl", &DrawSurfaceToScreenThread);
_draw_threaded = StartNewThread(&draw_thread, "ottd:draw-sdl", &VideoDriver_SDL::PaintThreadThunk, this);
/* Free the mutex if we won't be able to use it. */
if (!_draw_threaded) {
draw_lock.unlock();
draw_lock.release();
this->draw_lock.unlock();
this->draw_lock.release();
delete _draw_mutex;
delete _draw_signal;
_draw_mutex = nullptr;
@@ -703,107 +730,14 @@ void VideoDriver_SDL::MainLoop()
while (PollEvent() == -1) {}
if (_exit_game) break;
mod = SDL_GetModState();
#if SDL_VERSION_ATLEAST(1, 3, 0)
keys = SDL_GetKeyboardState(&numkeys);
#else
keys = SDL_GetKeyState(&numkeys);
#endif
#if defined(_DEBUG)
if (_shift_pressed)
#else
/* Speedup when pressing tab, except when using ALT+TAB
* to switch to another application */
#if SDL_VERSION_ATLEAST(1, 3, 0)
if (keys[SDL_SCANCODE_TAB] && (mod & KMOD_ALT) == 0)
#else
if (keys[SDLK_TAB] && (mod & KMOD_ALT) == 0)
#endif /* SDL_VERSION_ATLEAST(1, 3, 0) */
#endif /* defined(_DEBUG) */
{
if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2;
} else if (_fast_forward & 2) {
_fast_forward = 0;
}
cur_ticks = std::chrono::steady_clock::now();
/* If more than a millisecond has passed, increase the _realtime_tick. */
if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) {
auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(cur_ticks - last_realtime_tick);
IncreaseRealtimeTick(delta.count());
last_realtime_tick += delta;
}
if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) {
if (_fast_forward && !_pause_mode) {
next_game_tick = cur_ticks + this->GetGameInterval();
} else {
next_game_tick += this->GetGameInterval();
/* Avoid next_game_tick getting behind more and more if it cannot keep up. */
if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks;
}
/* The gameloop is the part that can run asynchronously. The rest
* except sleeping can't. */
if (_draw_mutex != nullptr) draw_lock.unlock();
GameLoop();
if (_draw_mutex != nullptr) draw_lock.lock();
GameLoopPaletteAnimations();
}
/* Prevent drawing when switching mode, as windows can be removed when they should still appear. */
if (cur_ticks >= next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) {
next_draw_tick += this->GetDrawInterval();
/* Avoid next_draw_tick getting behind more and more if it cannot keep up. */
if (next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) next_draw_tick = cur_ticks;
bool old_ctrl_pressed = _ctrl_pressed;
bool old_shift_pressed = _shift_pressed;
_ctrl_pressed = !!(mod & KMOD_CTRL) != _invert_ctrl;
_shift_pressed = !!(mod & KMOD_SHIFT) != _invert_shift;
/* determine which directional keys are down */
_dirkeys =
#if SDL_VERSION_ATLEAST(1, 3, 0)
(keys[SDL_SCANCODE_LEFT] ? 1 : 0) |
(keys[SDL_SCANCODE_UP] ? 2 : 0) |
(keys[SDL_SCANCODE_RIGHT] ? 4 : 0) |
(keys[SDL_SCANCODE_DOWN] ? 8 : 0);
#else
(keys[SDLK_LEFT] ? 1 : 0) |
(keys[SDLK_UP] ? 2 : 0) |
(keys[SDLK_RIGHT] ? 4 : 0) |
(keys[SDLK_DOWN] ? 8 : 0);
#endif
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
if (old_shift_pressed != _shift_pressed) HandleShiftChanged();
InputLoop();
UpdateWindows();
_local_palette = _cur_palette;
if (this->Tick()) {
if (_draw_mutex != nullptr && !HasModalProgress()) {
_draw_signal->notify_one();
} else {
CheckPaletteAnim();
DrawSurfaceToScreen();
}
}
/* If we are not in fast-forward, create some time between calls to ease up CPU usage. */
if (!_fast_forward || _pause_mode) {
/* See how much time there is till we have to process the next event, and try to hit that as close as possible. */
auto next_tick = std::min(next_draw_tick, next_game_tick);
auto now = std::chrono::steady_clock::now();
if (next_tick > now) {
if (_draw_mutex != nullptr) draw_lock.unlock();
std::this_thread::sleep_for(next_tick - now);
if (_draw_mutex != nullptr) draw_lock.lock();
this->Paint();
}
}
this->SleepTillNextTick();
}
if (_draw_mutex != nullptr) {
@@ -811,8 +745,8 @@ void VideoDriver_SDL::MainLoop()
/* Sending signal if there is no thread blocked
* is very valid and results in noop */
_draw_signal->notify_one();
if (draw_lock.owns_lock()) draw_lock.unlock();
draw_lock.release();
if (this->draw_lock.owns_lock()) this->draw_lock.unlock();
this->draw_lock.release();
draw_thread.join();
delete _draw_mutex;
@@ -863,4 +797,15 @@ void VideoDriver_SDL::ReleaseBlitterLock()
if (_draw_mutex != nullptr) _draw_mutex->unlock();
}
bool VideoDriver_SDL::LockVideoBuffer()
{
if (_draw_threaded) this->draw_lock.lock();
return true;
}
void VideoDriver_SDL::UnlockVideoBuffer()
{
if (_draw_threaded) this->draw_lock.unlock();
}
#endif /* WITH_SDL */

View File

@@ -36,10 +36,23 @@ public:
bool ClaimMousePointer() override;
const char *GetName() const override { return "sdl"; }
protected:
void InputLoop() override;
bool LockVideoBuffer() override;
void UnlockVideoBuffer() override;
void Paint() override;
void PaintThread() override;
void CheckPaletteAnim();
private:
std::unique_lock<std::recursive_mutex> draw_lock;
int PollEvent();
bool CreateMainSurface(uint w, uint h);
void SetupKeyboard();
static void PaintThreadThunk(VideoDriver_SDL *drv);
};
/** Factory for the SDL video driver. */

View File

@@ -0,0 +1,84 @@
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file video_driver.cpp Common code between video driver implementations. */
#include "../stdafx.h"
#include "../debug.h"
#include "../gfx_func.h"
#include "../progress.h"
#include "../thread.h"
#include "../window_func.h"
#include "video_driver.hpp"
bool VideoDriver::Tick()
{
auto cur_ticks = std::chrono::steady_clock::now();
/* If more than a millisecond has passed, increase the _realtime_tick. */
if (cur_ticks - this->last_realtime_tick > std::chrono::milliseconds(1)) {
auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(cur_ticks - this->last_realtime_tick);
_realtime_tick += delta.count();
this->last_realtime_tick += delta;
}
if (cur_ticks >= this->next_game_tick || (_fast_forward && !_pause_mode)) {
if (_fast_forward && !_pause_mode) {
this->next_game_tick = cur_ticks + this->GetGameInterval();
} else {
this->next_game_tick += this->GetGameInterval();
/* Avoid next_game_tick getting behind more and more if it cannot keep up. */
if (this->next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) this->next_game_tick = cur_ticks;
}
/* The game loop is the part that can run asynchronously.
* The rest except sleeping can't. */
this->UnlockVideoBuffer();
::GameLoop();
this->LockVideoBuffer();
::GameLoopPaletteAnimations();
/* For things like dedicated server, don't run a separate draw-tick. */
if (!this->HasGUI()) {
::InputLoop();
UpdateWindows();
this->next_draw_tick = this->next_game_tick;
}
}
/* Prevent drawing when switching mode, as windows can be removed when they should still appear. */
if (this->HasGUI() && cur_ticks >= this->next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) {
this->next_draw_tick += this->GetDrawInterval();
/* Avoid next_draw_tick getting behind more and more if it cannot keep up. */
if (this->next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) this->next_draw_tick = cur_ticks;
this->InputLoop();
::InputLoop();
UpdateWindows();
this->CheckPaletteAnim();
return true;
}
return false;
}
void VideoDriver::SleepTillNextTick()
{
/* If we are not in fast-forward, create some time between calls to ease up CPU usage. */
if (!_fast_forward || _pause_mode) {
/* See how much time there is till we have to process the next event, and try to hit that as close as possible. */
auto next_tick = std::min(this->next_draw_tick, this->next_game_tick);
auto now = std::chrono::steady_clock::now();
if (next_tick > now) {
this->UnlockVideoBuffer();
std::this_thread::sleep_for(next_tick - now);
this->LockVideoBuffer();
}
}
}

View File

@@ -156,6 +156,50 @@ protected:
}
}
/**
* Handle input logic, is CTRL pressed, should we fast-forward, etc.
*/
virtual void InputLoop() {}
/**
* Make sure the video buffer is ready for drawing.
* @returns True if the video buffer has to be unlocked.
*/
virtual bool LockVideoBuffer() {
return false;
}
/**
* Unlock a previously locked video buffer.
*/
virtual void UnlockVideoBuffer() {}
/**
* Paint the window.
*/
virtual void Paint() {}
/**
* Thread function for threaded drawing.
*/
virtual void PaintThread() {}
/**
* Process any pending palette animation.
*/
virtual void CheckPaletteAnim() {}
/**
* Run the game for a single tick, processing boththe game-tick and draw-tick.
* @returns True if the driver should redraw the screen.
*/
bool Tick();
/**
* Sleep till the next tick is about to happen.
*/
void SleepTillNextTick();
std::chrono::steady_clock::duration GetGameInterval()
{
return std::chrono::milliseconds(MILLISECONDS_PER_TICK);
@@ -165,6 +209,10 @@ protected:
{
return std::chrono::microseconds(1000000 / _settings_client.gui.refresh_rate);
}
std::chrono::steady_clock::time_point last_realtime_tick;
std::chrono::steady_clock::time_point next_game_tick;
std::chrono::steady_clock::time_point next_draw_tick;
};
#endif /* VIDEO_VIDEO_DRIVER_HPP */

View File

@@ -14,6 +14,7 @@
#include "../rev.h"
#include "../blitter/factory.hpp"
#include "../network/network.h"
#include "../core/geometry_func.hpp"
#include "../core/math_func.hpp"
#include "../core/random_func.hpp"
#include "../texteff.hpp"
@@ -49,7 +50,6 @@ static struct {
HBITMAP dib_sect; ///< System bitmap object referencing our rendering buffer.
void *buffer_bits; ///< Internal rendering buffer.
HPALETTE gdi_palette; ///< Palette object for 8bpp blitter.
RECT update_rect; ///< Current dirty rect.
int width; ///< Width in pixels of our display surface.
int height; ///< Height in pixels of our display surface.
int width_org; ///< Original monitor resolution width, before we changed it.
@@ -59,9 +59,7 @@ static struct {
bool running; ///< Is the main loop running?
} _wnd;
bool _force_full_redraw;
bool _window_maximize;
uint _display_hz;
static Dimension _bck_resolution;
DWORD _imm_props;
@@ -75,6 +73,8 @@ static std::condition_variable_any *_draw_signal = nullptr;
static volatile bool _draw_continue;
/** Local copy of the palette for use in the drawing thread. */
static Palette _local_palette;
/** Region of the screen that needs redrawing. */
static Rect _dirty_rect;
static void MakePalette()
{
@@ -256,12 +256,10 @@ bool VideoDriver_Win32::MakeWindow(bool full_screen)
settings.dmFields =
DM_BITSPERPEL |
DM_PELSWIDTH |
DM_PELSHEIGHT |
(_display_hz != 0 ? DM_DISPLAYFREQUENCY : 0);
DM_PELSHEIGHT;
settings.dmBitsPerPel = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
settings.dmPelsWidth = _wnd.width_org;
settings.dmPelsHeight = _wnd.height_org;
settings.dmDisplayFrequency = _display_hz;
/* Check for 8 bpp support. */
if (settings.dmBitsPerPel == 8 &&
@@ -335,10 +333,24 @@ bool VideoDriver_Win32::MakeWindow(bool full_screen)
}
/** Do palette animation and blit to the window. */
static void PaintWindow(HDC dc)
void VideoDriver_Win32::Paint()
{
PerformanceMeasurer framerate(PFE_VIDEO);
if (IsEmptyRect(_dirty_rect)) return;
/* Convert update region from logical to device coordinates. */
POINT pt = {0, 0};
ClientToScreen(_wnd.main_wnd, &pt);
RECT r = { _dirty_rect.left, _dirty_rect.top, _dirty_rect.right, _dirty_rect.bottom };
OffsetRect(&r, pt.x, pt.y);
/* Create a device context that is clipped to the region we need to draw.
* GetDCEx 'consumes' the update region, so we may not destroy it ourself. */
HRGN rgn = CreateRectRgnIndirect(&r);
HDC dc = GetDCEx(_wnd.main_wnd, rgn, DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN | DCX_INTERSECTRGN);
HDC dc2 = CreateCompatibleDC(dc);
HBITMAP old_bmp = (HBITMAP)SelectObject(dc2, _wnd.dib_sect);
HPALETTE old_palette = SelectPalette(dc, _wnd.gdi_palette, FALSE);
@@ -368,9 +380,13 @@ static void PaintWindow(HDC dc)
SelectPalette(dc, old_palette, TRUE);
SelectObject(dc2, old_bmp);
DeleteDC(dc2);
ReleaseDC(_wnd.main_wnd, dc);
_dirty_rect = {};
}
static void PaintWindowThread()
void VideoDriver_Win32::PaintThread()
{
/* First tell the main thread we're started */
std::unique_lock<std::recursive_mutex> lock(*_draw_mutex);
@@ -380,21 +396,7 @@ static void PaintWindowThread()
_draw_signal->wait(*_draw_mutex);
while (_draw_continue) {
/* Convert update region from logical to device coordinates. */
POINT pt = {0, 0};
ClientToScreen(_wnd.main_wnd, &pt);
OffsetRect(&_wnd.update_rect, pt.x, pt.y);
/* Create a device context that is clipped to the region we need to draw.
* GetDCEx 'consumes' the update region, so we may not destroy it ourself. */
HRGN rgn = CreateRectRgnIndirect(&_wnd.update_rect);
HDC dc = GetDCEx(_wnd.main_wnd, rgn, DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN | DCX_INTERSECTRGN);
PaintWindow(dc);
/* Clear update rect. */
SetRectEmpty(&_wnd.update_rect);
ReleaseDC(_wnd.main_wnd, dc);
this->Paint();
/* Flush GDI buffer to ensure drawing here doesn't conflict with any GDI usage in the main WndProc. */
GdiFlush();
@@ -403,6 +405,11 @@ static void PaintWindowThread()
}
}
/* static */ void VideoDriver_Win32::PaintThreadThunk(VideoDriver_Win32 *drv)
{
drv->PaintThread();
}
/** Forward key presses to the window system. */
static LRESULT HandleCharMsg(uint keycode, WChar charcode)
{
@@ -606,7 +613,6 @@ static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lP
{
static uint32 keycode = 0;
static bool console = false;
static bool in_sizemove = false;
switch (msg) {
case WM_CREATE:
@@ -615,32 +621,14 @@ static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lP
_imm_props = ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY);
break;
case WM_ENTERSIZEMOVE:
in_sizemove = true;
break;
case WM_PAINT: {
RECT r;
GetUpdateRect(hwnd, &r, FALSE);
static_cast<VideoDriver_Win32 *>(VideoDriver::GetInstance())->MakeDirty(r.left, r.top, r.right - r.left, r.bottom - r.top);
case WM_EXITSIZEMOVE:
in_sizemove = false;
break;
case WM_PAINT:
if (!in_sizemove && _draw_mutex != nullptr && !HasModalProgress()) {
/* Get the union of the old update rect and the new update rect. */
RECT r;
GetUpdateRect(hwnd, &r, FALSE);
UnionRect(&_wnd.update_rect, &_wnd.update_rect, &r);
/* Mark the window as updated, otherwise Windows would send more WM_PAINT messages. */
ValidateRect(hwnd, nullptr);
_draw_signal->notify_one();
} else {
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
PaintWindow(ps.hdc);
EndPaint(hwnd, &ps);
}
ValidateRect(hwnd, nullptr);
return 0;
}
case WM_PALETTECHANGED:
if ((HWND)wParam == hwnd) return 0;
@@ -653,7 +641,9 @@ static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lP
SelectPalette(hDC, hOldPalette, TRUE);
ReleaseDC(hwnd, hDC);
if (nChanged != 0) InvalidateRect(hwnd, nullptr, FALSE);
if (nChanged != 0) {
static_cast<VideoDriver_Win32 *>(VideoDriver::GetInstance())->MakeDirty(0, 0, _screen.width, _screen.height);
}
return 0;
}
@@ -1122,29 +1112,59 @@ void VideoDriver_Win32::Stop()
void VideoDriver_Win32::MakeDirty(int left, int top, int width, int height)
{
RECT r = { left, top, left + width, top + height };
InvalidateRect(_wnd.main_wnd, &r, FALSE);
Rect r = {left, top, left + width, top + height};
_dirty_rect = BoundingRect(_dirty_rect, r);
}
static void CheckPaletteAnim()
void VideoDriver_Win32::CheckPaletteAnim()
{
if (_cur_palette.count_dirty == 0) return;
_local_palette = _cur_palette;
InvalidateRect(_wnd.main_wnd, nullptr, FALSE);
this->MakeDirty(0, 0, _screen.width, _screen.height);
}
void VideoDriver_Win32::InputLoop()
{
bool old_ctrl_pressed = _ctrl_pressed;
bool old_shift_pressed = _shift_pressed;
_ctrl_pressed = (_wnd.has_focus && GetAsyncKeyState(VK_CONTROL) < 0) != _invert_ctrl;
_shift_pressed = (_wnd.has_focus && GetAsyncKeyState(VK_SHIFT) < 0) != _invert_shift;
#if defined(_DEBUG)
if (_shift_pressed)
#else
/* Speedup when pressing tab, except when using ALT+TAB
* to switch to another application. */
if (_wnd.has_focus && GetAsyncKeyState(VK_TAB) < 0 && GetAsyncKeyState(VK_MENU) >= 0)
#endif
{
if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2;
} else if (_fast_forward & 2) {
_fast_forward = 0;
}
/* Determine which directional keys are down. */
if (_wnd.has_focus) {
_dirkeys =
(GetAsyncKeyState(VK_LEFT) < 0 ? 1 : 0) +
(GetAsyncKeyState(VK_UP) < 0 ? 2 : 0) +
(GetAsyncKeyState(VK_RIGHT) < 0 ? 4 : 0) +
(GetAsyncKeyState(VK_DOWN) < 0 ? 8 : 0);
} else {
_dirkeys = 0;
}
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
if (old_shift_pressed != _shift_pressed) HandleShiftChanged();
}
void VideoDriver_Win32::MainLoop()
{
MSG mesg;
auto cur_ticks = std::chrono::steady_clock::now();
auto last_realtime_tick = cur_ticks;
auto next_game_tick = cur_ticks;
auto next_draw_tick = cur_ticks;
std::thread draw_thread;
std::unique_lock<std::recursive_mutex> draw_lock;
if (_draw_threaded) {
/* Initialise the mutex first, because that's the thing we *need*
@@ -1157,15 +1177,15 @@ void VideoDriver_Win32::MainLoop()
}
if (_draw_threaded) {
draw_lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
this->draw_lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
_draw_continue = true;
_draw_threaded = StartNewThread(&draw_thread, "ottd:draw-win32", &PaintWindowThread);
_draw_threaded = StartNewThread(&draw_thread, "ottd:draw-win32", &VideoDriver_Win32::PaintThreadThunk, this);
/* Free the mutex if we won't be able to use it. */
if (!_draw_threaded) {
draw_lock.unlock();
draw_lock.release();
this->draw_lock.unlock();
this->draw_lock.release();
delete _draw_mutex;
delete _draw_signal;
_draw_mutex = nullptr;
@@ -1180,108 +1200,27 @@ void VideoDriver_Win32::MainLoop()
_wnd.running = true;
CheckPaletteAnim();
for (;;) {
InteractiveRandom(); // randomness
while (PeekMessage(&mesg, nullptr, 0, 0, PM_REMOVE)) {
InteractiveRandom(); // randomness
/* Convert key messages to char messages if we want text input. */
if (EditBoxInGlobalFocus()) TranslateMessage(&mesg);
DispatchMessage(&mesg);
}
if (_exit_game) break;
#if defined(_DEBUG)
if (_wnd.has_focus && GetAsyncKeyState(VK_SHIFT) < 0 &&
#else
/* Speed up using TAB, but disable for ALT+TAB of course */
if (_wnd.has_focus && GetAsyncKeyState(VK_TAB) < 0 && GetAsyncKeyState(VK_MENU) >= 0 &&
#endif
!_networking && _game_mode != GM_MENU) {
_fast_forward |= 2;
} else if (_fast_forward & 2) {
_fast_forward = 0;
}
/* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
GdiFlush();
cur_ticks = std::chrono::steady_clock::now();
/* If more than a millisecond has passed, increase the _realtime_tick. */
if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) {
auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(cur_ticks - last_realtime_tick);
IncreaseRealtimeTick(delta.count());
last_realtime_tick += delta;
}
if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) {
if (_fast_forward && !_pause_mode) {
next_game_tick = cur_ticks + this->GetGameInterval();
if (this->Tick()) {
if (_draw_mutex != nullptr && !HasModalProgress()) {
_draw_signal->notify_one();
} else {
next_game_tick += this->GetGameInterval();
/* Avoid next_game_tick getting behind more and more if it cannot keep up. */
if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks;
}
/* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
GdiFlush();
/* The game loop is the part that can run asynchronously.
* The rest except sleeping can't. */
if (_draw_threaded) draw_lock.unlock();
GameLoop();
if (_draw_threaded) draw_lock.lock();
GameLoopPaletteAnimations();
}
/* Prevent drawing when switching mode, as windows can be removed when they should still appear. */
if (cur_ticks >= next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) {
next_draw_tick += this->GetDrawInterval();
/* Avoid next_draw_tick getting behind more and more if it cannot keep up. */
if (next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) next_draw_tick = cur_ticks;
bool old_ctrl_pressed = _ctrl_pressed;
bool old_shift_pressed = _shift_pressed;
_ctrl_pressed = (_wnd.has_focus && GetAsyncKeyState(VK_CONTROL) < 0) != _invert_ctrl;
_shift_pressed = (_wnd.has_focus && GetAsyncKeyState(VK_SHIFT) < 0) != _invert_shift;
/* determine which directional keys are down */
if (_wnd.has_focus) {
_dirkeys =
(GetAsyncKeyState(VK_LEFT) < 0 ? 1 : 0) +
(GetAsyncKeyState(VK_UP) < 0 ? 2 : 0) +
(GetAsyncKeyState(VK_RIGHT) < 0 ? 4 : 0) +
(GetAsyncKeyState(VK_DOWN) < 0 ? 8 : 0);
} else {
_dirkeys = 0;
}
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
if (old_shift_pressed != _shift_pressed) HandleShiftChanged();
if (_force_full_redraw) MarkWholeScreenDirty();
/* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
GdiFlush();
InputLoop();
UpdateWindows();
CheckPaletteAnim();
}
/* If we are not in fast-forward, create some time between calls to ease up CPU usage. */
if (!_fast_forward || _pause_mode) {
/* See how much time there is till we have to process the next event, and try to hit that as close as possible. */
auto next_tick = std::min(next_draw_tick, next_game_tick);
auto now = std::chrono::steady_clock::now();
if (next_tick > now) {
/* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
GdiFlush();
if (_draw_mutex != nullptr) draw_lock.unlock();
std::this_thread::sleep_for(next_tick - now);
if (_draw_mutex != nullptr) draw_lock.lock();
this->Paint();
}
}
this->SleepTillNextTick();
}
if (_draw_threaded) {
@@ -1289,8 +1228,8 @@ void VideoDriver_Win32::MainLoop()
/* Sending signal if there is no thread blocked
* is very valid and results in noop */
_draw_signal->notify_all();
if (draw_lock.owns_lock()) draw_lock.unlock();
draw_lock.release();
if (this->draw_lock.owns_lock()) this->draw_lock.unlock();
this->draw_lock.release();
draw_thread.join();
delete _draw_mutex;
@@ -1391,3 +1330,14 @@ float VideoDriver_Win32::GetDPIScale()
return cur_dpi > 0 ? cur_dpi / 96.0f : 1.0f; // Default Windows DPI value is 96.
}
bool VideoDriver_Win32::LockVideoBuffer()
{
if (_draw_threaded) this->draw_lock.lock();
return true;
}
void VideoDriver_Win32::UnlockVideoBuffer()
{
if (_draw_threaded) this->draw_lock.unlock();
}

View File

@@ -43,8 +43,18 @@ public:
protected:
Dimension GetScreenSize() const override;
float GetDPIScale() override;
void InputLoop() override;
bool LockVideoBuffer() override;
void UnlockVideoBuffer() override;
void Paint() override;
void PaintThread() override;
void CheckPaletteAnim() override;
private:
std::unique_lock<std::recursive_mutex> draw_lock;
static void PaintThreadThunk(VideoDriver_Win32 *drv);
};
/** The factory for Windows' video driver. */