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 "../error_func.h"
13 #include "../gfx_func.h"
14 #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"
25 #include "../library_loader.h"
29 #include <versionhelpers.h>
31 #include "../safeguards.h"
33 /* Missing define in MinGW headers. */
34 #ifndef MAPVK_VK_TO_CHAR
35 #define MAPVK_VK_TO_CHAR (2)
39 #define PM_QS_INPUT 0x20000
43 #define WM_DPICHANGED 0x02E0
46 bool _window_maximize
;
47 static Dimension _bck_resolution
;
50 static Palette _local_palette
; ///< Current palette to use for drawing.
52 bool VideoDriver_Win32Base::ClaimMousePointer()
54 MyShowCursor(false, true);
58 struct Win32VkMapping
{
64 #define AS(x, z) {x, 1, z}
65 #define AM(x, y, z, w) {x, y - x + 1, z}
67 static const Win32VkMapping _vk_mapping
[] = {
68 /* Pageup stuff + up/down */
69 AM(VK_PRIOR
, VK_DOWN
, WKC_PAGEUP
, WKC_DOWN
),
70 /* Map letters & digits */
71 AM('A', 'Z', 'A', 'Z'),
72 AM('0', '9', '0', '9'),
74 AS(VK_ESCAPE
, WKC_ESC
),
75 AS(VK_PAUSE
, WKC_PAUSE
),
76 AS(VK_BACK
, WKC_BACKSPACE
),
77 AM(VK_INSERT
, VK_DELETE
, WKC_INSERT
, WKC_DELETE
),
79 AS(VK_SPACE
, WKC_SPACE
),
80 AS(VK_RETURN
, WKC_RETURN
),
84 AM(VK_F1
, VK_F12
, WKC_F1
, WKC_F12
),
87 AM(VK_NUMPAD0
, VK_NUMPAD9
, '0', '9'),
88 AS(VK_DIVIDE
, WKC_NUM_DIV
),
89 AS(VK_MULTIPLY
, WKC_NUM_MUL
),
90 AS(VK_SUBTRACT
, WKC_NUM_MINUS
),
91 AS(VK_ADD
, WKC_NUM_PLUS
),
92 AS(VK_DECIMAL
, WKC_NUM_DECIMAL
),
94 /* Other non-letter keys */
96 AS(0xBA, WKC_SEMICOLON
),
98 AS(0xDB, WKC_L_BRACKET
),
99 AS(0xDC, WKC_BACKSLASH
),
100 AS(0xDD, WKC_R_BRACKET
),
102 AS(0xDE, WKC_SINGLEQUOTE
),
108 static uint
MapWindowsKey(uint sym
)
112 for (const auto &map
: _vk_mapping
) {
113 if (IsInsideBS(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_t 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
);
218 /* For windowed mode, center on the workspace of the primary display. */
219 if (!this->fullscreen
) {
221 mi
.cbSize
= sizeof(mi
);
222 GetMonitorInfo(MonitorFromWindow(0, MONITOR_DEFAULTTOPRIMARY
), &mi
);
224 x
= (mi
.rcWork
.right
- mi
.rcWork
.left
- w
) / 2;
225 y
= (mi
.rcWork
.bottom
- mi
.rcWork
.top
- h
) / 2;
228 std::string caption
= VideoDriver::GetCaption();
229 this->main_wnd
= CreateWindow(L
"OTTD", OTTD2FS(caption
).c_str(), style
, x
, y
, w
, h
, 0, 0, GetModuleHandle(nullptr), this);
230 if (this->main_wnd
== nullptr) UserError("CreateWindow failed");
231 ShowWindow(this->main_wnd
, showstyle
);
235 BlitterFactory::GetCurrentBlitter()->PostResize();
241 /** Forward key presses to the window system. */
242 static LRESULT
HandleCharMsg(uint keycode
, char32_t charcode
)
244 static char32_t prev_char
= 0;
246 /* Did we get a lead surrogate? If yes, store and exit. */
247 if (Utf16IsLeadSurrogate(charcode
)) {
248 if (prev_char
!= 0) Debug(driver
, 1, "Got two UTF-16 lead surrogates, dropping the first one");
249 prev_char
= charcode
;
253 /* Stored lead surrogate and incoming trail surrogate? Combine and forward to input handling. */
254 if (prev_char
!= 0) {
255 if (Utf16IsTrailSurrogate(charcode
)) {
256 charcode
= Utf16DecodeSurrogate(prev_char
, charcode
);
258 Debug(driver
, 1, "Got an UTF-16 lead surrogate without a trail surrogate, dropping the lead surrogate");
263 HandleKeypress(keycode
, charcode
);
268 /** Should we draw the composition string ourself, i.e is this a normal IME? */
269 static bool DrawIMECompositionString()
271 return (_imm_props
& IME_PROP_AT_CARET
) && !(_imm_props
& IME_PROP_SPECIAL_UI
);
274 /** Set position of the composition window to the caret position. */
275 static void SetCompositionPos(HWND hwnd
)
277 HIMC hIMC
= ImmGetContext(hwnd
);
278 if (hIMC
!= nullptr) {
280 cf
.dwStyle
= CFS_POINT
;
282 if (EditBoxInGlobalFocus()) {
283 /* Get caret position. */
284 Point pt
= _focused_window
->GetCaretPosition();
285 cf
.ptCurrentPos
.x
= _focused_window
->left
+ pt
.x
;
286 cf
.ptCurrentPos
.y
= _focused_window
->top
+ pt
.y
;
288 cf
.ptCurrentPos
.x
= 0;
289 cf
.ptCurrentPos
.y
= 0;
291 ImmSetCompositionWindow(hIMC
, &cf
);
293 ImmReleaseContext(hwnd
, hIMC
);
296 /** Set the position of the candidate window. */
297 static void SetCandidatePos(HWND hwnd
)
299 HIMC hIMC
= ImmGetContext(hwnd
);
300 if (hIMC
!= nullptr) {
303 cf
.dwStyle
= CFS_EXCLUDE
;
305 if (EditBoxInGlobalFocus()) {
306 Point pt
= _focused_window
->GetCaretPosition();
307 cf
.ptCurrentPos
.x
= _focused_window
->left
+ pt
.x
;
308 cf
.ptCurrentPos
.y
= _focused_window
->top
+ pt
.y
;
309 if (_focused_window
->window_class
== WC_CONSOLE
) {
310 cf
.rcArea
.left
= _focused_window
->left
;
311 cf
.rcArea
.top
= _focused_window
->top
;
312 cf
.rcArea
.right
= _focused_window
->left
+ _focused_window
->width
;
313 cf
.rcArea
.bottom
= _focused_window
->top
+ _focused_window
->height
;
315 cf
.rcArea
.left
= _focused_window
->left
+ _focused_window
->nested_focus
->pos_x
;
316 cf
.rcArea
.top
= _focused_window
->top
+ _focused_window
->nested_focus
->pos_y
;
317 cf
.rcArea
.right
= cf
.rcArea
.left
+ _focused_window
->nested_focus
->current_x
;
318 cf
.rcArea
.bottom
= cf
.rcArea
.top
+ _focused_window
->nested_focus
->current_y
;
321 cf
.ptCurrentPos
.x
= 0;
322 cf
.ptCurrentPos
.y
= 0;
323 SetRectEmpty(&cf
.rcArea
);
325 ImmSetCandidateWindow(hIMC
, &cf
);
327 ImmReleaseContext(hwnd
, hIMC
);
330 /** Handle WM_IME_COMPOSITION messages. */
331 static LRESULT
HandleIMEComposition(HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
333 HIMC hIMC
= ImmGetContext(hwnd
);
335 if (hIMC
!= nullptr) {
336 if (lParam
& GCS_RESULTSTR
) {
337 /* Read result string from the IME. */
338 LONG len
= ImmGetCompositionString(hIMC
, GCS_RESULTSTR
, nullptr, 0); // Length is always in bytes, even in UNICODE build.
339 std::wstring
str(len
+ 1, L
'\0');
340 len
= ImmGetCompositionString(hIMC
, GCS_RESULTSTR
, str
.data(), len
);
341 str
[len
/ sizeof(wchar_t)] = L
'\0';
343 /* Transmit text to windowing system. */
345 HandleTextInput(nullptr, true); // Clear marked string.
346 HandleTextInput(FS2OTTD(str
).c_str());
348 SetCompositionPos(hwnd
);
350 /* Don't pass the result string on to the default window proc. */
351 lParam
&= ~(GCS_RESULTSTR
| GCS_RESULTCLAUSE
| GCS_RESULTREADCLAUSE
| GCS_RESULTREADSTR
);
354 if ((lParam
& GCS_COMPSTR
) && DrawIMECompositionString()) {
355 /* Read composition string from the IME. */
356 LONG len
= ImmGetCompositionString(hIMC
, GCS_COMPSTR
, nullptr, 0); // Length is always in bytes, even in UNICODE build.
357 std::wstring
str(len
+ 1, L
'\0');
358 len
= ImmGetCompositionString(hIMC
, GCS_COMPSTR
, str
.data(), len
);
359 str
[len
/ sizeof(wchar_t)] = L
'\0';
362 static char utf8_buf
[1024];
363 convert_from_fs(str
.c_str(), utf8_buf
);
365 /* Convert caret position from bytes in the input string to a position in the UTF-8 encoded string. */
366 LONG caret_bytes
= ImmGetCompositionString(hIMC
, GCS_CURSORPOS
, nullptr, 0);
367 const char *caret
= utf8_buf
;
368 for (const wchar_t *c
= str
.c_str(); *c
!= '\0' && *caret
!= '\0' && caret_bytes
> 0; c
++, caret_bytes
--) {
369 /* Skip DBCS lead bytes or leading surrogates. */
370 if (Utf16IsLeadSurrogate(*c
)) {
377 HandleTextInput(utf8_buf
, true, caret
);
379 HandleTextInput(nullptr, true);
382 lParam
&= ~(GCS_COMPSTR
| GCS_COMPATTR
| GCS_COMPCLAUSE
| GCS_CURSORPOS
| GCS_DELTASTART
);
385 ImmReleaseContext(hwnd
, hIMC
);
387 return lParam
!= 0 ? DefWindowProc(hwnd
, WM_IME_COMPOSITION
, wParam
, lParam
) : 0;
390 /** Clear the current composition string. */
391 static void CancelIMEComposition(HWND hwnd
)
393 HIMC hIMC
= ImmGetContext(hwnd
);
394 if (hIMC
!= nullptr) ImmNotifyIME(hIMC
, NI_COMPOSITIONSTR
, CPS_CANCEL
, 0);
395 ImmReleaseContext(hwnd
, hIMC
);
396 /* Clear any marked string from the current edit box. */
397 HandleTextInput(nullptr, true);
400 LRESULT CALLBACK
WndProcGdi(HWND hwnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
402 static uint32_t keycode
= 0;
403 static bool console
= false;
405 VideoDriver_Win32Base
*video_driver
= (VideoDriver_Win32Base
*)GetWindowLongPtr(hwnd
, GWLP_USERDATA
);
409 SetWindowLongPtr(hwnd
, GWLP_USERDATA
, (LONG_PTR
)((LPCREATESTRUCT
)lParam
)->lpCreateParams
);
410 _cursor
.in_window
= false; // Win32 has mouse tracking.
411 SetCompositionPos(hwnd
);
412 _imm_props
= ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY
);
417 GetUpdateRect(hwnd
, &r
, FALSE
);
418 video_driver
->MakeDirty(r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
);
420 ValidateRect(hwnd
, nullptr);
424 case WM_PALETTECHANGED
:
425 if ((HWND
)wParam
== hwnd
) return 0;
428 case WM_QUERYNEWPALETTE
:
429 video_driver
->PaletteChanged(hwnd
);
433 HandleExitGameRequest();
437 if (_window_maximize
) _cur_resolution
= _bck_resolution
;
442 _left_button_down
= true;
448 _left_button_down
= false;
449 _left_button_clicked
= false;
455 _right_button_down
= true;
456 _right_button_clicked
= true;
462 _right_button_down
= false;
468 _cursor
.in_window
= false;
470 if (!_left_button_down
&& !_right_button_down
) MyShowCursor(true);
474 int x
= (int16_t)LOWORD(lParam
);
475 int y
= (int16_t)HIWORD(lParam
);
477 /* If the mouse was not in the window and it has moved it means it has
478 * come into the window, so start drawing the mouse. Also start
479 * tracking the mouse for exiting the window */
480 if (!_cursor
.in_window
) {
481 _cursor
.in_window
= true;
483 tme
.cbSize
= sizeof(tme
);
484 tme
.dwFlags
= TME_LEAVE
;
485 tme
.hwndTrack
= hwnd
;
487 TrackMouseEvent(&tme
);
490 if (_cursor
.fix_at
) {
491 /* Get all queued mouse events now in case we have to warp the cursor. In the
492 * end, we only care about the current mouse position and not bygone events. */
494 while (PeekMessage(&m
, hwnd
, WM_MOUSEMOVE
, WM_MOUSEMOVE
, PM_REMOVE
| PM_NOYIELD
| PM_QS_INPUT
)) {
495 x
= (int16_t)LOWORD(m
.lParam
);
496 y
= (int16_t)HIWORD(m
.lParam
);
500 if (_cursor
.UpdateCursorPosition(x
, y
)) {
502 pt
.x
= _cursor
.pos
.x
;
503 pt
.y
= _cursor
.pos
.y
;
504 ClientToScreen(hwnd
, &pt
);
505 SetCursorPos(pt
.x
, pt
.y
);
512 case WM_INPUTLANGCHANGE
:
513 _imm_props
= ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY
);
516 case WM_IME_SETCONTEXT
:
517 /* Don't show the composition window if we draw the string ourself. */
518 if (DrawIMECompositionString()) lParam
&= ~ISC_SHOWUICOMPOSITIONWINDOW
;
521 case WM_IME_STARTCOMPOSITION
:
522 SetCompositionPos(hwnd
);
523 if (DrawIMECompositionString()) return 0;
526 case WM_IME_COMPOSITION
:
527 return HandleIMEComposition(hwnd
, wParam
, lParam
);
529 case WM_IME_ENDCOMPOSITION
:
530 /* Clear any pending composition string. */
531 HandleTextInput(nullptr, true);
532 if (DrawIMECompositionString()) return 0;
536 if (wParam
== IMN_OPENCANDIDATE
) SetCandidatePos(hwnd
);
540 console
= GB(lParam
, 16, 8) == 41;
544 uint scancode
= GB(lParam
, 16, 8);
545 uint charcode
= wParam
;
547 /* If the console key is a dead-key, we need to press it twice to get a WM_CHAR message.
548 * But we then get two WM_CHAR messages, so ignore the first one */
549 if (console
&& scancode
== 41) {
554 /* IMEs and other input methods sometimes send a WM_CHAR without a WM_KEYDOWN,
555 * clear the keycode so a previous WM_KEYDOWN doesn't become 'stuck'. */
556 uint cur_keycode
= keycode
;
559 return HandleCharMsg(cur_keycode
, charcode
);
563 /* No matter the keyboard layout, we will map the '~' to the console. */
564 uint scancode
= GB(lParam
, 16, 8);
565 keycode
= scancode
== 41 ? (uint
)WKC_BACKQUOTE
: MapWindowsKey(wParam
);
567 uint charcode
= MapVirtualKey(wParam
, MAPVK_VK_TO_CHAR
);
569 /* No character translation? */
571 HandleKeypress(keycode
, 0);
575 /* If an edit box is in focus, wait for the corresponding WM_CHAR message. */
576 if (!EditBoxInGlobalFocus()) {
577 /* Is the console key a dead key? If yes, ignore the first key down event. */
578 if (HasBit(charcode
, 31) && !console
) {
579 if (scancode
== 41) {
586 /* IMEs and other input methods sometimes send a WM_CHAR without a WM_KEYDOWN,
587 * clear the keycode so a previous WM_KEYDOWN doesn't become 'stuck'. */
588 uint cur_keycode
= keycode
;
591 return HandleCharMsg(cur_keycode
, LOWORD(charcode
));
597 case WM_SYSKEYDOWN
: // user presses F10 or Alt, both activating the title-menu
600 case 'F': // Full Screen on ALT + ENTER/F
601 ToggleFullScreen(!video_driver
->fullscreen
);
604 case VK_MENU
: // Just ALT
605 return 0; // do nothing
607 case VK_F10
: // F10, ignore activation of menu
608 HandleKeypress(MapWindowsKey(wParam
), 0);
611 default: // ALT in combination with something else
612 HandleKeypress(MapWindowsKey(wParam
), 0);
618 if (wParam
!= SIZE_MINIMIZED
) {
619 /* Set maximized flag when we maximize (obviously), but also when we
620 * switched to fullscreen from a maximized state */
621 _window_maximize
= (wParam
== SIZE_MAXIMIZED
|| (_window_maximize
&& _fullscreen
));
622 if (_window_maximize
|| _fullscreen
) _bck_resolution
= _cur_resolution
;
623 video_driver
->ClientSizeChanged(LOWORD(lParam
), HIWORD(lParam
));
628 RECT
*r
= (RECT
*)lParam
;
632 SetRect(&r2
, 0, 0, 0, 0);
633 AdjustWindowRect(&r2
, GetWindowLong(hwnd
, GWL_STYLE
), FALSE
);
635 w
= r
->right
- r
->left
- (r2
.right
- r2
.left
);
636 h
= r
->bottom
- r
->top
- (r2
.bottom
- r2
.top
);
639 SetRect(&r2
, 0, 0, w
, h
);
641 AdjustWindowRect(&r2
, GetWindowLong(hwnd
, GWL_STYLE
), FALSE
);
642 w
= r2
.right
- r2
.left
;
643 h
= r2
.bottom
- r2
.top
;
647 r
->bottom
= r
->top
+ h
;
650 case WMSZ_BOTTOMLEFT
:
651 r
->bottom
= r
->top
+ h
;
652 r
->left
= r
->right
- w
;
655 case WMSZ_BOTTOMRIGHT
:
656 r
->bottom
= r
->top
+ h
;
657 r
->right
= r
->left
+ w
;
661 r
->left
= r
->right
- w
;
665 r
->right
= r
->left
+ w
;
669 r
->top
= r
->bottom
- h
;
673 r
->top
= r
->bottom
- h
;
674 r
->left
= r
->right
- w
;
678 r
->top
= r
->bottom
- h
;
679 r
->right
= r
->left
+ w
;
685 case WM_DPICHANGED
: {
686 auto did_adjust
= AdjustGUIZoom(true);
688 /* Resize the window to match the new DPI setting. */
689 RECT
*prcNewWindow
= (RECT
*)lParam
;
694 prcNewWindow
->right
- prcNewWindow
->left
,
695 prcNewWindow
->bottom
- prcNewWindow
->top
,
696 SWP_NOZORDER
| SWP_NOACTIVATE
);
698 if (did_adjust
) ReInitAllWindows(true);
703 /* needed for wheel */
704 #if !defined(WM_MOUSEWHEEL)
705 # define WM_MOUSEWHEEL 0x020A
706 #endif /* WM_MOUSEWHEEL */
707 #if !defined(GET_WHEEL_DELTA_WPARAM)
708 # define GET_WHEEL_DELTA_WPARAM(wparam) ((short)HIWORD(wparam))
709 #endif /* GET_WHEEL_DELTA_WPARAM */
711 case WM_MOUSEWHEEL
: {
712 int delta
= GET_WHEEL_DELTA_WPARAM(wParam
);
716 } else if (delta
> 0) {
724 video_driver
->has_focus
= true;
725 SetCompositionPos(hwnd
);
729 video_driver
->has_focus
= false;
733 /* Don't do anything if we are closing openttd */
734 if (_exit_game
) break;
736 bool active
= (LOWORD(wParam
) != WA_INACTIVE
);
737 bool minimized
= (HIWORD(wParam
) != 0);
738 if (video_driver
->fullscreen
) {
739 if (active
&& minimized
) {
740 /* Restore the game window */
741 Dimension d
= _bck_resolution
; // Save current non-fullscreen window size as it will be overwritten by ShowWindow.
742 ShowWindow(hwnd
, SW_RESTORE
);
744 video_driver
->MakeWindow(true);
745 } else if (!active
&& !minimized
) {
746 /* Minimise the window and restore desktop */
747 ShowWindow(hwnd
, SW_MINIMIZE
);
748 ChangeDisplaySettings(nullptr, 0);
755 return DefWindowProc(hwnd
, msg
, wParam
, lParam
);
758 static void RegisterWndClass()
760 static bool registered
= false;
762 if (registered
) return;
764 HINSTANCE hinst
= GetModuleHandle(nullptr);
771 LoadIcon(hinst
, MAKEINTRESOURCE(100)),
772 LoadCursor(nullptr, IDC_ARROW
),
779 if (!RegisterClass(&wnd
)) UserError("RegisterClass failed");
782 static const Dimension default_resolutions
[] = {
796 static void FindResolutions(uint8_t bpp
)
798 _resolutions
.clear();
801 for (uint i
= 0; EnumDisplaySettings(nullptr, i
, &dm
) != 0; i
++) {
802 if (dm
.dmBitsPerPel
!= bpp
|| dm
.dmPelsWidth
< 640 || dm
.dmPelsHeight
< 480) continue;
803 if (std::find(_resolutions
.begin(), _resolutions
.end(), Dimension(dm
.dmPelsWidth
, dm
.dmPelsHeight
)) != _resolutions
.end()) continue;
804 _resolutions
.emplace_back(dm
.dmPelsWidth
, dm
.dmPelsHeight
);
807 /* We have found no resolutions, show the default list */
808 if (_resolutions
.empty()) {
809 _resolutions
.assign(std::begin(default_resolutions
), std::end(default_resolutions
));
815 void VideoDriver_Win32Base::Initialize()
817 this->UpdateAutoResolution();
820 FindResolutions(this->GetFullscreenBpp());
822 /* fullscreen uses those */
823 this->width
= this->width_org
= _cur_resolution
.width
;
824 this->height
= this->height_org
= _cur_resolution
.height
;
826 Debug(driver
, 2, "Resolution for display: {}x{}", _cur_resolution
.width
, _cur_resolution
.height
);
829 void VideoDriver_Win32Base::Stop()
831 DestroyWindow(this->main_wnd
);
833 if (this->fullscreen
) ChangeDisplaySettings(nullptr, 0);
836 void VideoDriver_Win32Base::MakeDirty(int left
, int top
, int width
, int height
)
838 Rect r
= {left
, top
, left
+ width
, top
+ height
};
839 this->dirty_rect
= BoundingRect(this->dirty_rect
, r
);
842 void VideoDriver_Win32Base::CheckPaletteAnim()
844 if (!CopyPalette(_local_palette
)) return;
845 this->MakeDirty(0, 0, _screen
.width
, _screen
.height
);
848 void VideoDriver_Win32Base::InputLoop()
850 bool old_ctrl_pressed
= _ctrl_pressed
;
852 _ctrl_pressed
= this->has_focus
&& GetAsyncKeyState(VK_CONTROL
) < 0;
853 _shift_pressed
= this->has_focus
&& GetAsyncKeyState(VK_SHIFT
) < 0;
855 /* Speedup when pressing tab, except when using ALT+TAB
856 * to switch to another application. */
857 this->fast_forward_key_pressed
= this->has_focus
&& GetAsyncKeyState(VK_TAB
) < 0 && GetAsyncKeyState(VK_MENU
) >= 0;
859 /* Determine which directional keys are down. */
860 if (this->has_focus
) {
862 (GetAsyncKeyState(VK_LEFT
) < 0 ? 1 : 0) +
863 (GetAsyncKeyState(VK_UP
) < 0 ? 2 : 0) +
864 (GetAsyncKeyState(VK_RIGHT
) < 0 ? 4 : 0) +
865 (GetAsyncKeyState(VK_DOWN
) < 0 ? 8 : 0);
870 if (old_ctrl_pressed
!= _ctrl_pressed
) HandleCtrlChanged();
873 bool VideoDriver_Win32Base::PollEvent()
877 if (!PeekMessage(&mesg
, nullptr, 0, 0, PM_REMOVE
)) return false;
879 /* Convert key messages to char messages if we want text input. */
880 if (EditBoxInGlobalFocus()) TranslateMessage(&mesg
);
881 DispatchMessage(&mesg
);
886 void VideoDriver_Win32Base::MainLoop()
888 this->StartGameThread();
891 if (_exit_game
) break;
894 this->SleepTillNextTick();
897 this->StopGameThread();
900 void VideoDriver_Win32Base::ClientSizeChanged(int w
, int h
, bool force
)
902 /* Allocate backing store of the new size. */
903 if (this->AllocateBackingStore(w
, h
, force
)) {
904 CopyPalette(_local_palette
, true);
906 BlitterFactory::GetCurrentBlitter()->PostResize();
912 bool VideoDriver_Win32Base::ChangeResolution(int w
, int h
)
914 if (_window_maximize
) ShowWindow(this->main_wnd
, SW_SHOWNORMAL
);
916 this->width
= this->width_org
= w
;
917 this->height
= this->height_org
= h
;
919 return this->MakeWindow(_fullscreen
); // _wnd.fullscreen screws up ingame resolution switching
922 bool VideoDriver_Win32Base::ToggleFullscreen(bool full_screen
)
924 bool res
= this->MakeWindow(full_screen
);
926 InvalidateWindowClassesData(WC_GAME_OPTIONS
, 3);
930 void VideoDriver_Win32Base::EditBoxLostFocus()
932 CancelIMEComposition(this->main_wnd
);
933 SetCompositionPos(this->main_wnd
);
934 SetCandidatePos(this->main_wnd
);
937 static BOOL CALLBACK
MonitorEnumProc(HMONITOR hMonitor
, HDC
, LPRECT
, LPARAM data
)
939 auto &list
= *reinterpret_cast<std::vector
<int>*>(data
);
941 MONITORINFOEX monitorInfo
= {};
942 monitorInfo
.cbSize
= sizeof(MONITORINFOEX
);
943 GetMonitorInfo(hMonitor
, &monitorInfo
);
945 DEVMODE devMode
= {};
946 devMode
.dmSize
= sizeof(DEVMODE
);
947 devMode
.dmDriverExtra
= 0;
948 EnumDisplaySettings(monitorInfo
.szDevice
, ENUM_CURRENT_SETTINGS
, &devMode
);
950 if (devMode
.dmDisplayFrequency
!= 0) list
.push_back(devMode
.dmDisplayFrequency
);
954 std::vector
<int> VideoDriver_Win32Base::GetListOfMonitorRefreshRates()
956 std::vector
<int> rates
= {};
957 EnumDisplayMonitors(nullptr, nullptr, MonitorEnumProc
, reinterpret_cast<LPARAM
>(&rates
));
961 Dimension
VideoDriver_Win32Base::GetScreenSize() const
963 return { static_cast<uint
>(GetSystemMetrics(SM_CXSCREEN
)), static_cast<uint
>(GetSystemMetrics(SM_CYSCREEN
)) };
966 float VideoDriver_Win32Base::GetDPIScale()
968 typedef UINT (WINAPI
*PFNGETDPIFORWINDOW
)(HWND hwnd
);
969 typedef UINT (WINAPI
*PFNGETDPIFORSYSTEM
)(VOID
);
970 typedef HRESULT (WINAPI
*PFNGETDPIFORMONITOR
)(HMONITOR hMonitor
, int dpiType
, UINT
*dpiX
, UINT
*dpiY
);
972 static PFNGETDPIFORWINDOW _GetDpiForWindow
= nullptr;
973 static PFNGETDPIFORSYSTEM _GetDpiForSystem
= nullptr;
974 static PFNGETDPIFORMONITOR _GetDpiForMonitor
= nullptr;
976 static bool init_done
= false;
979 static LibraryLoader
_user32("user32.dll");
980 static LibraryLoader
_shcore("shcore.dll");
981 _GetDpiForWindow
= _user32
.GetFunction("GetDpiForWindow");
982 _GetDpiForSystem
= _user32
.GetFunction("GetDpiForSystem");
983 _GetDpiForMonitor
= _shcore
.GetFunction("GetDpiForMonitor");
988 if (cur_dpi
== 0 && _GetDpiForWindow
!= nullptr && this->main_wnd
!= nullptr) {
989 /* Per window DPI is supported since Windows 10 Ver 1607. */
990 cur_dpi
= _GetDpiForWindow(this->main_wnd
);
992 if (cur_dpi
== 0 && _GetDpiForMonitor
!= nullptr && this->main_wnd
!= nullptr) {
993 /* Per monitor is supported since Windows 8.1. */
995 if (SUCCEEDED(_GetDpiForMonitor(MonitorFromWindow(this->main_wnd
, MONITOR_DEFAULTTOPRIMARY
), 0 /* MDT_EFFECTIVE_DPI */, &dpiX
, &dpiY
))) {
996 cur_dpi
= dpiX
; // X and Y are always identical.
999 if (cur_dpi
== 0 && _GetDpiForSystem
!= nullptr) {
1000 /* Fall back to system DPI. */
1001 cur_dpi
= _GetDpiForSystem();
1004 return cur_dpi
> 0 ? cur_dpi
/ 96.0f
: 1.0f
; // Default Windows DPI value is 96.
1007 bool VideoDriver_Win32Base::LockVideoBuffer()
1009 if (this->buffer_locked
) return false;
1010 this->buffer_locked
= true;
1012 _screen
.dst_ptr
= this->GetVideoPointer();
1013 assert(_screen
.dst_ptr
!= nullptr);
1018 void VideoDriver_Win32Base::UnlockVideoBuffer()
1020 assert(_screen
.dst_ptr
!= nullptr);
1021 if (_screen
.dst_ptr
!= nullptr) {
1022 /* Hand video buffer back to the drawing backend. */
1023 this->ReleaseVideoPointer();
1024 _screen
.dst_ptr
= nullptr;
1027 this->buffer_locked
= false;
1031 static FVideoDriver_Win32GDI iFVideoDriver_Win32GDI
;
1033 std::optional
<std::string_view
> VideoDriver_Win32GDI::Start(const StringList
¶m
)
1035 if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) return "Only real blitters supported";
1039 this->MakePalette();
1040 this->AllocateBackingStore(_cur_resolution
.width
, _cur_resolution
.height
);
1041 this->MakeWindow(_fullscreen
);
1043 MarkWholeScreenDirty();
1045 this->is_game_threaded
= !GetDriverParamBool(param
, "no_threads") && !GetDriverParamBool(param
, "no_thread");
1047 return std::nullopt
;
1050 void VideoDriver_Win32GDI::Stop()
1052 DeleteObject(this->gdi_palette
);
1053 DeleteObject(this->dib_sect
);
1055 this->VideoDriver_Win32Base::Stop();
1058 bool VideoDriver_Win32GDI::AllocateBackingStore(int w
, int h
, bool force
)
1060 uint bpp
= BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
1062 w
= std::max(w
, 64);
1063 h
= std::max(h
, 64);
1065 if (!force
&& w
== _screen
.width
&& h
== _screen
.height
) return false;
1067 BITMAPINFO
*bi
= (BITMAPINFO
*)new char[sizeof(BITMAPINFOHEADER
) + sizeof(RGBQUAD
) * 256]();
1068 bi
->bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
1070 bi
->bmiHeader
.biWidth
= this->width
= w
;
1071 bi
->bmiHeader
.biHeight
= -(this->height
= h
);
1073 bi
->bmiHeader
.biPlanes
= 1;
1074 bi
->bmiHeader
.biBitCount
= bpp
;
1075 bi
->bmiHeader
.biCompression
= BI_RGB
;
1077 if (this->dib_sect
) DeleteObject(this->dib_sect
);
1080 this->dib_sect
= CreateDIBSection(dc
, bi
, DIB_RGB_COLORS
, (VOID
**)&this->buffer_bits
, nullptr, 0);
1081 if (this->dib_sect
== nullptr) {
1083 UserError("CreateDIBSection failed");
1088 _screen
.pitch
= (bpp
== 8) ? Align(w
, 4) : w
;
1090 _screen
.dst_ptr
= this->GetVideoPointer();
1096 bool VideoDriver_Win32GDI::AfterBlitterChange()
1098 assert(BlitterFactory::GetCurrentBlitter()->GetScreenDepth() != 0);
1099 return this->AllocateBackingStore(_screen
.width
, _screen
.height
, true) && this->MakeWindow(_fullscreen
, false);
1102 void VideoDriver_Win32GDI::MakePalette()
1104 CopyPalette(_local_palette
, true);
1106 LOGPALETTE
*pal
= (LOGPALETTE
*)new char[sizeof(LOGPALETTE
) + (256 - 1) * sizeof(PALETTEENTRY
)]();
1108 pal
->palVersion
= 0x300;
1109 pal
->palNumEntries
= 256;
1111 for (uint i
= 0; i
!= 256; i
++) {
1112 pal
->palPalEntry
[i
].peRed
= _local_palette
.palette
[i
].r
;
1113 pal
->palPalEntry
[i
].peGreen
= _local_palette
.palette
[i
].g
;
1114 pal
->palPalEntry
[i
].peBlue
= _local_palette
.palette
[i
].b
;
1115 pal
->palPalEntry
[i
].peFlags
= 0;
1118 this->gdi_palette
= CreatePalette(pal
);
1120 if (this->gdi_palette
== nullptr) UserError("CreatePalette failed!\n");
1123 void VideoDriver_Win32GDI::UpdatePalette(HDC dc
, uint start
, uint count
)
1127 for (uint i
= 0; i
!= count
; i
++) {
1128 rgb
[i
].rgbRed
= _local_palette
.palette
[start
+ i
].r
;
1129 rgb
[i
].rgbGreen
= _local_palette
.palette
[start
+ i
].g
;
1130 rgb
[i
].rgbBlue
= _local_palette
.palette
[start
+ i
].b
;
1131 rgb
[i
].rgbReserved
= 0;
1134 SetDIBColorTable(dc
, start
, count
, rgb
);
1137 void VideoDriver_Win32GDI::PaletteChanged(HWND hWnd
)
1139 HDC hDC
= GetWindowDC(hWnd
);
1140 HPALETTE hOldPalette
= SelectPalette(hDC
, this->gdi_palette
, FALSE
);
1141 UINT nChanged
= RealizePalette(hDC
);
1143 SelectPalette(hDC
, hOldPalette
, TRUE
);
1144 ReleaseDC(hWnd
, hDC
);
1145 if (nChanged
!= 0) this->MakeDirty(0, 0, _screen
.width
, _screen
.height
);
1148 void VideoDriver_Win32GDI::Paint()
1150 PerformanceMeasurer
framerate(PFE_VIDEO
);
1152 if (IsEmptyRect(this->dirty_rect
)) return;
1154 HDC dc
= GetDC(this->main_wnd
);
1155 HDC dc2
= CreateCompatibleDC(dc
);
1157 HBITMAP old_bmp
= (HBITMAP
)SelectObject(dc2
, this->dib_sect
);
1158 HPALETTE old_palette
= SelectPalette(dc
, this->gdi_palette
, FALSE
);
1160 if (_local_palette
.count_dirty
!= 0) {
1161 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1163 switch (blitter
->UsePaletteAnimation()) {
1164 case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND
:
1165 this->UpdatePalette(dc2
, _local_palette
.first_dirty
, _local_palette
.count_dirty
);
1168 case Blitter::PALETTE_ANIMATION_BLITTER
: {
1169 blitter
->PaletteAnimate(_local_palette
);
1173 case Blitter::PALETTE_ANIMATION_NONE
:
1179 _local_palette
.count_dirty
= 0;
1182 BitBlt(dc
, 0, 0, this->width
, this->height
, dc2
, 0, 0, SRCCOPY
);
1183 SelectPalette(dc
, old_palette
, TRUE
);
1184 SelectObject(dc2
, old_bmp
);
1187 ReleaseDC(this->main_wnd
, dc
);
1189 this->dirty_rect
= {};
1193 /* Keep this function here..
1194 * It allows you to redraw the screen from within the MSVC debugger */
1195 /* static */ int VideoDriver_Win32GDI::RedrawScreenDebug()
1199 VideoDriver_Win32GDI
*drv
= static_cast<VideoDriver_Win32GDI
*>(VideoDriver::GetInstance());
1201 _screen
.dst_ptr
= drv
->GetVideoPointer();
1214 #include "../3rdparty/opengl/glext.h"
1215 #include "../3rdparty/opengl/wglext.h"
1218 #ifndef PFD_SUPPORT_COMPOSITION
1219 # define PFD_SUPPORT_COMPOSITION 0x00008000
1222 static PFNWGLCREATECONTEXTATTRIBSARBPROC _wglCreateContextAttribsARB
= nullptr;
1223 static PFNWGLSWAPINTERVALEXTPROC _wglSwapIntervalEXT
= nullptr;
1224 static bool _hasWGLARBCreateContextProfile
= false; ///< Is WGL_ARB_create_context_profile supported?
1226 /** Platform-specific callback to get an OpenGL function pointer. */
1227 static OGLProc
GetOGLProcAddressCallback(const char *proc
)
1229 OGLProc ret
= reinterpret_cast<OGLProc
>(wglGetProcAddress(proc
));
1230 if (ret
== nullptr) {
1231 /* Non-extension GL function? Try normal loading. */
1232 ret
= reinterpret_cast<OGLProc
>(GetProcAddress(GetModuleHandle(L
"opengl32"), proc
));
1238 * Set the pixel format of a window-
1239 * @param dc Device context to set the pixel format of.
1240 * @return std::nullopt on success, error message otherwise.
1242 static std::optional
<std::string_view
> SelectPixelFormat(HDC dc
)
1244 PIXELFORMATDESCRIPTOR pfd
= {
1245 sizeof(PIXELFORMATDESCRIPTOR
), // Size of this struct.
1246 1, // Version of this struct.
1247 PFD_DRAW_TO_WINDOW
| // Require window support.
1248 PFD_SUPPORT_OPENGL
| // Require OpenGL support.
1249 PFD_DOUBLEBUFFER
| // Use double buffering.
1251 PFD_TYPE_RGBA
, // Request RGBA format.
1252 24, // 24 bpp (excluding alpha).
1253 0, 0, 0, 0, 0, 0, 0, 0, // Colour bits and shift ignored.
1254 0, 0, 0, 0, 0, // No accumulation buffer.
1255 0, 0, // No depth/stencil buffer.
1256 0, // No aux buffers.
1257 PFD_MAIN_PLANE
, // Main layer.
1258 0, 0, 0, 0 // Ignored/reserved.
1261 pfd
.dwFlags
|= PFD_SUPPORT_COMPOSITION
; // Make OpenTTD compatible with Aero.
1263 /* Choose a suitable pixel format. */
1264 int format
= ChoosePixelFormat(dc
, &pfd
);
1265 if (format
== 0) return "No suitable pixel format found";
1266 if (!SetPixelFormat(dc
, format
, &pfd
)) return "Can't set pixel format";
1268 return std::nullopt
;
1271 /** Bind all WGL extension functions we need. */
1272 static void LoadWGLExtensions()
1274 /* Querying the supported WGL extensions and loading the matching
1275 * functions requires a valid context, even for the extensions
1276 * regarding context creation. To get around this, we create
1277 * a dummy window with a dummy context. The extension functions
1278 * remain valid even after this context is destroyed. */
1279 HWND wnd
= CreateWindow(L
"STATIC", L
"dummy", WS_OVERLAPPEDWINDOW
, 0, 0, 0, 0, nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
1280 HDC dc
= GetDC(wnd
);
1282 /* Set pixel format of the window. */
1283 if (SelectPixelFormat(dc
) == std::nullopt
) {
1284 /* Create rendering context. */
1285 HGLRC rc
= wglCreateContext(dc
);
1286 if (rc
!= nullptr) {
1287 wglMakeCurrent(dc
, rc
);
1290 /* GCC doesn't understand the expected usage of wglGetProcAddress(). */
1291 #pragma GCC diagnostic push
1292 #pragma GCC diagnostic ignored "-Wcast-function-type"
1293 #endif /* __MINGW32__ */
1295 /* Get list of WGL extensions. */
1296 PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB
= (PFNWGLGETEXTENSIONSSTRINGARBPROC
)wglGetProcAddress("wglGetExtensionsStringARB");
1297 if (wglGetExtensionsStringARB
!= nullptr) {
1298 const char *wgl_exts
= wglGetExtensionsStringARB(dc
);
1299 /* Bind supported functions. */
1300 if (FindStringInExtensionList(wgl_exts
, "WGL_ARB_create_context") != nullptr) {
1301 _wglCreateContextAttribsARB
= (PFNWGLCREATECONTEXTATTRIBSARBPROC
)wglGetProcAddress("wglCreateContextAttribsARB");
1303 _hasWGLARBCreateContextProfile
= FindStringInExtensionList(wgl_exts
, "WGL_ARB_create_context_profile") != nullptr;
1304 if (FindStringInExtensionList(wgl_exts
, "WGL_EXT_swap_control") != nullptr) {
1305 _wglSwapIntervalEXT
= (PFNWGLSWAPINTERVALEXTPROC
)wglGetProcAddress("wglSwapIntervalEXT");
1310 #pragma GCC diagnostic pop
1312 wglMakeCurrent(nullptr, nullptr);
1313 wglDeleteContext(rc
);
1321 static FVideoDriver_Win32OpenGL iFVideoDriver_Win32OpenGL
;
1323 std::optional
<std::string_view
> VideoDriver_Win32OpenGL::Start(const StringList
¶m
)
1325 if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) return "Only real blitters supported";
1327 Dimension old_res
= _cur_resolution
; // Save current screen resolution in case of errors, as MakeWindow invalidates it.
1329 LoadWGLExtensions();
1332 this->MakeWindow(_fullscreen
);
1334 /* Create and initialize OpenGL context. */
1335 auto err
= this->AllocateContext();
1338 _cur_resolution
= old_res
;
1342 this->driver_info
= GetName();
1343 this->driver_info
+= " (";
1344 this->driver_info
+= OpenGLBackend::Get()->GetDriverName();
1345 this->driver_info
+= ")";
1347 this->ClientSizeChanged(this->width
, this->height
, true);
1348 /* We should have a valid screen buffer now. If not, something went wrong and we should abort. */
1349 if (_screen
.dst_ptr
== nullptr) {
1351 _cur_resolution
= old_res
;
1352 return "Can't get pointer to screen buffer";
1354 /* Main loop expects to start with the buffer unmapped. */
1355 this->ReleaseVideoPointer();
1357 MarkWholeScreenDirty();
1359 this->is_game_threaded
= !GetDriverParamBool(param
, "no_threads") && !GetDriverParamBool(param
, "no_thread");
1361 return std::nullopt
;
1364 void VideoDriver_Win32OpenGL::Stop()
1366 this->DestroyContext();
1367 this->VideoDriver_Win32Base::Stop();
1370 void VideoDriver_Win32OpenGL::DestroyContext()
1372 OpenGLBackend::Destroy();
1374 wglMakeCurrent(nullptr, nullptr);
1375 if (this->gl_rc
!= nullptr) {
1376 wglDeleteContext(this->gl_rc
);
1377 this->gl_rc
= nullptr;
1379 if (this->dc
!= nullptr) {
1380 ReleaseDC(this->main_wnd
, this->dc
);
1385 void VideoDriver_Win32OpenGL::ToggleVsync(bool vsync
)
1387 if (_wglSwapIntervalEXT
!= nullptr) {
1388 _wglSwapIntervalEXT(vsync
);
1390 Debug(driver
, 0, "OpenGL: Vsync requested, but not supported by driver");
1394 std::optional
<std::string_view
> VideoDriver_Win32OpenGL::AllocateContext()
1396 this->dc
= GetDC(this->main_wnd
);
1398 auto err
= SelectPixelFormat(this->dc
);
1399 if (err
) return err
;
1403 /* Create OpenGL device context. Try to get an 3.2+ context if possible. */
1404 if (_wglCreateContextAttribsARB
!= nullptr) {
1405 /* Try for OpenGL 4.5 first. */
1407 WGL_CONTEXT_MAJOR_VERSION_ARB
, 4,
1408 WGL_CONTEXT_MINOR_VERSION_ARB
, 5,
1409 WGL_CONTEXT_FLAGS_ARB
, _debug_driver_level
>= 8 ? WGL_CONTEXT_DEBUG_BIT_ARB
: 0,
1410 _hasWGLARBCreateContextProfile
? WGL_CONTEXT_PROFILE_MASK_ARB
: 0, WGL_CONTEXT_CORE_PROFILE_BIT_ARB
, // Terminate list if WGL_ARB_create_context_profile isn't supported.
1413 rc
= _wglCreateContextAttribsARB(this->dc
, nullptr, attribs
);
1415 if (rc
== nullptr) {
1416 /* Try again for a 3.2 context. */
1419 rc
= _wglCreateContextAttribsARB(this->dc
, nullptr, attribs
);
1423 if (rc
== nullptr) {
1424 /* Old OpenGL or old driver, let's hope for the best. */
1425 rc
= wglCreateContext(this->dc
);
1426 if (rc
== nullptr) return "Can't create OpenGL context";
1428 if (!wglMakeCurrent(this->dc
, rc
)) return "Can't active GL context";
1430 this->ToggleVsync(_video_vsync
);
1433 return OpenGLBackend::Create(&GetOGLProcAddressCallback
, this->GetScreenSize());
1436 bool VideoDriver_Win32OpenGL::ToggleFullscreen(bool full_screen
)
1438 if (_screen
.dst_ptr
!= nullptr) this->ReleaseVideoPointer();
1439 this->DestroyContext();
1440 bool res
= this->VideoDriver_Win32Base::ToggleFullscreen(full_screen
);
1441 res
&= this->AllocateContext() == std::nullopt
;
1442 this->ClientSizeChanged(this->width
, this->height
, true);
1446 bool VideoDriver_Win32OpenGL::AfterBlitterChange()
1448 assert(BlitterFactory::GetCurrentBlitter()->GetScreenDepth() != 0);
1449 this->ClientSizeChanged(this->width
, this->height
, true);
1453 void VideoDriver_Win32OpenGL::PopulateSystemSprites()
1455 OpenGLBackend::Get()->PopulateCursorCache();
1458 void VideoDriver_Win32OpenGL::ClearSystemSprites()
1460 OpenGLBackend::Get()->ClearCursorCache();
1463 bool VideoDriver_Win32OpenGL::AllocateBackingStore(int w
, int h
, bool force
)
1465 if (!force
&& w
== _screen
.width
&& h
== _screen
.height
) return false;
1467 this->width
= w
= std::max(w
, 64);
1468 this->height
= h
= std::max(h
, 64);
1470 if (this->gl_rc
== nullptr) return false;
1472 if (_screen
.dst_ptr
!= nullptr) this->ReleaseVideoPointer();
1474 this->dirty_rect
= {};
1475 bool res
= OpenGLBackend::Get()->Resize(w
, h
, force
);
1476 SwapBuffers(this->dc
);
1477 _screen
.dst_ptr
= this->GetVideoPointer();
1482 void *VideoDriver_Win32OpenGL::GetVideoPointer()
1484 if (BlitterFactory::GetCurrentBlitter()->NeedsAnimationBuffer()) {
1485 this->anim_buffer
= OpenGLBackend::Get()->GetAnimBuffer();
1487 return OpenGLBackend::Get()->GetVideoBuffer();
1490 void VideoDriver_Win32OpenGL::ReleaseVideoPointer()
1492 if (this->anim_buffer
!= nullptr) OpenGLBackend::Get()->ReleaseAnimBuffer(this->dirty_rect
);
1493 OpenGLBackend::Get()->ReleaseVideoBuffer(this->dirty_rect
);
1494 this->dirty_rect
= {};
1495 _screen
.dst_ptr
= nullptr;
1496 this->anim_buffer
= nullptr;
1499 void VideoDriver_Win32OpenGL::Paint()
1501 PerformanceMeasurer
framerate(PFE_VIDEO
);
1503 if (_local_palette
.count_dirty
!= 0) {
1504 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1506 /* Always push a changed palette to OpenGL. */
1507 OpenGLBackend::Get()->UpdatePalette(_local_palette
.palette
, _local_palette
.first_dirty
, _local_palette
.count_dirty
);
1508 if (blitter
->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_BLITTER
) {
1509 blitter
->PaletteAnimate(_local_palette
);
1512 _local_palette
.count_dirty
= 0;
1515 OpenGLBackend::Get()->Paint();
1516 OpenGLBackend::Get()->DrawMouseCursor();
1518 SwapBuffers(this->dc
);
1521 #endif /* WITH_OPENGL */