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, 0, z}
65 #define AM(x, y, z, w) {x, y - x, 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
)
110 const Win32VkMapping
*map
;
113 for (map
= _vk_mapping
; map
!= endof(_vk_mapping
); ++map
) {
114 if ((uint
)(sym
- map
->vk_from
) <= map
->vk_count
) {
115 key
= sym
- map
->vk_from
+ map
->map_to
;
120 if (GetAsyncKeyState(VK_SHIFT
) < 0) key
|= WKC_SHIFT
;
121 if (GetAsyncKeyState(VK_CONTROL
) < 0) key
|= WKC_CTRL
;
122 if (GetAsyncKeyState(VK_MENU
) < 0) key
|= WKC_ALT
;
126 /** Colour depth to use for fullscreen display modes. */
127 uint8_t VideoDriver_Win32Base::GetFullscreenBpp()
129 /* Check modes for the relevant fullscreen bpp */
130 return _support8bpp
!= S8BPP_HARDWARE
? 32 : BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
134 * Instantiate a new window.
135 * @param full_screen Whether to make a full screen window or not.
136 * @param resize Whether to change window size.
137 * @return True if the window could be created.
139 bool VideoDriver_Win32Base::MakeWindow(bool full_screen
, bool resize
)
141 /* full_screen is whether the new window should be fullscreen,
142 * _wnd.fullscreen is whether the current window is. */
143 _fullscreen
= full_screen
;
145 /* recreate window? */
146 if ((full_screen
!= this->fullscreen
) && this->main_wnd
) {
147 DestroyWindow(this->main_wnd
);
154 memset(&settings
, 0, sizeof(settings
));
155 settings
.dmSize
= sizeof(settings
);
160 settings
.dmBitsPerPel
= this->GetFullscreenBpp();
161 settings
.dmPelsWidth
= this->width_org
;
162 settings
.dmPelsHeight
= this->height_org
;
164 /* Check for 8 bpp support. */
165 if (settings
.dmBitsPerPel
== 8 && ChangeDisplaySettings(&settings
, CDS_FULLSCREEN
| CDS_TEST
) != DISP_CHANGE_SUCCESSFUL
) {
166 settings
.dmBitsPerPel
= 32;
169 /* Test fullscreen with current resolution, if it fails use desktop resolution. */
170 if (ChangeDisplaySettings(&settings
, CDS_FULLSCREEN
| CDS_TEST
) != DISP_CHANGE_SUCCESSFUL
) {
172 GetWindowRect(GetDesktopWindow(), &r
);
173 /* Guard against recursion. If we already failed here once, just fall through to
174 * the next ChangeDisplaySettings call which will fail and error out appropriately. */
175 if ((int)settings
.dmPelsWidth
!= r
.right
- r
.left
|| (int)settings
.dmPelsHeight
!= r
.bottom
- r
.top
) {
176 return this->ChangeResolution(r
.right
- r
.left
, r
.bottom
- r
.top
);
180 if (ChangeDisplaySettings(&settings
, CDS_FULLSCREEN
) != DISP_CHANGE_SUCCESSFUL
) {
181 this->MakeWindow(false, resize
); // don't care about the result
182 return false; // the request failed
184 } else if (this->fullscreen
) {
185 /* restore display? */
186 ChangeDisplaySettings(nullptr, 0);
187 /* restore the resolution */
188 this->width
= _bck_resolution
.width
;
189 this->height
= _bck_resolution
.height
;
194 DWORD style
, showstyle
;
197 showstyle
= SW_SHOWNORMAL
;
198 this->fullscreen
= full_screen
;
199 if (this->fullscreen
) {
201 SetRect(&r
, 0, 0, this->width_org
, this->height_org
);
203 style
= WS_OVERLAPPEDWINDOW
;
204 /* On window creation, check if we were in maximize mode before */
205 if (_window_maximize
) showstyle
= SW_SHOWMAXIMIZED
;
206 SetRect(&r
, 0, 0, this->width
, this->height
);
209 AdjustWindowRect(&r
, style
, FALSE
);
210 w
= r
.right
- r
.left
;
211 h
= r
.bottom
- r
.top
;
213 if (this->main_wnd
!= nullptr) {
214 if (!_window_maximize
&& resize
) SetWindowPos(this->main_wnd
, 0, 0, 0, w
, h
, SWP_NOACTIVATE
| SWP_NOOWNERZORDER
| SWP_NOZORDER
| SWP_NOMOVE
);
219 /* For windowed mode, center on the workspace of the primary display. */
220 if (!this->fullscreen
) {
222 mi
.cbSize
= sizeof(mi
);
223 GetMonitorInfo(MonitorFromWindow(0, MONITOR_DEFAULTTOPRIMARY
), &mi
);
225 x
= (mi
.rcWork
.right
- mi
.rcWork
.left
- w
) / 2;
226 y
= (mi
.rcWork
.bottom
- mi
.rcWork
.top
- h
) / 2;
229 std::string caption
= VideoDriver::GetCaption();
230 this->main_wnd
= CreateWindow(L
"OTTD", OTTD2FS(caption
).c_str(), style
, x
, y
, w
, h
, 0, 0, GetModuleHandle(nullptr), this);
231 if (this->main_wnd
== nullptr) UserError("CreateWindow failed");
232 ShowWindow(this->main_wnd
, showstyle
);
236 BlitterFactory::GetCurrentBlitter()->PostResize();
242 /** Forward key presses to the window system. */
243 static LRESULT
HandleCharMsg(uint keycode
, char32_t charcode
)
245 static char32_t prev_char
= 0;
247 /* Did we get a lead surrogate? If yes, store and exit. */
248 if (Utf16IsLeadSurrogate(charcode
)) {
249 if (prev_char
!= 0) Debug(driver
, 1, "Got two UTF-16 lead surrogates, dropping the first one");
250 prev_char
= charcode
;
254 /* Stored lead surrogate and incoming trail surrogate? Combine and forward to input handling. */
255 if (prev_char
!= 0) {
256 if (Utf16IsTrailSurrogate(charcode
)) {
257 charcode
= Utf16DecodeSurrogate(prev_char
, charcode
);
259 Debug(driver
, 1, "Got an UTF-16 lead surrogate without a trail surrogate, dropping the lead surrogate");
264 HandleKeypress(keycode
, charcode
);
269 /** Should we draw the composition string ourself, i.e is this a normal IME? */
270 static bool DrawIMECompositionString()
272 return (_imm_props
& IME_PROP_AT_CARET
) && !(_imm_props
& IME_PROP_SPECIAL_UI
);
275 /** Set position of the composition window to the caret position. */
276 static void SetCompositionPos(HWND hwnd
)
278 HIMC hIMC
= ImmGetContext(hwnd
);
279 if (hIMC
!= nullptr) {
281 cf
.dwStyle
= CFS_POINT
;
283 if (EditBoxInGlobalFocus()) {
284 /* Get caret position. */
285 Point pt
= _focused_window
->GetCaretPosition();
286 cf
.ptCurrentPos
.x
= _focused_window
->left
+ pt
.x
;
287 cf
.ptCurrentPos
.y
= _focused_window
->top
+ pt
.y
;
289 cf
.ptCurrentPos
.x
= 0;
290 cf
.ptCurrentPos
.y
= 0;
292 ImmSetCompositionWindow(hIMC
, &cf
);
294 ImmReleaseContext(hwnd
, hIMC
);
297 /** Set the position of the candidate window. */
298 static void SetCandidatePos(HWND hwnd
)
300 HIMC hIMC
= ImmGetContext(hwnd
);
301 if (hIMC
!= nullptr) {
304 cf
.dwStyle
= CFS_EXCLUDE
;
306 if (EditBoxInGlobalFocus()) {
307 Point pt
= _focused_window
->GetCaretPosition();
308 cf
.ptCurrentPos
.x
= _focused_window
->left
+ pt
.x
;
309 cf
.ptCurrentPos
.y
= _focused_window
->top
+ pt
.y
;
310 if (_focused_window
->window_class
== WC_CONSOLE
) {
311 cf
.rcArea
.left
= _focused_window
->left
;
312 cf
.rcArea
.top
= _focused_window
->top
;
313 cf
.rcArea
.right
= _focused_window
->left
+ _focused_window
->width
;
314 cf
.rcArea
.bottom
= _focused_window
->top
+ _focused_window
->height
;
316 cf
.rcArea
.left
= _focused_window
->left
+ _focused_window
->nested_focus
->pos_x
;
317 cf
.rcArea
.top
= _focused_window
->top
+ _focused_window
->nested_focus
->pos_y
;
318 cf
.rcArea
.right
= cf
.rcArea
.left
+ _focused_window
->nested_focus
->current_x
;
319 cf
.rcArea
.bottom
= cf
.rcArea
.top
+ _focused_window
->nested_focus
->current_y
;
322 cf
.ptCurrentPos
.x
= 0;
323 cf
.ptCurrentPos
.y
= 0;
324 SetRectEmpty(&cf
.rcArea
);
326 ImmSetCandidateWindow(hIMC
, &cf
);
328 ImmReleaseContext(hwnd
, hIMC
);
331 /** Handle WM_IME_COMPOSITION messages. */
332 static LRESULT
HandleIMEComposition(HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
334 HIMC hIMC
= ImmGetContext(hwnd
);
336 if (hIMC
!= nullptr) {
337 if (lParam
& GCS_RESULTSTR
) {
338 /* Read result string from the IME. */
339 LONG len
= ImmGetCompositionString(hIMC
, GCS_RESULTSTR
, nullptr, 0); // Length is always in bytes, even in UNICODE build.
340 std::wstring
str(len
+ 1, L
'\0');
341 len
= ImmGetCompositionString(hIMC
, GCS_RESULTSTR
, str
.data(), len
);
342 str
[len
/ sizeof(wchar_t)] = L
'\0';
344 /* Transmit text to windowing system. */
346 HandleTextInput(nullptr, true); // Clear marked string.
347 HandleTextInput(FS2OTTD(str
).c_str());
349 SetCompositionPos(hwnd
);
351 /* Don't pass the result string on to the default window proc. */
352 lParam
&= ~(GCS_RESULTSTR
| GCS_RESULTCLAUSE
| GCS_RESULTREADCLAUSE
| GCS_RESULTREADSTR
);
355 if ((lParam
& GCS_COMPSTR
) && DrawIMECompositionString()) {
356 /* Read composition string from the IME. */
357 LONG len
= ImmGetCompositionString(hIMC
, GCS_COMPSTR
, nullptr, 0); // Length is always in bytes, even in UNICODE build.
358 std::wstring
str(len
+ 1, L
'\0');
359 len
= ImmGetCompositionString(hIMC
, GCS_COMPSTR
, str
.data(), len
);
360 str
[len
/ sizeof(wchar_t)] = L
'\0';
363 static char utf8_buf
[1024];
364 convert_from_fs(str
.c_str(), utf8_buf
, lengthof(utf8_buf
));
366 /* Convert caret position from bytes in the input string to a position in the UTF-8 encoded string. */
367 LONG caret_bytes
= ImmGetCompositionString(hIMC
, GCS_CURSORPOS
, nullptr, 0);
368 const char *caret
= utf8_buf
;
369 for (const wchar_t *c
= str
.c_str(); *c
!= '\0' && *caret
!= '\0' && caret_bytes
> 0; c
++, caret_bytes
--) {
370 /* Skip DBCS lead bytes or leading surrogates. */
371 if (Utf16IsLeadSurrogate(*c
)) {
378 HandleTextInput(utf8_buf
, true, caret
);
380 HandleTextInput(nullptr, true);
383 lParam
&= ~(GCS_COMPSTR
| GCS_COMPATTR
| GCS_COMPCLAUSE
| GCS_CURSORPOS
| GCS_DELTASTART
);
386 ImmReleaseContext(hwnd
, hIMC
);
388 return lParam
!= 0 ? DefWindowProc(hwnd
, WM_IME_COMPOSITION
, wParam
, lParam
) : 0;
391 /** Clear the current composition string. */
392 static void CancelIMEComposition(HWND hwnd
)
394 HIMC hIMC
= ImmGetContext(hwnd
);
395 if (hIMC
!= nullptr) ImmNotifyIME(hIMC
, NI_COMPOSITIONSTR
, CPS_CANCEL
, 0);
396 ImmReleaseContext(hwnd
, hIMC
);
397 /* Clear any marked string from the current edit box. */
398 HandleTextInput(nullptr, true);
401 LRESULT CALLBACK
WndProcGdi(HWND hwnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
403 static uint32_t keycode
= 0;
404 static bool console
= false;
406 VideoDriver_Win32Base
*video_driver
= (VideoDriver_Win32Base
*)GetWindowLongPtr(hwnd
, GWLP_USERDATA
);
410 SetWindowLongPtr(hwnd
, GWLP_USERDATA
, (LONG_PTR
)((LPCREATESTRUCT
)lParam
)->lpCreateParams
);
411 _cursor
.in_window
= false; // Win32 has mouse tracking.
412 SetCompositionPos(hwnd
);
413 _imm_props
= ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY
);
418 GetUpdateRect(hwnd
, &r
, FALSE
);
419 video_driver
->MakeDirty(r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
);
421 ValidateRect(hwnd
, nullptr);
425 case WM_PALETTECHANGED
:
426 if ((HWND
)wParam
== hwnd
) return 0;
429 case WM_QUERYNEWPALETTE
:
430 video_driver
->PaletteChanged(hwnd
);
434 HandleExitGameRequest();
438 if (_window_maximize
) _cur_resolution
= _bck_resolution
;
443 _left_button_down
= true;
449 _left_button_down
= false;
450 _left_button_clicked
= false;
456 _right_button_down
= true;
457 _right_button_clicked
= true;
463 _right_button_down
= false;
469 _cursor
.in_window
= false;
471 if (!_left_button_down
&& !_right_button_down
) MyShowCursor(true);
475 int x
= (int16_t)LOWORD(lParam
);
476 int y
= (int16_t)HIWORD(lParam
);
478 /* If the mouse was not in the window and it has moved it means it has
479 * come into the window, so start drawing the mouse. Also start
480 * tracking the mouse for exiting the window */
481 if (!_cursor
.in_window
) {
482 _cursor
.in_window
= true;
484 tme
.cbSize
= sizeof(tme
);
485 tme
.dwFlags
= TME_LEAVE
;
486 tme
.hwndTrack
= hwnd
;
488 TrackMouseEvent(&tme
);
491 if (_cursor
.fix_at
) {
492 /* Get all queued mouse events now in case we have to warp the cursor. In the
493 * end, we only care about the current mouse position and not bygone events. */
495 while (PeekMessage(&m
, hwnd
, WM_MOUSEMOVE
, WM_MOUSEMOVE
, PM_REMOVE
| PM_NOYIELD
| PM_QS_INPUT
)) {
496 x
= (int16_t)LOWORD(m
.lParam
);
497 y
= (int16_t)HIWORD(m
.lParam
);
501 if (_cursor
.UpdateCursorPosition(x
, y
)) {
503 pt
.x
= _cursor
.pos
.x
;
504 pt
.y
= _cursor
.pos
.y
;
505 ClientToScreen(hwnd
, &pt
);
506 SetCursorPos(pt
.x
, pt
.y
);
513 case WM_INPUTLANGCHANGE
:
514 _imm_props
= ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY
);
517 case WM_IME_SETCONTEXT
:
518 /* Don't show the composition window if we draw the string ourself. */
519 if (DrawIMECompositionString()) lParam
&= ~ISC_SHOWUICOMPOSITIONWINDOW
;
522 case WM_IME_STARTCOMPOSITION
:
523 SetCompositionPos(hwnd
);
524 if (DrawIMECompositionString()) return 0;
527 case WM_IME_COMPOSITION
:
528 return HandleIMEComposition(hwnd
, wParam
, lParam
);
530 case WM_IME_ENDCOMPOSITION
:
531 /* Clear any pending composition string. */
532 HandleTextInput(nullptr, true);
533 if (DrawIMECompositionString()) return 0;
537 if (wParam
== IMN_OPENCANDIDATE
) SetCandidatePos(hwnd
);
541 console
= GB(lParam
, 16, 8) == 41;
545 uint scancode
= GB(lParam
, 16, 8);
546 uint charcode
= wParam
;
548 /* If the console key is a dead-key, we need to press it twice to get a WM_CHAR message.
549 * But we then get two WM_CHAR messages, so ignore the first one */
550 if (console
&& scancode
== 41) {
555 /* IMEs and other input methods sometimes send a WM_CHAR without a WM_KEYDOWN,
556 * clear the keycode so a previous WM_KEYDOWN doesn't become 'stuck'. */
557 uint cur_keycode
= keycode
;
560 return HandleCharMsg(cur_keycode
, charcode
);
564 /* No matter the keyboard layout, we will map the '~' to the console. */
565 uint scancode
= GB(lParam
, 16, 8);
566 keycode
= scancode
== 41 ? (uint
)WKC_BACKQUOTE
: MapWindowsKey(wParam
);
568 uint charcode
= MapVirtualKey(wParam
, MAPVK_VK_TO_CHAR
);
570 /* No character translation? */
572 HandleKeypress(keycode
, 0);
576 /* If an edit box is in focus, wait for the corresponding WM_CHAR message. */
577 if (!EditBoxInGlobalFocus()) {
578 /* Is the console key a dead key? If yes, ignore the first key down event. */
579 if (HasBit(charcode
, 31) && !console
) {
580 if (scancode
== 41) {
587 /* IMEs and other input methods sometimes send a WM_CHAR without a WM_KEYDOWN,
588 * clear the keycode so a previous WM_KEYDOWN doesn't become 'stuck'. */
589 uint cur_keycode
= keycode
;
592 return HandleCharMsg(cur_keycode
, LOWORD(charcode
));
598 case WM_SYSKEYDOWN
: // user presses F10 or Alt, both activating the title-menu
601 case 'F': // Full Screen on ALT + ENTER/F
602 ToggleFullScreen(!video_driver
->fullscreen
);
605 case VK_MENU
: // Just ALT
606 return 0; // do nothing
608 case VK_F10
: // F10, ignore activation of menu
609 HandleKeypress(MapWindowsKey(wParam
), 0);
612 default: // ALT in combination with something else
613 HandleKeypress(MapWindowsKey(wParam
), 0);
619 if (wParam
!= SIZE_MINIMIZED
) {
620 /* Set maximized flag when we maximize (obviously), but also when we
621 * switched to fullscreen from a maximized state */
622 _window_maximize
= (wParam
== SIZE_MAXIMIZED
|| (_window_maximize
&& _fullscreen
));
623 if (_window_maximize
|| _fullscreen
) _bck_resolution
= _cur_resolution
;
624 video_driver
->ClientSizeChanged(LOWORD(lParam
), HIWORD(lParam
));
629 RECT
*r
= (RECT
*)lParam
;
633 SetRect(&r2
, 0, 0, 0, 0);
634 AdjustWindowRect(&r2
, GetWindowLong(hwnd
, GWL_STYLE
), FALSE
);
636 w
= r
->right
- r
->left
- (r2
.right
- r2
.left
);
637 h
= r
->bottom
- r
->top
- (r2
.bottom
- r2
.top
);
640 SetRect(&r2
, 0, 0, w
, h
);
642 AdjustWindowRect(&r2
, GetWindowLong(hwnd
, GWL_STYLE
), FALSE
);
643 w
= r2
.right
- r2
.left
;
644 h
= r2
.bottom
- r2
.top
;
648 r
->bottom
= r
->top
+ h
;
651 case WMSZ_BOTTOMLEFT
:
652 r
->bottom
= r
->top
+ h
;
653 r
->left
= r
->right
- w
;
656 case WMSZ_BOTTOMRIGHT
:
657 r
->bottom
= r
->top
+ h
;
658 r
->right
= r
->left
+ w
;
662 r
->left
= r
->right
- w
;
666 r
->right
= r
->left
+ w
;
670 r
->top
= r
->bottom
- h
;
674 r
->top
= r
->bottom
- h
;
675 r
->left
= r
->right
- w
;
679 r
->top
= r
->bottom
- h
;
680 r
->right
= r
->left
+ w
;
686 case WM_DPICHANGED
: {
687 auto did_adjust
= AdjustGUIZoom(true);
689 /* Resize the window to match the new DPI setting. */
690 RECT
*prcNewWindow
= (RECT
*)lParam
;
695 prcNewWindow
->right
- prcNewWindow
->left
,
696 prcNewWindow
->bottom
- prcNewWindow
->top
,
697 SWP_NOZORDER
| SWP_NOACTIVATE
);
699 if (did_adjust
) ReInitAllWindows(true);
704 /* needed for wheel */
705 #if !defined(WM_MOUSEWHEEL)
706 # define WM_MOUSEWHEEL 0x020A
707 #endif /* WM_MOUSEWHEEL */
708 #if !defined(GET_WHEEL_DELTA_WPARAM)
709 # define GET_WHEEL_DELTA_WPARAM(wparam) ((short)HIWORD(wparam))
710 #endif /* GET_WHEEL_DELTA_WPARAM */
712 case WM_MOUSEWHEEL
: {
713 int delta
= GET_WHEEL_DELTA_WPARAM(wParam
);
717 } else if (delta
> 0) {
725 video_driver
->has_focus
= true;
726 SetCompositionPos(hwnd
);
730 video_driver
->has_focus
= false;
734 /* Don't do anything if we are closing openttd */
735 if (_exit_game
) break;
737 bool active
= (LOWORD(wParam
) != WA_INACTIVE
);
738 bool minimized
= (HIWORD(wParam
) != 0);
739 if (video_driver
->fullscreen
) {
740 if (active
&& minimized
) {
741 /* Restore the game window */
742 Dimension d
= _bck_resolution
; // Save current non-fullscreen window size as it will be overwritten by ShowWindow.
743 ShowWindow(hwnd
, SW_RESTORE
);
745 video_driver
->MakeWindow(true);
746 } else if (!active
&& !minimized
) {
747 /* Minimise the window and restore desktop */
748 ShowWindow(hwnd
, SW_MINIMIZE
);
749 ChangeDisplaySettings(nullptr, 0);
756 return DefWindowProc(hwnd
, msg
, wParam
, lParam
);
759 static void RegisterWndClass()
761 static bool registered
= false;
763 if (registered
) return;
765 HINSTANCE hinst
= GetModuleHandle(nullptr);
772 LoadIcon(hinst
, MAKEINTRESOURCE(100)),
773 LoadCursor(nullptr, IDC_ARROW
),
780 if (!RegisterClass(&wnd
)) UserError("RegisterClass failed");
783 static const Dimension default_resolutions
[] = {
797 static void FindResolutions(uint8_t bpp
)
799 _resolutions
.clear();
802 for (uint i
= 0; EnumDisplaySettings(nullptr, i
, &dm
) != 0; i
++) {
803 if (dm
.dmBitsPerPel
!= bpp
|| dm
.dmPelsWidth
< 640 || dm
.dmPelsHeight
< 480) continue;
804 if (std::find(_resolutions
.begin(), _resolutions
.end(), Dimension(dm
.dmPelsWidth
, dm
.dmPelsHeight
)) != _resolutions
.end()) continue;
805 _resolutions
.emplace_back(dm
.dmPelsWidth
, dm
.dmPelsHeight
);
808 /* We have found no resolutions, show the default list */
809 if (_resolutions
.empty()) {
810 _resolutions
.assign(std::begin(default_resolutions
), std::end(default_resolutions
));
816 void VideoDriver_Win32Base::Initialize()
818 this->UpdateAutoResolution();
821 FindResolutions(this->GetFullscreenBpp());
823 /* fullscreen uses those */
824 this->width
= this->width_org
= _cur_resolution
.width
;
825 this->height
= this->height_org
= _cur_resolution
.height
;
827 Debug(driver
, 2, "Resolution for display: {}x{}", _cur_resolution
.width
, _cur_resolution
.height
);
830 void VideoDriver_Win32Base::Stop()
832 DestroyWindow(this->main_wnd
);
834 if (this->fullscreen
) ChangeDisplaySettings(nullptr, 0);
837 void VideoDriver_Win32Base::MakeDirty(int left
, int top
, int width
, int height
)
839 Rect r
= {left
, top
, left
+ width
, top
+ height
};
840 this->dirty_rect
= BoundingRect(this->dirty_rect
, r
);
843 void VideoDriver_Win32Base::CheckPaletteAnim()
845 if (!CopyPalette(_local_palette
)) return;
846 this->MakeDirty(0, 0, _screen
.width
, _screen
.height
);
849 void VideoDriver_Win32Base::InputLoop()
851 bool old_ctrl_pressed
= _ctrl_pressed
;
853 _ctrl_pressed
= this->has_focus
&& GetAsyncKeyState(VK_CONTROL
) < 0;
854 _shift_pressed
= this->has_focus
&& GetAsyncKeyState(VK_SHIFT
) < 0;
856 /* Speedup when pressing tab, except when using ALT+TAB
857 * to switch to another application. */
858 this->fast_forward_key_pressed
= this->has_focus
&& GetAsyncKeyState(VK_TAB
) < 0 && GetAsyncKeyState(VK_MENU
) >= 0;
860 /* Determine which directional keys are down. */
861 if (this->has_focus
) {
863 (GetAsyncKeyState(VK_LEFT
) < 0 ? 1 : 0) +
864 (GetAsyncKeyState(VK_UP
) < 0 ? 2 : 0) +
865 (GetAsyncKeyState(VK_RIGHT
) < 0 ? 4 : 0) +
866 (GetAsyncKeyState(VK_DOWN
) < 0 ? 8 : 0);
871 if (old_ctrl_pressed
!= _ctrl_pressed
) HandleCtrlChanged();
874 bool VideoDriver_Win32Base::PollEvent()
878 if (!PeekMessage(&mesg
, nullptr, 0, 0, PM_REMOVE
)) return false;
880 /* Convert key messages to char messages if we want text input. */
881 if (EditBoxInGlobalFocus()) TranslateMessage(&mesg
);
882 DispatchMessage(&mesg
);
887 void VideoDriver_Win32Base::MainLoop()
889 this->StartGameThread();
892 if (_exit_game
) break;
895 this->SleepTillNextTick();
898 this->StopGameThread();
901 void VideoDriver_Win32Base::ClientSizeChanged(int w
, int h
, bool force
)
903 /* Allocate backing store of the new size. */
904 if (this->AllocateBackingStore(w
, h
, force
)) {
905 CopyPalette(_local_palette
, true);
907 BlitterFactory::GetCurrentBlitter()->PostResize();
913 bool VideoDriver_Win32Base::ChangeResolution(int w
, int h
)
915 if (_window_maximize
) ShowWindow(this->main_wnd
, SW_SHOWNORMAL
);
917 this->width
= this->width_org
= w
;
918 this->height
= this->height_org
= h
;
920 return this->MakeWindow(_fullscreen
); // _wnd.fullscreen screws up ingame resolution switching
923 bool VideoDriver_Win32Base::ToggleFullscreen(bool full_screen
)
925 bool res
= this->MakeWindow(full_screen
);
927 InvalidateWindowClassesData(WC_GAME_OPTIONS
, 3);
931 void VideoDriver_Win32Base::EditBoxLostFocus()
933 CancelIMEComposition(this->main_wnd
);
934 SetCompositionPos(this->main_wnd
);
935 SetCandidatePos(this->main_wnd
);
938 static BOOL CALLBACK
MonitorEnumProc(HMONITOR hMonitor
, HDC
, LPRECT
, LPARAM data
)
940 auto &list
= *reinterpret_cast<std::vector
<int>*>(data
);
942 MONITORINFOEX monitorInfo
= {};
943 monitorInfo
.cbSize
= sizeof(MONITORINFOEX
);
944 GetMonitorInfo(hMonitor
, &monitorInfo
);
946 DEVMODE devMode
= {};
947 devMode
.dmSize
= sizeof(DEVMODE
);
948 devMode
.dmDriverExtra
= 0;
949 EnumDisplaySettings(monitorInfo
.szDevice
, ENUM_CURRENT_SETTINGS
, &devMode
);
951 if (devMode
.dmDisplayFrequency
!= 0) list
.push_back(devMode
.dmDisplayFrequency
);
955 std::vector
<int> VideoDriver_Win32Base::GetListOfMonitorRefreshRates()
957 std::vector
<int> rates
= {};
958 EnumDisplayMonitors(nullptr, nullptr, MonitorEnumProc
, reinterpret_cast<LPARAM
>(&rates
));
962 Dimension
VideoDriver_Win32Base::GetScreenSize() const
964 return { static_cast<uint
>(GetSystemMetrics(SM_CXSCREEN
)), static_cast<uint
>(GetSystemMetrics(SM_CYSCREEN
)) };
967 float VideoDriver_Win32Base::GetDPIScale()
969 typedef UINT (WINAPI
*PFNGETDPIFORWINDOW
)(HWND hwnd
);
970 typedef UINT (WINAPI
*PFNGETDPIFORSYSTEM
)(VOID
);
971 typedef HRESULT (WINAPI
*PFNGETDPIFORMONITOR
)(HMONITOR hMonitor
, int dpiType
, UINT
*dpiX
, UINT
*dpiY
);
973 static PFNGETDPIFORWINDOW _GetDpiForWindow
= nullptr;
974 static PFNGETDPIFORSYSTEM _GetDpiForSystem
= nullptr;
975 static PFNGETDPIFORMONITOR _GetDpiForMonitor
= nullptr;
977 static bool init_done
= false;
980 static LibraryLoader
_user32("user32.dll");
981 static LibraryLoader
_shcore("shcore.dll");
982 _GetDpiForWindow
= _user32
.GetFunction("GetDpiForWindow");
983 _GetDpiForSystem
= _user32
.GetFunction("GetDpiForSystem");
984 _GetDpiForMonitor
= _shcore
.GetFunction("GetDpiForMonitor");
989 if (cur_dpi
== 0 && _GetDpiForWindow
!= nullptr && this->main_wnd
!= nullptr) {
990 /* Per window DPI is supported since Windows 10 Ver 1607. */
991 cur_dpi
= _GetDpiForWindow(this->main_wnd
);
993 if (cur_dpi
== 0 && _GetDpiForMonitor
!= nullptr && this->main_wnd
!= nullptr) {
994 /* Per monitor is supported since Windows 8.1. */
996 if (SUCCEEDED(_GetDpiForMonitor(MonitorFromWindow(this->main_wnd
, MONITOR_DEFAULTTOPRIMARY
), 0 /* MDT_EFFECTIVE_DPI */, &dpiX
, &dpiY
))) {
997 cur_dpi
= dpiX
; // X and Y are always identical.
1000 if (cur_dpi
== 0 && _GetDpiForSystem
!= nullptr) {
1001 /* Fall back to system DPI. */
1002 cur_dpi
= _GetDpiForSystem();
1005 return cur_dpi
> 0 ? cur_dpi
/ 96.0f
: 1.0f
; // Default Windows DPI value is 96.
1008 bool VideoDriver_Win32Base::LockVideoBuffer()
1010 if (this->buffer_locked
) return false;
1011 this->buffer_locked
= true;
1013 _screen
.dst_ptr
= this->GetVideoPointer();
1014 assert(_screen
.dst_ptr
!= nullptr);
1019 void VideoDriver_Win32Base::UnlockVideoBuffer()
1021 assert(_screen
.dst_ptr
!= nullptr);
1022 if (_screen
.dst_ptr
!= nullptr) {
1023 /* Hand video buffer back to the drawing backend. */
1024 this->ReleaseVideoPointer();
1025 _screen
.dst_ptr
= nullptr;
1028 this->buffer_locked
= false;
1032 static FVideoDriver_Win32GDI iFVideoDriver_Win32GDI
;
1034 const char *VideoDriver_Win32GDI::Start(const StringList
¶m
)
1036 if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) return "Only real blitters supported";
1040 this->MakePalette();
1041 this->AllocateBackingStore(_cur_resolution
.width
, _cur_resolution
.height
);
1042 this->MakeWindow(_fullscreen
);
1044 MarkWholeScreenDirty();
1046 this->is_game_threaded
= !GetDriverParamBool(param
, "no_threads") && !GetDriverParamBool(param
, "no_thread");
1051 void VideoDriver_Win32GDI::Stop()
1053 DeleteObject(this->gdi_palette
);
1054 DeleteObject(this->dib_sect
);
1056 this->VideoDriver_Win32Base::Stop();
1059 bool VideoDriver_Win32GDI::AllocateBackingStore(int w
, int h
, bool force
)
1061 uint bpp
= BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
1063 w
= std::max(w
, 64);
1064 h
= std::max(h
, 64);
1066 if (!force
&& w
== _screen
.width
&& h
== _screen
.height
) return false;
1068 BITMAPINFO
*bi
= (BITMAPINFO
*)new char[sizeof(BITMAPINFOHEADER
) + sizeof(RGBQUAD
) * 256]();
1069 bi
->bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
1071 bi
->bmiHeader
.biWidth
= this->width
= w
;
1072 bi
->bmiHeader
.biHeight
= -(this->height
= h
);
1074 bi
->bmiHeader
.biPlanes
= 1;
1075 bi
->bmiHeader
.biBitCount
= bpp
;
1076 bi
->bmiHeader
.biCompression
= BI_RGB
;
1078 if (this->dib_sect
) DeleteObject(this->dib_sect
);
1081 this->dib_sect
= CreateDIBSection(dc
, bi
, DIB_RGB_COLORS
, (VOID
**)&this->buffer_bits
, nullptr, 0);
1082 if (this->dib_sect
== nullptr) {
1084 UserError("CreateDIBSection failed");
1089 _screen
.pitch
= (bpp
== 8) ? Align(w
, 4) : w
;
1091 _screen
.dst_ptr
= this->GetVideoPointer();
1097 bool VideoDriver_Win32GDI::AfterBlitterChange()
1099 assert(BlitterFactory::GetCurrentBlitter()->GetScreenDepth() != 0);
1100 return this->AllocateBackingStore(_screen
.width
, _screen
.height
, true) && this->MakeWindow(_fullscreen
, false);
1103 void VideoDriver_Win32GDI::MakePalette()
1105 CopyPalette(_local_palette
, true);
1107 LOGPALETTE
*pal
= (LOGPALETTE
*)new char[sizeof(LOGPALETTE
) + (256 - 1) * sizeof(PALETTEENTRY
)]();
1109 pal
->palVersion
= 0x300;
1110 pal
->palNumEntries
= 256;
1112 for (uint i
= 0; i
!= 256; i
++) {
1113 pal
->palPalEntry
[i
].peRed
= _local_palette
.palette
[i
].r
;
1114 pal
->palPalEntry
[i
].peGreen
= _local_palette
.palette
[i
].g
;
1115 pal
->palPalEntry
[i
].peBlue
= _local_palette
.palette
[i
].b
;
1116 pal
->palPalEntry
[i
].peFlags
= 0;
1119 this->gdi_palette
= CreatePalette(pal
);
1121 if (this->gdi_palette
== nullptr) UserError("CreatePalette failed!\n");
1124 void VideoDriver_Win32GDI::UpdatePalette(HDC dc
, uint start
, uint count
)
1128 for (uint i
= 0; i
!= count
; i
++) {
1129 rgb
[i
].rgbRed
= _local_palette
.palette
[start
+ i
].r
;
1130 rgb
[i
].rgbGreen
= _local_palette
.palette
[start
+ i
].g
;
1131 rgb
[i
].rgbBlue
= _local_palette
.palette
[start
+ i
].b
;
1132 rgb
[i
].rgbReserved
= 0;
1135 SetDIBColorTable(dc
, start
, count
, rgb
);
1138 void VideoDriver_Win32GDI::PaletteChanged(HWND hWnd
)
1140 HDC hDC
= GetWindowDC(hWnd
);
1141 HPALETTE hOldPalette
= SelectPalette(hDC
, this->gdi_palette
, FALSE
);
1142 UINT nChanged
= RealizePalette(hDC
);
1144 SelectPalette(hDC
, hOldPalette
, TRUE
);
1145 ReleaseDC(hWnd
, hDC
);
1146 if (nChanged
!= 0) this->MakeDirty(0, 0, _screen
.width
, _screen
.height
);
1149 void VideoDriver_Win32GDI::Paint()
1151 PerformanceMeasurer
framerate(PFE_VIDEO
);
1153 if (IsEmptyRect(this->dirty_rect
)) return;
1155 HDC dc
= GetDC(this->main_wnd
);
1156 HDC dc2
= CreateCompatibleDC(dc
);
1158 HBITMAP old_bmp
= (HBITMAP
)SelectObject(dc2
, this->dib_sect
);
1159 HPALETTE old_palette
= SelectPalette(dc
, this->gdi_palette
, FALSE
);
1161 if (_local_palette
.count_dirty
!= 0) {
1162 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1164 switch (blitter
->UsePaletteAnimation()) {
1165 case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND
:
1166 this->UpdatePalette(dc2
, _local_palette
.first_dirty
, _local_palette
.count_dirty
);
1169 case Blitter::PALETTE_ANIMATION_BLITTER
: {
1170 blitter
->PaletteAnimate(_local_palette
);
1174 case Blitter::PALETTE_ANIMATION_NONE
:
1180 _local_palette
.count_dirty
= 0;
1183 BitBlt(dc
, 0, 0, this->width
, this->height
, dc2
, 0, 0, SRCCOPY
);
1184 SelectPalette(dc
, old_palette
, TRUE
);
1185 SelectObject(dc2
, old_bmp
);
1188 ReleaseDC(this->main_wnd
, dc
);
1190 this->dirty_rect
= {};
1194 /* Keep this function here..
1195 * It allows you to redraw the screen from within the MSVC debugger */
1196 /* static */ int VideoDriver_Win32GDI::RedrawScreenDebug()
1200 VideoDriver_Win32GDI
*drv
= static_cast<VideoDriver_Win32GDI
*>(VideoDriver::GetInstance());
1202 _screen
.dst_ptr
= drv
->GetVideoPointer();
1215 #include "../3rdparty/opengl/glext.h"
1216 #include "../3rdparty/opengl/wglext.h"
1219 #ifndef PFD_SUPPORT_COMPOSITION
1220 # define PFD_SUPPORT_COMPOSITION 0x00008000
1223 static PFNWGLCREATECONTEXTATTRIBSARBPROC _wglCreateContextAttribsARB
= nullptr;
1224 static PFNWGLSWAPINTERVALEXTPROC _wglSwapIntervalEXT
= nullptr;
1225 static bool _hasWGLARBCreateContextProfile
= false; ///< Is WGL_ARB_create_context_profile supported?
1227 /** Platform-specific callback to get an OpenGL function pointer. */
1228 static OGLProc
GetOGLProcAddressCallback(const char *proc
)
1230 OGLProc ret
= reinterpret_cast<OGLProc
>(wglGetProcAddress(proc
));
1231 if (ret
== nullptr) {
1232 /* Non-extension GL function? Try normal loading. */
1233 ret
= reinterpret_cast<OGLProc
>(GetProcAddress(GetModuleHandle(L
"opengl32"), proc
));
1239 * Set the pixel format of a window-
1240 * @param dc Device context to set the pixel format of.
1241 * @return nullptr on success, error message otherwise.
1243 static const char *SelectPixelFormat(HDC dc
)
1245 PIXELFORMATDESCRIPTOR pfd
= {
1246 sizeof(PIXELFORMATDESCRIPTOR
), // Size of this struct.
1247 1, // Version of this struct.
1248 PFD_DRAW_TO_WINDOW
| // Require window support.
1249 PFD_SUPPORT_OPENGL
| // Require OpenGL support.
1250 PFD_DOUBLEBUFFER
| // Use double buffering.
1252 PFD_TYPE_RGBA
, // Request RGBA format.
1253 24, // 24 bpp (excluding alpha).
1254 0, 0, 0, 0, 0, 0, 0, 0, // Colour bits and shift ignored.
1255 0, 0, 0, 0, 0, // No accumulation buffer.
1256 0, 0, // No depth/stencil buffer.
1257 0, // No aux buffers.
1258 PFD_MAIN_PLANE
, // Main layer.
1259 0, 0, 0, 0 // Ignored/reserved.
1262 pfd
.dwFlags
|= PFD_SUPPORT_COMPOSITION
; // Make OpenTTD compatible with Aero.
1264 /* Choose a suitable pixel format. */
1265 int format
= ChoosePixelFormat(dc
, &pfd
);
1266 if (format
== 0) return "No suitable pixel format found";
1267 if (!SetPixelFormat(dc
, format
, &pfd
)) return "Can't set pixel format";
1272 /** Bind all WGL extension functions we need. */
1273 static void LoadWGLExtensions()
1275 /* Querying the supported WGL extensions and loading the matching
1276 * functions requires a valid context, even for the extensions
1277 * regarding context creation. To get around this, we create
1278 * a dummy window with a dummy context. The extension functions
1279 * remain valid even after this context is destroyed. */
1280 HWND wnd
= CreateWindow(_T("STATIC"), _T("dummy"), WS_OVERLAPPEDWINDOW
, 0, 0, 0, 0, nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
1281 HDC dc
= GetDC(wnd
);
1283 /* Set pixel format of the window. */
1284 if (SelectPixelFormat(dc
) == nullptr) {
1285 /* Create rendering context. */
1286 HGLRC rc
= wglCreateContext(dc
);
1287 if (rc
!= nullptr) {
1288 wglMakeCurrent(dc
, rc
);
1291 /* GCC doesn't understand the expected usage of wglGetProcAddress(). */
1292 #pragma GCC diagnostic push
1293 #pragma GCC diagnostic ignored "-Wcast-function-type"
1294 #endif /* __MINGW32__ */
1296 /* Get list of WGL extensions. */
1297 PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB
= (PFNWGLGETEXTENSIONSSTRINGARBPROC
)wglGetProcAddress("wglGetExtensionsStringARB");
1298 if (wglGetExtensionsStringARB
!= nullptr) {
1299 const char *wgl_exts
= wglGetExtensionsStringARB(dc
);
1300 /* Bind supported functions. */
1301 if (FindStringInExtensionList(wgl_exts
, "WGL_ARB_create_context") != nullptr) {
1302 _wglCreateContextAttribsARB
= (PFNWGLCREATECONTEXTATTRIBSARBPROC
)wglGetProcAddress("wglCreateContextAttribsARB");
1304 _hasWGLARBCreateContextProfile
= FindStringInExtensionList(wgl_exts
, "WGL_ARB_create_context_profile") != nullptr;
1305 if (FindStringInExtensionList(wgl_exts
, "WGL_EXT_swap_control") != nullptr) {
1306 _wglSwapIntervalEXT
= (PFNWGLSWAPINTERVALEXTPROC
)wglGetProcAddress("wglSwapIntervalEXT");
1311 #pragma GCC diagnostic pop
1313 wglMakeCurrent(nullptr, nullptr);
1314 wglDeleteContext(rc
);
1322 static FVideoDriver_Win32OpenGL iFVideoDriver_Win32OpenGL
;
1324 const char *VideoDriver_Win32OpenGL::Start(const StringList
¶m
)
1326 if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) return "Only real blitters supported";
1328 Dimension old_res
= _cur_resolution
; // Save current screen resolution in case of errors, as MakeWindow invalidates it.
1330 LoadWGLExtensions();
1333 this->MakeWindow(_fullscreen
);
1335 /* Create and initialize OpenGL context. */
1336 const char *err
= this->AllocateContext();
1337 if (err
!= nullptr) {
1339 _cur_resolution
= old_res
;
1343 this->driver_info
= GetName();
1344 this->driver_info
+= " (";
1345 this->driver_info
+= OpenGLBackend::Get()->GetDriverName();
1346 this->driver_info
+= ")";
1348 this->ClientSizeChanged(this->width
, this->height
, true);
1349 /* We should have a valid screen buffer now. If not, something went wrong and we should abort. */
1350 if (_screen
.dst_ptr
== nullptr) {
1352 _cur_resolution
= old_res
;
1353 return "Can't get pointer to screen buffer";
1355 /* Main loop expects to start with the buffer unmapped. */
1356 this->ReleaseVideoPointer();
1358 MarkWholeScreenDirty();
1360 this->is_game_threaded
= !GetDriverParamBool(param
, "no_threads") && !GetDriverParamBool(param
, "no_thread");
1365 void VideoDriver_Win32OpenGL::Stop()
1367 this->DestroyContext();
1368 this->VideoDriver_Win32Base::Stop();
1371 void VideoDriver_Win32OpenGL::DestroyContext()
1373 OpenGLBackend::Destroy();
1375 wglMakeCurrent(nullptr, nullptr);
1376 if (this->gl_rc
!= nullptr) {
1377 wglDeleteContext(this->gl_rc
);
1378 this->gl_rc
= nullptr;
1380 if (this->dc
!= nullptr) {
1381 ReleaseDC(this->main_wnd
, this->dc
);
1386 void VideoDriver_Win32OpenGL::ToggleVsync(bool vsync
)
1388 if (_wglSwapIntervalEXT
!= nullptr) {
1389 _wglSwapIntervalEXT(vsync
);
1391 Debug(driver
, 0, "OpenGL: Vsync requested, but not supported by driver");
1395 const char *VideoDriver_Win32OpenGL::AllocateContext()
1397 this->dc
= GetDC(this->main_wnd
);
1399 const char *err
= SelectPixelFormat(this->dc
);
1400 if (err
!= nullptr) return err
;
1404 /* Create OpenGL device context. Try to get an 3.2+ context if possible. */
1405 if (_wglCreateContextAttribsARB
!= nullptr) {
1406 /* Try for OpenGL 4.5 first. */
1408 WGL_CONTEXT_MAJOR_VERSION_ARB
, 4,
1409 WGL_CONTEXT_MINOR_VERSION_ARB
, 5,
1410 WGL_CONTEXT_FLAGS_ARB
, _debug_driver_level
>= 8 ? WGL_CONTEXT_DEBUG_BIT_ARB
: 0,
1411 _hasWGLARBCreateContextProfile
? WGL_CONTEXT_PROFILE_MASK_ARB
: 0, WGL_CONTEXT_CORE_PROFILE_BIT_ARB
, // Terminate list if WGL_ARB_create_context_profile isn't supported.
1414 rc
= _wglCreateContextAttribsARB(this->dc
, nullptr, attribs
);
1416 if (rc
== nullptr) {
1417 /* Try again for a 3.2 context. */
1420 rc
= _wglCreateContextAttribsARB(this->dc
, nullptr, attribs
);
1424 if (rc
== nullptr) {
1425 /* Old OpenGL or old driver, let's hope for the best. */
1426 rc
= wglCreateContext(this->dc
);
1427 if (rc
== nullptr) return "Can't create OpenGL context";
1429 if (!wglMakeCurrent(this->dc
, rc
)) return "Can't active GL context";
1431 this->ToggleVsync(_video_vsync
);
1434 return OpenGLBackend::Create(&GetOGLProcAddressCallback
, this->GetScreenSize());
1437 bool VideoDriver_Win32OpenGL::ToggleFullscreen(bool full_screen
)
1439 if (_screen
.dst_ptr
!= nullptr) this->ReleaseVideoPointer();
1440 this->DestroyContext();
1441 bool res
= this->VideoDriver_Win32Base::ToggleFullscreen(full_screen
);
1442 res
&= this->AllocateContext() == nullptr;
1443 this->ClientSizeChanged(this->width
, this->height
, true);
1447 bool VideoDriver_Win32OpenGL::AfterBlitterChange()
1449 assert(BlitterFactory::GetCurrentBlitter()->GetScreenDepth() != 0);
1450 this->ClientSizeChanged(this->width
, this->height
, true);
1454 void VideoDriver_Win32OpenGL::PopulateSystemSprites()
1456 OpenGLBackend::Get()->PopulateCursorCache();
1459 void VideoDriver_Win32OpenGL::ClearSystemSprites()
1461 OpenGLBackend::Get()->ClearCursorCache();
1464 bool VideoDriver_Win32OpenGL::AllocateBackingStore(int w
, int h
, bool force
)
1466 if (!force
&& w
== _screen
.width
&& h
== _screen
.height
) return false;
1468 this->width
= w
= std::max(w
, 64);
1469 this->height
= h
= std::max(h
, 64);
1471 if (this->gl_rc
== nullptr) return false;
1473 if (_screen
.dst_ptr
!= nullptr) this->ReleaseVideoPointer();
1475 this->dirty_rect
= {};
1476 bool res
= OpenGLBackend::Get()->Resize(w
, h
, force
);
1477 SwapBuffers(this->dc
);
1478 _screen
.dst_ptr
= this->GetVideoPointer();
1483 void *VideoDriver_Win32OpenGL::GetVideoPointer()
1485 if (BlitterFactory::GetCurrentBlitter()->NeedsAnimationBuffer()) {
1486 this->anim_buffer
= OpenGLBackend::Get()->GetAnimBuffer();
1488 return OpenGLBackend::Get()->GetVideoBuffer();
1491 void VideoDriver_Win32OpenGL::ReleaseVideoPointer()
1493 if (this->anim_buffer
!= nullptr) OpenGLBackend::Get()->ReleaseAnimBuffer(this->dirty_rect
);
1494 OpenGLBackend::Get()->ReleaseVideoBuffer(this->dirty_rect
);
1495 this->dirty_rect
= {};
1496 _screen
.dst_ptr
= nullptr;
1497 this->anim_buffer
= nullptr;
1500 void VideoDriver_Win32OpenGL::Paint()
1502 PerformanceMeasurer
framerate(PFE_VIDEO
);
1504 if (_local_palette
.count_dirty
!= 0) {
1505 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1507 /* Always push a changed palette to OpenGL. */
1508 OpenGLBackend::Get()->UpdatePalette(_local_palette
.palette
, _local_palette
.first_dirty
, _local_palette
.count_dirty
);
1509 if (blitter
->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_BLITTER
) {
1510 blitter
->PaletteAnimate(_local_palette
);
1513 _local_palette
.count_dirty
= 0;
1516 OpenGLBackend::Get()->Paint();
1517 OpenGLBackend::Get()->DrawMouseCursor();
1519 SwapBuffers(this->dc
);
1522 #endif /* WITH_OPENGL */