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:
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user