2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
8 /** @file win32_v.cpp Implementation of the Windows (GDI) video driver. */
10 #include "../stdafx.h"
11 #include "../openttd.h"
12 #include "../gfx_func.h"
13 #include "../os/windows/win32.h"
15 #include "../blitter/factory.hpp"
16 #include "../core/geometry_func.hpp"
17 #include "../core/math_func.hpp"
18 #include "../core/random_func.hpp"
19 #include "../texteff.hpp"
20 #include "../thread.h"
21 #include "../progress.h"
22 #include "../window_gui.h"
23 #include "../window_func.h"
24 #include "../framerate_type.h"
28 #include <versionhelpers.h>
30 #include "../safeguards.h"
32 /* Missing define in MinGW headers. */
33 #ifndef MAPVK_VK_TO_CHAR
34 #define MAPVK_VK_TO_CHAR (2)
38 #define PM_QS_INPUT 0x20000
42 #define WM_DPICHANGED 0x02E0
45 bool _window_maximize
;
46 static Dimension _bck_resolution
;
49 static Palette _local_palette
; ///< Current palette to use for drawing.
51 bool VideoDriver_Win32Base::ClaimMousePointer()
53 MyShowCursor(false, true);
57 struct Win32VkMapping
{
63 #define AS(x, z) {x, 0, z}
64 #define AM(x, y, z, w) {x, y - x, z}
66 static const Win32VkMapping _vk_mapping
[] = {
67 /* Pageup stuff + up/down */
68 AM(VK_PRIOR
, VK_DOWN
, WKC_PAGEUP
, WKC_DOWN
),
69 /* Map letters & digits */
70 AM('A', 'Z', 'A', 'Z'),
71 AM('0', '9', '0', '9'),
73 AS(VK_ESCAPE
, WKC_ESC
),
74 AS(VK_PAUSE
, WKC_PAUSE
),
75 AS(VK_BACK
, WKC_BACKSPACE
),
76 AM(VK_INSERT
, VK_DELETE
, WKC_INSERT
, WKC_DELETE
),
78 AS(VK_SPACE
, WKC_SPACE
),
79 AS(VK_RETURN
, WKC_RETURN
),
83 AM(VK_F1
, VK_F12
, WKC_F1
, WKC_F12
),
86 AM(VK_NUMPAD0
, VK_NUMPAD9
, '0', '9'),
87 AS(VK_DIVIDE
, WKC_NUM_DIV
),
88 AS(VK_MULTIPLY
, WKC_NUM_MUL
),
89 AS(VK_SUBTRACT
, WKC_NUM_MINUS
),
90 AS(VK_ADD
, WKC_NUM_PLUS
),
91 AS(VK_DECIMAL
, WKC_NUM_DECIMAL
),
93 /* Other non-letter keys */
95 AS(0xBA, WKC_SEMICOLON
),
97 AS(0xDB, WKC_L_BRACKET
),
98 AS(0xDC, WKC_BACKSLASH
),
99 AS(0xDD, WKC_R_BRACKET
),
101 AS(0xDE, WKC_SINGLEQUOTE
),
107 static uint
MapWindowsKey(uint sym
)
109 const Win32VkMapping
*map
;
112 for (map
= _vk_mapping
; map
!= endof(_vk_mapping
); ++map
) {
113 if ((uint
)(sym
- map
->vk_from
) <= map
->vk_count
) {
114 key
= sym
- map
->vk_from
+ map
->map_to
;
119 if (GetAsyncKeyState(VK_SHIFT
) < 0) key
|= WKC_SHIFT
;
120 if (GetAsyncKeyState(VK_CONTROL
) < 0) key
|= WKC_CTRL
;
121 if (GetAsyncKeyState(VK_MENU
) < 0) key
|= WKC_ALT
;
125 /** Colour depth to use for fullscreen display modes. */
126 uint8
VideoDriver_Win32Base::GetFullscreenBpp()
128 /* Check modes for the relevant fullscreen bpp */
129 return _support8bpp
!= S8BPP_HARDWARE
? 32 : BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
133 * Instantiate a new window.
134 * @param full_screen Whether to make a full screen window or not.
135 * @param resize Whether to change window size.
136 * @return True if the window could be created.
138 bool VideoDriver_Win32Base::MakeWindow(bool full_screen
, bool resize
)
140 /* full_screen is whether the new window should be fullscreen,
141 * _wnd.fullscreen is whether the current window is. */
142 _fullscreen
= full_screen
;
144 /* recreate window? */
145 if ((full_screen
!= this->fullscreen
) && this->main_wnd
) {
146 DestroyWindow(this->main_wnd
);
153 memset(&settings
, 0, sizeof(settings
));
154 settings
.dmSize
= sizeof(settings
);
159 settings
.dmBitsPerPel
= this->GetFullscreenBpp();
160 settings
.dmPelsWidth
= this->width_org
;
161 settings
.dmPelsHeight
= this->height_org
;
163 /* Check for 8 bpp support. */
164 if (settings
.dmBitsPerPel
== 8 && ChangeDisplaySettings(&settings
, CDS_FULLSCREEN
| CDS_TEST
) != DISP_CHANGE_SUCCESSFUL
) {
165 settings
.dmBitsPerPel
= 32;
168 /* Test fullscreen with current resolution, if it fails use desktop resolution. */
169 if (ChangeDisplaySettings(&settings
, CDS_FULLSCREEN
| CDS_TEST
) != DISP_CHANGE_SUCCESSFUL
) {
171 GetWindowRect(GetDesktopWindow(), &r
);
172 /* Guard against recursion. If we already failed here once, just fall through to
173 * the next ChangeDisplaySettings call which will fail and error out appropriately. */
174 if ((int)settings
.dmPelsWidth
!= r
.right
- r
.left
|| (int)settings
.dmPelsHeight
!= r
.bottom
- r
.top
) {
175 return this->ChangeResolution(r
.right
- r
.left
, r
.bottom
- r
.top
);
179 if (ChangeDisplaySettings(&settings
, CDS_FULLSCREEN
) != DISP_CHANGE_SUCCESSFUL
) {
180 this->MakeWindow(false, resize
); // don't care about the result
181 return false; // the request failed
183 } else if (this->fullscreen
) {
184 /* restore display? */
185 ChangeDisplaySettings(nullptr, 0);
186 /* restore the resolution */
187 this->width
= _bck_resolution
.width
;
188 this->height
= _bck_resolution
.height
;
193 DWORD style
, showstyle
;
196 showstyle
= SW_SHOWNORMAL
;
197 this->fullscreen
= full_screen
;
198 if (this->fullscreen
) {
200 SetRect(&r
, 0, 0, this->width_org
, this->height_org
);
202 style
= WS_OVERLAPPEDWINDOW
;
203 /* On window creation, check if we were in maximize mode before */
204 if (_window_maximize
) showstyle
= SW_SHOWMAXIMIZED
;
205 SetRect(&r
, 0, 0, this->width
, this->height
);
208 AdjustWindowRect(&r
, style
, FALSE
);
209 w
= r
.right
- r
.left
;
210 h
= r
.bottom
- r
.top
;
212 if (this->main_wnd
!= nullptr) {
213 if (!_window_maximize
&& resize
) SetWindowPos(this->main_wnd
, 0, 0, 0, w
, h
, SWP_NOACTIVATE
| SWP_NOOWNERZORDER
| SWP_NOZORDER
| SWP_NOMOVE
);
215 int x
= (GetSystemMetrics(SM_CXSCREEN
) - w
) / 2;
216 int y
= (GetSystemMetrics(SM_CYSCREEN
) - h
) / 2;
218 char window_title
[64];
219 seprintf(window_title
, lastof(window_title
), "OpenTTD %s", _openttd_revision
);
221 this->main_wnd
= CreateWindow(L
"OTTD", OTTD2FS(window_title
).c_str(), style
, x
, y
, w
, h
, 0, 0, GetModuleHandle(nullptr), this);
222 if (this->main_wnd
== nullptr) usererror("CreateWindow failed");
223 ShowWindow(this->main_wnd
, showstyle
);
227 BlitterFactory::GetCurrentBlitter()->PostResize();
233 /** Forward key presses to the window system. */
234 static LRESULT
HandleCharMsg(uint keycode
, WChar charcode
)
236 static WChar prev_char
= 0;
238 /* Did we get a lead surrogate? If yes, store and exit. */
239 if (Utf16IsLeadSurrogate(charcode
)) {
240 if (prev_char
!= 0) Debug(driver
, 1, "Got two UTF-16 lead surrogates, dropping the first one");
241 prev_char
= charcode
;
245 /* Stored lead surrogate and incoming trail surrogate? Combine and forward to input handling. */
246 if (prev_char
!= 0) {
247 if (Utf16IsTrailSurrogate(charcode
)) {
248 charcode
= Utf16DecodeSurrogate(prev_char
, charcode
);
250 Debug(driver
, 1, "Got an UTF-16 lead surrogate without a trail surrogate, dropping the lead surrogate");
255 HandleKeypress(keycode
, charcode
);
260 /** Should we draw the composition string ourself, i.e is this a normal IME? */
261 static bool DrawIMECompositionString()
263 return (_imm_props
& IME_PROP_AT_CARET
) && !(_imm_props
& IME_PROP_SPECIAL_UI
);
266 /** Set position of the composition window to the caret position. */
267 static void SetCompositionPos(HWND hwnd
)
269 HIMC hIMC
= ImmGetContext(hwnd
);
272 cf
.dwStyle
= CFS_POINT
;
274 if (EditBoxInGlobalFocus()) {
275 /* Get caret position. */
276 Point pt
= _focused_window
->GetCaretPosition();
277 cf
.ptCurrentPos
.x
= _focused_window
->left
+ pt
.x
;
278 cf
.ptCurrentPos
.y
= _focused_window
->top
+ pt
.y
;
280 cf
.ptCurrentPos
.x
= 0;
281 cf
.ptCurrentPos
.y
= 0;
283 ImmSetCompositionWindow(hIMC
, &cf
);
285 ImmReleaseContext(hwnd
, hIMC
);
288 /** Set the position of the candidate window. */
289 static void SetCandidatePos(HWND hwnd
)
291 HIMC hIMC
= ImmGetContext(hwnd
);
295 cf
.dwStyle
= CFS_EXCLUDE
;
297 if (EditBoxInGlobalFocus()) {
298 Point pt
= _focused_window
->GetCaretPosition();
299 cf
.ptCurrentPos
.x
= _focused_window
->left
+ pt
.x
;
300 cf
.ptCurrentPos
.y
= _focused_window
->top
+ pt
.y
;
301 if (_focused_window
->window_class
== WC_CONSOLE
) {
302 cf
.rcArea
.left
= _focused_window
->left
;
303 cf
.rcArea
.top
= _focused_window
->top
;
304 cf
.rcArea
.right
= _focused_window
->left
+ _focused_window
->width
;
305 cf
.rcArea
.bottom
= _focused_window
->top
+ _focused_window
->height
;
307 cf
.rcArea
.left
= _focused_window
->left
+ _focused_window
->nested_focus
->pos_x
;
308 cf
.rcArea
.top
= _focused_window
->top
+ _focused_window
->nested_focus
->pos_y
;
309 cf
.rcArea
.right
= cf
.rcArea
.left
+ _focused_window
->nested_focus
->current_x
;
310 cf
.rcArea
.bottom
= cf
.rcArea
.top
+ _focused_window
->nested_focus
->current_y
;
313 cf
.ptCurrentPos
.x
= 0;
314 cf
.ptCurrentPos
.y
= 0;
315 SetRectEmpty(&cf
.rcArea
);
317 ImmSetCandidateWindow(hIMC
, &cf
);
319 ImmReleaseContext(hwnd
, hIMC
);
322 /** Handle WM_IME_COMPOSITION messages. */
323 static LRESULT
HandleIMEComposition(HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
325 HIMC hIMC
= ImmGetContext(hwnd
);
328 if (lParam
& GCS_RESULTSTR
) {
329 /* Read result string from the IME. */
330 LONG len
= ImmGetCompositionString(hIMC
, GCS_RESULTSTR
, nullptr, 0); // Length is always in bytes, even in UNICODE build.
331 wchar_t *str
= (wchar_t *)_alloca(len
+ sizeof(wchar_t));
332 len
= ImmGetCompositionString(hIMC
, GCS_RESULTSTR
, str
, len
);
333 str
[len
/ sizeof(wchar_t)] = '\0';
335 /* Transmit text to windowing system. */
337 HandleTextInput(nullptr, true); // Clear marked string.
338 HandleTextInput(FS2OTTD(str
).c_str());
340 SetCompositionPos(hwnd
);
342 /* Don't pass the result string on to the default window proc. */
343 lParam
&= ~(GCS_RESULTSTR
| GCS_RESULTCLAUSE
| GCS_RESULTREADCLAUSE
| GCS_RESULTREADSTR
);
346 if ((lParam
& GCS_COMPSTR
) && DrawIMECompositionString()) {
347 /* Read composition string from the IME. */
348 LONG len
= ImmGetCompositionString(hIMC
, GCS_COMPSTR
, nullptr, 0); // Length is always in bytes, even in UNICODE build.
349 wchar_t *str
= (wchar_t *)_alloca(len
+ sizeof(wchar_t));
350 len
= ImmGetCompositionString(hIMC
, GCS_COMPSTR
, str
, len
);
351 str
[len
/ sizeof(wchar_t)] = '\0';
354 static char utf8_buf
[1024];
355 convert_from_fs(str
, utf8_buf
, lengthof(utf8_buf
));
357 /* Convert caret position from bytes in the input string to a position in the UTF-8 encoded string. */
358 LONG caret_bytes
= ImmGetCompositionString(hIMC
, GCS_CURSORPOS
, nullptr, 0);
359 const char *caret
= utf8_buf
;
360 for (const wchar_t *c
= str
; *c
!= '\0' && *caret
!= '\0' && caret_bytes
> 0; c
++, caret_bytes
--) {
361 /* Skip DBCS lead bytes or leading surrogates. */
362 if (Utf16IsLeadSurrogate(*c
)) {
369 HandleTextInput(utf8_buf
, true, caret
);
371 HandleTextInput(nullptr, true);
374 lParam
&= ~(GCS_COMPSTR
| GCS_COMPATTR
| GCS_COMPCLAUSE
| GCS_CURSORPOS
| GCS_DELTASTART
);
377 ImmReleaseContext(hwnd
, hIMC
);
379 return lParam
!= 0 ? DefWindowProc(hwnd
, WM_IME_COMPOSITION
, wParam
, lParam
) : 0;
382 /** Clear the current composition string. */
383 static void CancelIMEComposition(HWND hwnd
)
385 HIMC hIMC
= ImmGetContext(hwnd
);
386 if (hIMC
!= NULL
) ImmNotifyIME(hIMC
, NI_COMPOSITIONSTR
, CPS_CANCEL
, 0);
387 ImmReleaseContext(hwnd
, hIMC
);
388 /* Clear any marked string from the current edit box. */
389 HandleTextInput(nullptr, true);
392 LRESULT CALLBACK
WndProcGdi(HWND hwnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
394 static uint32 keycode
= 0;
395 static bool console
= false;
397 VideoDriver_Win32Base
*video_driver
= (VideoDriver_Win32Base
*)GetWindowLongPtr(hwnd
, GWLP_USERDATA
);
401 SetWindowLongPtr(hwnd
, GWLP_USERDATA
, (LONG_PTR
)((LPCREATESTRUCT
)lParam
)->lpCreateParams
);
402 _cursor
.in_window
= false; // Win32 has mouse tracking.
403 SetCompositionPos(hwnd
);
404 _imm_props
= ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY
);
409 GetUpdateRect(hwnd
, &r
, FALSE
);
410 video_driver
->MakeDirty(r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
);
412 ValidateRect(hwnd
, nullptr);
416 case WM_PALETTECHANGED
:
417 if ((HWND
)wParam
== hwnd
) return 0;
420 case WM_QUERYNEWPALETTE
:
421 video_driver
->PaletteChanged(hwnd
);
425 HandleExitGameRequest();
429 if (_window_maximize
) _cur_resolution
= _bck_resolution
;
434 _left_button_down
= true;
440 _left_button_down
= false;
441 _left_button_clicked
= false;
447 _right_button_down
= true;
448 _right_button_clicked
= true;
454 _right_button_down
= false;
460 _cursor
.in_window
= false;
462 if (!_left_button_down
&& !_right_button_down
) MyShowCursor(true);
466 int x
= (int16
)LOWORD(lParam
);
467 int y
= (int16
)HIWORD(lParam
);
469 /* If the mouse was not in the window and it has moved it means it has
470 * come into the window, so start drawing the mouse. Also start
471 * tracking the mouse for exiting the window */
472 if (!_cursor
.in_window
) {
473 _cursor
.in_window
= true;
475 tme
.cbSize
= sizeof(tme
);
476 tme
.dwFlags
= TME_LEAVE
;
477 tme
.hwndTrack
= hwnd
;
479 TrackMouseEvent(&tme
);
482 if (_cursor
.fix_at
) {
483 /* Get all queued mouse events now in case we have to warp the cursor. In the
484 * end, we only care about the current mouse position and not bygone events. */
486 while (PeekMessage(&m
, hwnd
, WM_MOUSEMOVE
, WM_MOUSEMOVE
, PM_REMOVE
| PM_NOYIELD
| PM_QS_INPUT
)) {
487 x
= (int16
)LOWORD(m
.lParam
);
488 y
= (int16
)HIWORD(m
.lParam
);
492 if (_cursor
.UpdateCursorPosition(x
, y
, false)) {
494 pt
.x
= _cursor
.pos
.x
;
495 pt
.y
= _cursor
.pos
.y
;
496 ClientToScreen(hwnd
, &pt
);
497 SetCursorPos(pt
.x
, pt
.y
);
504 case WM_INPUTLANGCHANGE
:
505 _imm_props
= ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY
);
508 case WM_IME_SETCONTEXT
:
509 /* Don't show the composition window if we draw the string ourself. */
510 if (DrawIMECompositionString()) lParam
&= ~ISC_SHOWUICOMPOSITIONWINDOW
;
513 case WM_IME_STARTCOMPOSITION
:
514 SetCompositionPos(hwnd
);
515 if (DrawIMECompositionString()) return 0;
518 case WM_IME_COMPOSITION
:
519 return HandleIMEComposition(hwnd
, wParam
, lParam
);
521 case WM_IME_ENDCOMPOSITION
:
522 /* Clear any pending composition string. */
523 HandleTextInput(nullptr, true);
524 if (DrawIMECompositionString()) return 0;
528 if (wParam
== IMN_OPENCANDIDATE
) SetCandidatePos(hwnd
);
532 console
= GB(lParam
, 16, 8) == 41;
536 uint scancode
= GB(lParam
, 16, 8);
537 uint charcode
= wParam
;
539 /* If the console key is a dead-key, we need to press it twice to get a WM_CHAR message.
540 * But we then get two WM_CHAR messages, so ignore the first one */
541 if (console
&& scancode
== 41) {
546 /* IMEs and other input methods sometimes send a WM_CHAR without a WM_KEYDOWN,
547 * clear the keycode so a previous WM_KEYDOWN doesn't become 'stuck'. */
548 uint cur_keycode
= keycode
;
551 return HandleCharMsg(cur_keycode
, charcode
);
555 /* No matter the keyboard layout, we will map the '~' to the console. */
556 uint scancode
= GB(lParam
, 16, 8);
557 keycode
= scancode
== 41 ? (uint
)WKC_BACKQUOTE
: MapWindowsKey(wParam
);
559 uint charcode
= MapVirtualKey(wParam
, MAPVK_VK_TO_CHAR
);
561 /* No character translation? */
563 HandleKeypress(keycode
, 0);
567 /* If an edit box is in focus, wait for the corresponding WM_CHAR message. */
568 if (!EditBoxInGlobalFocus()) {
569 /* Is the console key a dead key? If yes, ignore the first key down event. */
570 if (HasBit(charcode
, 31) && !console
) {
571 if (scancode
== 41) {
578 /* IMEs and other input methods sometimes send a WM_CHAR without a WM_KEYDOWN,
579 * clear the keycode so a previous WM_KEYDOWN doesn't become 'stuck'. */
580 uint cur_keycode
= keycode
;
583 return HandleCharMsg(cur_keycode
, LOWORD(charcode
));
589 case WM_SYSKEYDOWN
: // user presses F10 or Alt, both activating the title-menu
592 case 'F': // Full Screen on ALT + ENTER/F
593 ToggleFullScreen(!video_driver
->fullscreen
);
596 case VK_MENU
: // Just ALT
597 return 0; // do nothing
599 case VK_F10
: // F10, ignore activation of menu
600 HandleKeypress(MapWindowsKey(wParam
), 0);
603 default: // ALT in combination with something else
604 HandleKeypress(MapWindowsKey(wParam
), 0);
610 if (wParam
!= SIZE_MINIMIZED
) {
611 /* Set maximized flag when we maximize (obviously), but also when we
612 * switched to fullscreen from a maximized state */
613 _window_maximize
= (wParam
== SIZE_MAXIMIZED
|| (_window_maximize
&& _fullscreen
));
614 if (_window_maximize
|| _fullscreen
) _bck_resolution
= _cur_resolution
;
615 video_driver
->ClientSizeChanged(LOWORD(lParam
), HIWORD(lParam
));
620 RECT
*r
= (RECT
*)lParam
;
624 SetRect(&r2
, 0, 0, 0, 0);
625 AdjustWindowRect(&r2
, GetWindowLong(hwnd
, GWL_STYLE
), FALSE
);
627 w
= r
->right
- r
->left
- (r2
.right
- r2
.left
);
628 h
= r
->bottom
- r
->top
- (r2
.bottom
- r2
.top
);
631 SetRect(&r2
, 0, 0, w
, h
);
633 AdjustWindowRect(&r2
, GetWindowLong(hwnd
, GWL_STYLE
), FALSE
);
634 w
= r2
.right
- r2
.left
;
635 h
= r2
.bottom
- r2
.top
;
639 r
->bottom
= r
->top
+ h
;
642 case WMSZ_BOTTOMLEFT
:
643 r
->bottom
= r
->top
+ h
;
644 r
->left
= r
->right
- w
;
647 case WMSZ_BOTTOMRIGHT
:
648 r
->bottom
= r
->top
+ h
;
649 r
->right
= r
->left
+ w
;
653 r
->left
= r
->right
- w
;
657 r
->right
= r
->left
+ w
;
661 r
->top
= r
->bottom
- h
;
665 r
->top
= r
->bottom
- h
;
666 r
->left
= r
->right
- w
;
670 r
->top
= r
->bottom
- h
;
671 r
->right
= r
->left
+ w
;
677 case WM_DPICHANGED
: {
678 auto did_adjust
= AdjustGUIZoom();
680 /* Resize the window to match the new DPI setting. */
681 RECT
*prcNewWindow
= (RECT
*)lParam
;
686 prcNewWindow
->right
- prcNewWindow
->left
,
687 prcNewWindow
->bottom
- prcNewWindow
->top
,
688 SWP_NOZORDER
| SWP_NOACTIVATE
);
690 if (did_adjust
) ReInitAllWindows(true);
695 /* needed for wheel */
696 #if !defined(WM_MOUSEWHEEL)
697 # define WM_MOUSEWHEEL 0x020A
698 #endif /* WM_MOUSEWHEEL */
699 #if !defined(GET_WHEEL_DELTA_WPARAM)
700 # define GET_WHEEL_DELTA_WPARAM(wparam) ((short)HIWORD(wparam))
701 #endif /* GET_WHEEL_DELTA_WPARAM */
703 case WM_MOUSEWHEEL
: {
704 int delta
= GET_WHEEL_DELTA_WPARAM(wParam
);
708 } else if (delta
> 0) {
716 video_driver
->has_focus
= true;
717 SetCompositionPos(hwnd
);
721 video_driver
->has_focus
= false;
725 /* Don't do anything if we are closing openttd */
726 if (_exit_game
) break;
728 bool active
= (LOWORD(wParam
) != WA_INACTIVE
);
729 bool minimized
= (HIWORD(wParam
) != 0);
730 if (video_driver
->fullscreen
) {
731 if (active
&& minimized
) {
732 /* Restore the game window */
733 Dimension d
= _bck_resolution
; // Save current non-fullscreen window size as it will be overwritten by ShowWindow.
734 ShowWindow(hwnd
, SW_RESTORE
);
736 video_driver
->MakeWindow(true);
737 } else if (!active
&& !minimized
) {
738 /* Minimise the window and restore desktop */
739 ShowWindow(hwnd
, SW_MINIMIZE
);
740 ChangeDisplaySettings(nullptr, 0);
747 return DefWindowProc(hwnd
, msg
, wParam
, lParam
);
750 static void RegisterWndClass()
752 static bool registered
= false;
754 if (registered
) return;
756 HINSTANCE hinst
= GetModuleHandle(nullptr);
763 LoadIcon(hinst
, MAKEINTRESOURCE(100)),
764 LoadCursor(nullptr, IDC_ARROW
),
771 if (!RegisterClass(&wnd
)) usererror("RegisterClass failed");
774 static const Dimension default_resolutions
[] = {
788 static void FindResolutions(uint8 bpp
)
790 _resolutions
.clear();
793 for (uint i
= 0; EnumDisplaySettings(nullptr, i
, &dm
) != 0; i
++) {
794 if (dm
.dmBitsPerPel
!= bpp
|| dm
.dmPelsWidth
< 640 || dm
.dmPelsHeight
< 480) continue;
795 if (std::find(_resolutions
.begin(), _resolutions
.end(), Dimension(dm
.dmPelsWidth
, dm
.dmPelsHeight
)) != _resolutions
.end()) continue;
796 _resolutions
.emplace_back(dm
.dmPelsWidth
, dm
.dmPelsHeight
);
799 /* We have found no resolutions, show the default list */
800 if (_resolutions
.empty()) {
801 _resolutions
.assign(std::begin(default_resolutions
), std::end(default_resolutions
));
807 void VideoDriver_Win32Base::Initialize()
809 this->UpdateAutoResolution();
812 FindResolutions(this->GetFullscreenBpp());
814 /* fullscreen uses those */
815 this->width
= this->width_org
= _cur_resolution
.width
;
816 this->height
= this->height_org
= _cur_resolution
.height
;
818 Debug(driver
, 2, "Resolution for display: {}x{}", _cur_resolution
.width
, _cur_resolution
.height
);
821 void VideoDriver_Win32Base::Stop()
823 DestroyWindow(this->main_wnd
);
825 if (this->fullscreen
) ChangeDisplaySettings(nullptr, 0);
828 void VideoDriver_Win32Base::MakeDirty(int left
, int top
, int width
, int height
)
830 Rect r
= {left
, top
, left
+ width
, top
+ height
};
831 this->dirty_rect
= BoundingRect(this->dirty_rect
, r
);
834 void VideoDriver_Win32Base::CheckPaletteAnim()
836 if (!CopyPalette(_local_palette
)) return;
837 this->MakeDirty(0, 0, _screen
.width
, _screen
.height
);
840 void VideoDriver_Win32Base::InputLoop()
842 bool old_ctrl_pressed
= _ctrl_pressed
;
844 _ctrl_pressed
= this->has_focus
&& GetAsyncKeyState(VK_CONTROL
) < 0;
845 _shift_pressed
= this->has_focus
&& GetAsyncKeyState(VK_SHIFT
) < 0;
848 this->fast_forward_key_pressed
= _shift_pressed
;
850 /* Speedup when pressing tab, except when using ALT+TAB
851 * to switch to another application. */
852 this->fast_forward_key_pressed
= this->has_focus
&& GetAsyncKeyState(VK_TAB
) < 0 && GetAsyncKeyState(VK_MENU
) >= 0;
855 /* Determine which directional keys are down. */
856 if (this->has_focus
) {
858 (GetAsyncKeyState(VK_LEFT
) < 0 ? 1 : 0) +
859 (GetAsyncKeyState(VK_UP
) < 0 ? 2 : 0) +
860 (GetAsyncKeyState(VK_RIGHT
) < 0 ? 4 : 0) +
861 (GetAsyncKeyState(VK_DOWN
) < 0 ? 8 : 0);
866 if (old_ctrl_pressed
!= _ctrl_pressed
) HandleCtrlChanged();
869 bool VideoDriver_Win32Base::PollEvent()
873 if (!PeekMessage(&mesg
, nullptr, 0, 0, PM_REMOVE
)) return false;
875 /* Convert key messages to char messages if we want text input. */
876 if (EditBoxInGlobalFocus()) TranslateMessage(&mesg
);
877 DispatchMessage(&mesg
);
882 void VideoDriver_Win32Base::MainLoop()
884 this->StartGameThread();
887 if (_exit_game
) break;
890 this->SleepTillNextTick();
893 this->StopGameThread();
896 void VideoDriver_Win32Base::ClientSizeChanged(int w
, int h
, bool force
)
898 /* Allocate backing store of the new size. */
899 if (this->AllocateBackingStore(w
, h
, force
)) {
900 CopyPalette(_local_palette
, true);
902 BlitterFactory::GetCurrentBlitter()->PostResize();
908 bool VideoDriver_Win32Base::ChangeResolution(int w
, int h
)
910 if (_window_maximize
) ShowWindow(this->main_wnd
, SW_SHOWNORMAL
);
912 this->width
= this->width_org
= w
;
913 this->height
= this->height_org
= h
;
915 return this->MakeWindow(_fullscreen
); // _wnd.fullscreen screws up ingame resolution switching
918 bool VideoDriver_Win32Base::ToggleFullscreen(bool full_screen
)
920 bool res
= this->MakeWindow(full_screen
);
922 InvalidateWindowClassesData(WC_GAME_OPTIONS
, 3);
926 void VideoDriver_Win32Base::EditBoxLostFocus()
928 CancelIMEComposition(this->main_wnd
);
929 SetCompositionPos(this->main_wnd
);
930 SetCandidatePos(this->main_wnd
);
933 static BOOL CALLBACK
MonitorEnumProc(HMONITOR hMonitor
, HDC hDC
, LPRECT rc
, LPARAM data
)
935 auto &list
= *reinterpret_cast<std::vector
<int>*>(data
);
937 MONITORINFOEX monitorInfo
= {};
938 monitorInfo
.cbSize
= sizeof(MONITORINFOEX
);
939 GetMonitorInfo(hMonitor
, &monitorInfo
);
941 DEVMODE devMode
= {};
942 devMode
.dmSize
= sizeof(DEVMODE
);
943 devMode
.dmDriverExtra
= 0;
944 EnumDisplaySettings(monitorInfo
.szDevice
, ENUM_CURRENT_SETTINGS
, &devMode
);
946 if (devMode
.dmDisplayFrequency
!= 0) list
.push_back(devMode
.dmDisplayFrequency
);
950 std::vector
<int> VideoDriver_Win32Base::GetListOfMonitorRefreshRates()
952 std::vector
<int> rates
= {};
953 EnumDisplayMonitors(nullptr, nullptr, MonitorEnumProc
, reinterpret_cast<LPARAM
>(&rates
));
957 Dimension
VideoDriver_Win32Base::GetScreenSize() const
959 return { static_cast<uint
>(GetSystemMetrics(SM_CXSCREEN
)), static_cast<uint
>(GetSystemMetrics(SM_CYSCREEN
)) };
962 float VideoDriver_Win32Base::GetDPIScale()
964 typedef UINT (WINAPI
*PFNGETDPIFORWINDOW
)(HWND hwnd
);
965 typedef UINT (WINAPI
*PFNGETDPIFORSYSTEM
)(VOID
);
966 typedef HRESULT (WINAPI
*PFNGETDPIFORMONITOR
)(HMONITOR hMonitor
, int dpiType
, UINT
*dpiX
, UINT
*dpiY
);
968 static PFNGETDPIFORWINDOW _GetDpiForWindow
= nullptr;
969 static PFNGETDPIFORSYSTEM _GetDpiForSystem
= nullptr;
970 static PFNGETDPIFORMONITOR _GetDpiForMonitor
= nullptr;
972 static bool init_done
= false;
975 static DllLoader
_user32(L
"user32.dll");
976 static DllLoader
_shcore(L
"shcore.dll");
977 _GetDpiForWindow
= _user32
.GetProcAddress("GetDpiForWindow");
978 _GetDpiForSystem
= _user32
.GetProcAddress("GetDpiForSystem");
979 _GetDpiForMonitor
= _shcore
.GetProcAddress("GetDpiForMonitor");
984 if (cur_dpi
== 0 && _GetDpiForWindow
!= nullptr && this->main_wnd
!= nullptr) {
985 /* Per window DPI is supported since Windows 10 Ver 1607. */
986 cur_dpi
= _GetDpiForWindow(this->main_wnd
);
988 if (cur_dpi
== 0 && _GetDpiForMonitor
!= nullptr && this->main_wnd
!= nullptr) {
989 /* Per monitor is supported since Windows 8.1. */
991 if (SUCCEEDED(_GetDpiForMonitor(MonitorFromWindow(this->main_wnd
, MONITOR_DEFAULTTOPRIMARY
), 0 /* MDT_EFFECTIVE_DPI */, &dpiX
, &dpiY
))) {
992 cur_dpi
= dpiX
; // X and Y are always identical.
995 if (cur_dpi
== 0 && _GetDpiForSystem
!= nullptr) {
996 /* Fall back to system DPI. */
997 cur_dpi
= _GetDpiForSystem();
1000 return cur_dpi
> 0 ? cur_dpi
/ 96.0f
: 1.0f
; // Default Windows DPI value is 96.
1003 bool VideoDriver_Win32Base::LockVideoBuffer()
1005 if (this->buffer_locked
) return false;
1006 this->buffer_locked
= true;
1008 _screen
.dst_ptr
= this->GetVideoPointer();
1009 assert(_screen
.dst_ptr
!= nullptr);
1014 void VideoDriver_Win32Base::UnlockVideoBuffer()
1016 assert(_screen
.dst_ptr
!= nullptr);
1017 if (_screen
.dst_ptr
!= nullptr) {
1018 /* Hand video buffer back to the drawing backend. */
1019 this->ReleaseVideoPointer();
1020 _screen
.dst_ptr
= nullptr;
1023 this->buffer_locked
= false;
1027 static FVideoDriver_Win32GDI iFVideoDriver_Win32GDI
;
1029 const char *VideoDriver_Win32GDI::Start(const StringList
¶m
)
1031 if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) return "Only real blitters supported";
1035 this->MakePalette();
1036 this->AllocateBackingStore(_cur_resolution
.width
, _cur_resolution
.height
);
1037 this->MakeWindow(_fullscreen
);
1039 MarkWholeScreenDirty();
1041 this->is_game_threaded
= !GetDriverParamBool(param
, "no_threads") && !GetDriverParamBool(param
, "no_thread");
1046 void VideoDriver_Win32GDI::Stop()
1048 DeleteObject(this->gdi_palette
);
1049 DeleteObject(this->dib_sect
);
1051 this->VideoDriver_Win32Base::Stop();
1054 bool VideoDriver_Win32GDI::AllocateBackingStore(int w
, int h
, bool force
)
1056 uint bpp
= BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
1058 w
= std::max(w
, 64);
1059 h
= std::max(h
, 64);
1061 if (!force
&& w
== _screen
.width
&& h
== _screen
.height
) return false;
1063 BITMAPINFO
*bi
= (BITMAPINFO
*)alloca(sizeof(BITMAPINFOHEADER
) + sizeof(RGBQUAD
) * 256);
1064 memset(bi
, 0, sizeof(BITMAPINFOHEADER
) + sizeof(RGBQUAD
) * 256);
1065 bi
->bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
1067 bi
->bmiHeader
.biWidth
= this->width
= w
;
1068 bi
->bmiHeader
.biHeight
= -(this->height
= h
);
1070 bi
->bmiHeader
.biPlanes
= 1;
1071 bi
->bmiHeader
.biBitCount
= bpp
;
1072 bi
->bmiHeader
.biCompression
= BI_RGB
;
1074 if (this->dib_sect
) DeleteObject(this->dib_sect
);
1077 this->dib_sect
= CreateDIBSection(dc
, bi
, DIB_RGB_COLORS
, (VOID
**)&this->buffer_bits
, nullptr, 0);
1078 if (this->dib_sect
== nullptr) usererror("CreateDIBSection failed");
1082 _screen
.pitch
= (bpp
== 8) ? Align(w
, 4) : w
;
1084 _screen
.dst_ptr
= this->GetVideoPointer();
1089 bool VideoDriver_Win32GDI::AfterBlitterChange()
1091 assert(BlitterFactory::GetCurrentBlitter()->GetScreenDepth() != 0);
1092 return this->AllocateBackingStore(_screen
.width
, _screen
.height
, true) && this->MakeWindow(_fullscreen
, false);
1095 void VideoDriver_Win32GDI::MakePalette()
1097 CopyPalette(_local_palette
, true);
1099 LOGPALETTE
*pal
= (LOGPALETTE
*)alloca(sizeof(LOGPALETTE
) + (256 - 1) * sizeof(PALETTEENTRY
));
1101 pal
->palVersion
= 0x300;
1102 pal
->palNumEntries
= 256;
1104 for (uint i
= 0; i
!= 256; i
++) {
1105 pal
->palPalEntry
[i
].peRed
= _local_palette
.palette
[i
].r
;
1106 pal
->palPalEntry
[i
].peGreen
= _local_palette
.palette
[i
].g
;
1107 pal
->palPalEntry
[i
].peBlue
= _local_palette
.palette
[i
].b
;
1108 pal
->palPalEntry
[i
].peFlags
= 0;
1111 this->gdi_palette
= CreatePalette(pal
);
1112 if (this->gdi_palette
== nullptr) usererror("CreatePalette failed!\n");
1115 void VideoDriver_Win32GDI::UpdatePalette(HDC dc
, uint start
, uint count
)
1119 for (uint i
= 0; i
!= count
; i
++) {
1120 rgb
[i
].rgbRed
= _local_palette
.palette
[start
+ i
].r
;
1121 rgb
[i
].rgbGreen
= _local_palette
.palette
[start
+ i
].g
;
1122 rgb
[i
].rgbBlue
= _local_palette
.palette
[start
+ i
].b
;
1123 rgb
[i
].rgbReserved
= 0;
1126 SetDIBColorTable(dc
, start
, count
, rgb
);
1129 void VideoDriver_Win32GDI::PaletteChanged(HWND hWnd
)
1131 HDC hDC
= GetWindowDC(hWnd
);
1132 HPALETTE hOldPalette
= SelectPalette(hDC
, this->gdi_palette
, FALSE
);
1133 UINT nChanged
= RealizePalette(hDC
);
1135 SelectPalette(hDC
, hOldPalette
, TRUE
);
1136 ReleaseDC(hWnd
, hDC
);
1137 if (nChanged
!= 0) this->MakeDirty(0, 0, _screen
.width
, _screen
.height
);
1140 void VideoDriver_Win32GDI::Paint()
1142 PerformanceMeasurer
framerate(PFE_VIDEO
);
1144 if (IsEmptyRect(this->dirty_rect
)) return;
1146 HDC dc
= GetDC(this->main_wnd
);
1147 HDC dc2
= CreateCompatibleDC(dc
);
1149 HBITMAP old_bmp
= (HBITMAP
)SelectObject(dc2
, this->dib_sect
);
1150 HPALETTE old_palette
= SelectPalette(dc
, this->gdi_palette
, FALSE
);
1152 if (_local_palette
.count_dirty
!= 0) {
1153 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1155 switch (blitter
->UsePaletteAnimation()) {
1156 case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND
:
1157 this->UpdatePalette(dc2
, _local_palette
.first_dirty
, _local_palette
.count_dirty
);
1160 case Blitter::PALETTE_ANIMATION_BLITTER
: {
1161 blitter
->PaletteAnimate(_local_palette
);
1165 case Blitter::PALETTE_ANIMATION_NONE
:
1171 _local_palette
.count_dirty
= 0;
1174 BitBlt(dc
, 0, 0, this->width
, this->height
, dc2
, 0, 0, SRCCOPY
);
1175 SelectPalette(dc
, old_palette
, TRUE
);
1176 SelectObject(dc2
, old_bmp
);
1179 ReleaseDC(this->main_wnd
, dc
);
1181 this->dirty_rect
= {};
1185 /* Keep this function here..
1186 * It allows you to redraw the screen from within the MSVC debugger */
1187 /* static */ int VideoDriver_Win32GDI::RedrawScreenDebug()
1191 VideoDriver_Win32GDI
*drv
= static_cast<VideoDriver_Win32GDI
*>(VideoDriver::GetInstance());
1193 _screen
.dst_ptr
= drv
->GetVideoPointer();
1206 #include "../3rdparty/opengl/glext.h"
1207 #include "../3rdparty/opengl/wglext.h"
1210 #ifndef PFD_SUPPORT_COMPOSITION
1211 # define PFD_SUPPORT_COMPOSITION 0x00008000
1214 static PFNWGLCREATECONTEXTATTRIBSARBPROC _wglCreateContextAttribsARB
= nullptr;
1215 static PFNWGLSWAPINTERVALEXTPROC _wglSwapIntervalEXT
= nullptr;
1216 static bool _hasWGLARBCreateContextProfile
= false; ///< Is WGL_ARB_create_context_profile supported?
1218 /** Platform-specific callback to get an OpenGL function pointer. */
1219 static OGLProc
GetOGLProcAddressCallback(const char *proc
)
1221 OGLProc ret
= reinterpret_cast<OGLProc
>(wglGetProcAddress(proc
));
1222 if (ret
== nullptr) {
1223 /* Non-extension GL function? Try normal loading. */
1224 ret
= reinterpret_cast<OGLProc
>(GetProcAddress(GetModuleHandle(L
"opengl32"), proc
));
1230 * Set the pixel format of a window-
1231 * @param dc Device context to set the pixel format of.
1232 * @param fullscreen Should the pixel format be used for fullscreen drawing?
1233 * @return nullptr on success, error message otherwise.
1235 static const char *SelectPixelFormat(HDC dc
, bool fullscreen
)
1237 PIXELFORMATDESCRIPTOR pfd
= {
1238 sizeof(PIXELFORMATDESCRIPTOR
), // Size of this struct.
1239 1, // Version of this struct.
1240 PFD_DRAW_TO_WINDOW
| // Require window support.
1241 PFD_SUPPORT_OPENGL
| // Require OpenGL support.
1242 PFD_DOUBLEBUFFER
| // Use double buffering.
1244 PFD_TYPE_RGBA
, // Request RGBA format.
1245 24, // 24 bpp (excluding alpha).
1246 0, 0, 0, 0, 0, 0, 0, 0, // Colour bits and shift ignored.
1247 0, 0, 0, 0, 0, // No accumulation buffer.
1248 0, 0, // No depth/stencil buffer.
1249 0, // No aux buffers.
1250 PFD_MAIN_PLANE
, // Main layer.
1251 0, 0, 0, 0 // Ignored/reserved.
1254 if (IsWindowsVistaOrGreater()) pfd
.dwFlags
|= PFD_SUPPORT_COMPOSITION
; // Make OpenTTD compatible with Aero.
1256 /* Choose a suitable pixel format. */
1257 int format
= ChoosePixelFormat(dc
, &pfd
);
1258 if (format
== 0) return "No suitable pixel format found";
1259 if (!SetPixelFormat(dc
, format
, &pfd
)) return "Can't set pixel format";
1264 /** Bind all WGL extension functions we need. */
1265 static void LoadWGLExtensions()
1267 /* Querying the supported WGL extensions and loading the matching
1268 * functions requires a valid context, even for the extensions
1269 * regarding context creation. To get around this, we create
1270 * a dummy window with a dummy context. The extension functions
1271 * remain valid even after this context is destroyed. */
1272 HWND wnd
= CreateWindow(_T("STATIC"), _T("dummy"), WS_OVERLAPPEDWINDOW
, 0, 0, 0, 0, nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
1273 HDC dc
= GetDC(wnd
);
1275 /* Set pixel format of the window. */
1276 if (SelectPixelFormat(dc
, false) == nullptr) {
1277 /* Create rendering context. */
1278 HGLRC rc
= wglCreateContext(dc
);
1279 if (rc
!= nullptr) {
1280 wglMakeCurrent(dc
, rc
);
1283 /* GCC doesn't understand the expected usage of wglGetProcAddress(). */
1284 #pragma GCC diagnostic push
1285 #pragma GCC diagnostic ignored "-Wcast-function-type"
1286 #endif /* __MINGW32__ */
1288 /* Get list of WGL extensions. */
1289 PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB
= (PFNWGLGETEXTENSIONSSTRINGARBPROC
)wglGetProcAddress("wglGetExtensionsStringARB");
1290 if (wglGetExtensionsStringARB
!= nullptr) {
1291 const char *wgl_exts
= wglGetExtensionsStringARB(dc
);
1292 /* Bind supported functions. */
1293 if (FindStringInExtensionList(wgl_exts
, "WGL_ARB_create_context") != nullptr) {
1294 _wglCreateContextAttribsARB
= (PFNWGLCREATECONTEXTATTRIBSARBPROC
)wglGetProcAddress("wglCreateContextAttribsARB");
1296 _hasWGLARBCreateContextProfile
= FindStringInExtensionList(wgl_exts
, "WGL_ARB_create_context_profile") != nullptr;
1297 if (FindStringInExtensionList(wgl_exts
, "WGL_EXT_swap_control") != nullptr) {
1298 _wglSwapIntervalEXT
= (PFNWGLSWAPINTERVALEXTPROC
)wglGetProcAddress("wglSwapIntervalEXT");
1303 #pragma GCC diagnostic pop
1305 wglMakeCurrent(nullptr, nullptr);
1306 wglDeleteContext(rc
);
1314 static FVideoDriver_Win32OpenGL iFVideoDriver_Win32OpenGL
;
1316 const char *VideoDriver_Win32OpenGL::Start(const StringList
¶m
)
1318 if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) return "Only real blitters supported";
1320 Dimension old_res
= _cur_resolution
; // Save current screen resolution in case of errors, as MakeWindow invalidates it.
1322 LoadWGLExtensions();
1325 this->MakeWindow(_fullscreen
);
1327 /* Create and initialize OpenGL context. */
1328 const char *err
= this->AllocateContext();
1329 if (err
!= nullptr) {
1331 _cur_resolution
= old_res
;
1335 this->driver_info
= GetName();
1336 this->driver_info
+= " (";
1337 this->driver_info
+= OpenGLBackend::Get()->GetDriverName();
1338 this->driver_info
+= ")";
1340 this->ClientSizeChanged(this->width
, this->height
, true);
1341 /* We should have a valid screen buffer now. If not, something went wrong and we should abort. */
1342 if (_screen
.dst_ptr
== nullptr) {
1344 _cur_resolution
= old_res
;
1345 return "Can't get pointer to screen buffer";
1347 /* Main loop expects to start with the buffer unmapped. */
1348 this->ReleaseVideoPointer();
1350 MarkWholeScreenDirty();
1352 this->is_game_threaded
= !GetDriverParamBool(param
, "no_threads") && !GetDriverParamBool(param
, "no_thread");
1357 void VideoDriver_Win32OpenGL::Stop()
1359 this->DestroyContext();
1360 this->VideoDriver_Win32Base::Stop();
1363 void VideoDriver_Win32OpenGL::DestroyContext()
1365 OpenGLBackend::Destroy();
1367 wglMakeCurrent(nullptr, nullptr);
1368 if (this->gl_rc
!= nullptr) {
1369 wglDeleteContext(this->gl_rc
);
1370 this->gl_rc
= nullptr;
1372 if (this->dc
!= nullptr) {
1373 ReleaseDC(this->main_wnd
, this->dc
);
1378 void VideoDriver_Win32OpenGL::ToggleVsync(bool vsync
)
1380 if (_wglSwapIntervalEXT
!= nullptr) {
1381 _wglSwapIntervalEXT(vsync
);
1383 Debug(driver
, 0, "OpenGL: Vsync requested, but not supported by driver");
1387 const char *VideoDriver_Win32OpenGL::AllocateContext()
1389 this->dc
= GetDC(this->main_wnd
);
1391 const char *err
= SelectPixelFormat(this->dc
, this->fullscreen
);
1392 if (err
!= nullptr) return err
;
1396 /* Create OpenGL device context. Try to get an 3.2+ context if possible. */
1397 if (_wglCreateContextAttribsARB
!= nullptr) {
1398 /* Try for OpenGL 4.5 first. */
1400 WGL_CONTEXT_MAJOR_VERSION_ARB
, 4,
1401 WGL_CONTEXT_MINOR_VERSION_ARB
, 5,
1402 WGL_CONTEXT_FLAGS_ARB
, _debug_driver_level
>= 8 ? WGL_CONTEXT_DEBUG_BIT_ARB
: 0,
1403 _hasWGLARBCreateContextProfile
? WGL_CONTEXT_PROFILE_MASK_ARB
: 0, WGL_CONTEXT_CORE_PROFILE_BIT_ARB
, // Terminate list if WGL_ARB_create_context_profile isn't supported.
1406 rc
= _wglCreateContextAttribsARB(this->dc
, nullptr, attribs
);
1408 if (rc
== nullptr) {
1409 /* Try again for a 3.2 context. */
1412 rc
= _wglCreateContextAttribsARB(this->dc
, nullptr, attribs
);
1416 if (rc
== nullptr) {
1417 /* Old OpenGL or old driver, let's hope for the best. */
1418 rc
= wglCreateContext(this->dc
);
1419 if (rc
== nullptr) return "Can't create OpenGL context";
1421 if (!wglMakeCurrent(this->dc
, rc
)) return "Can't active GL context";
1423 this->ToggleVsync(_video_vsync
);
1426 return OpenGLBackend::Create(&GetOGLProcAddressCallback
, this->GetScreenSize());
1429 bool VideoDriver_Win32OpenGL::ToggleFullscreen(bool full_screen
)
1431 if (_screen
.dst_ptr
!= nullptr) this->ReleaseVideoPointer();
1432 this->DestroyContext();
1433 bool res
= this->VideoDriver_Win32Base::ToggleFullscreen(full_screen
);
1434 res
&= this->AllocateContext() == nullptr;
1435 this->ClientSizeChanged(this->width
, this->height
, true);
1439 bool VideoDriver_Win32OpenGL::AfterBlitterChange()
1441 assert(BlitterFactory::GetCurrentBlitter()->GetScreenDepth() != 0);
1442 this->ClientSizeChanged(this->width
, this->height
, true);
1446 void VideoDriver_Win32OpenGL::PopulateSystemSprites()
1448 OpenGLBackend::Get()->PopulateCursorCache();
1451 void VideoDriver_Win32OpenGL::ClearSystemSprites()
1453 OpenGLBackend::Get()->ClearCursorCache();
1456 bool VideoDriver_Win32OpenGL::AllocateBackingStore(int w
, int h
, bool force
)
1458 if (!force
&& w
== _screen
.width
&& h
== _screen
.height
) return false;
1460 this->width
= w
= std::max(w
, 64);
1461 this->height
= h
= std::max(h
, 64);
1463 if (this->gl_rc
== nullptr) return false;
1465 if (_screen
.dst_ptr
!= nullptr) this->ReleaseVideoPointer();
1467 this->dirty_rect
= {};
1468 bool res
= OpenGLBackend::Get()->Resize(w
, h
, force
);
1469 SwapBuffers(this->dc
);
1470 _screen
.dst_ptr
= this->GetVideoPointer();
1475 void *VideoDriver_Win32OpenGL::GetVideoPointer()
1477 if (BlitterFactory::GetCurrentBlitter()->NeedsAnimationBuffer()) {
1478 this->anim_buffer
= OpenGLBackend::Get()->GetAnimBuffer();
1480 return OpenGLBackend::Get()->GetVideoBuffer();
1483 void VideoDriver_Win32OpenGL::ReleaseVideoPointer()
1485 if (this->anim_buffer
!= nullptr) OpenGLBackend::Get()->ReleaseAnimBuffer(this->dirty_rect
);
1486 OpenGLBackend::Get()->ReleaseVideoBuffer(this->dirty_rect
);
1487 this->dirty_rect
= {};
1488 _screen
.dst_ptr
= nullptr;
1489 this->anim_buffer
= nullptr;
1492 void VideoDriver_Win32OpenGL::Paint()
1494 PerformanceMeasurer
framerate(PFE_VIDEO
);
1496 if (_local_palette
.count_dirty
!= 0) {
1497 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1499 /* Always push a changed palette to OpenGL. */
1500 OpenGLBackend::Get()->UpdatePalette(_local_palette
.palette
, _local_palette
.first_dirty
, _local_palette
.count_dirty
);
1501 if (blitter
->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_BLITTER
) {
1502 blitter
->PaletteAnimate(_local_palette
);
1505 _local_palette
.count_dirty
= 0;
1508 OpenGLBackend::Get()->Paint();
1509 OpenGLBackend::Get()->DrawMouseCursor();
1511 SwapBuffers(this->dc
);
1514 #endif /* WITH_OPENGL */