diff --git a/code/bgfxbackend.cpp b/code/bgfxbackend.cpp
index b771f99..8d82543 100644
--- a/code/bgfxbackend.cpp
+++ b/code/bgfxbackend.cpp
@@ -52,8 +52,8 @@ static int _FrameWidth = 0;
static int _FrameHeight = 0;
static int _PrescaleWidth = 0;
static int _PrescaleHeight = 0;
-static int _WindowWidth = 0;
-static int _WindowHeight = 0;
+static int _DrawableWidth = 0;
+static int _DrawableHeight = 0;
static unsigned int _ResetFlags = BGFX_RESET_FLIP_AFTER_RENDER;
// True while the frame texture holds the game's own 565 layout. When the hardware cannot
@@ -233,12 +233,12 @@ static bool Ensure_Prescale_Target(int width, int height)
/// Starts the renderer on an existing window.
///
/// The window the frame is presented into.
-/// The width of that window's client area.
-/// The height of that window's client area.
+/// The drawable area's width in physical pixels.
+/// The drawable area's height in physical pixels.
/// Which graphics API to ask for, or auto to let bgfx decide.
/// Should presents wait for the display's refresh?
/// bool; Did the renderer start?
-bool Backend_Init(HWND window, int windowwidth, int windowheight, BackendRenderer renderer, bool vsync)
+bool Backend_Init(NativeWindow const & window, int drawablewidth, int drawableheight, BackendRenderer renderer, bool vsync)
{
if (_Initialized) {
return(true);
@@ -249,14 +249,15 @@ bool Backend_Init(HWND window, int windowwidth, int windowheight, BackendRendere
// renderFrame before init is what selects that.
bgfx::renderFrame();
- _WindowWidth = windowwidth;
- _WindowHeight = windowheight;
+ _DrawableWidth = drawablewidth;
+ _DrawableHeight = drawableheight;
_ResetFlags = BGFX_RESET_FLIP_AFTER_RENDER | (vsync ? BGFX_RESET_VSYNC : BGFX_RESET_NONE);
bgfx::Init init;
- init.platformData.nwh = window;
- init.resolution.width = (uint32_t)windowwidth;
- init.resolution.height = (uint32_t)windowheight;
+ init.platformData.ndt = window.Display;
+ init.platformData.nwh = window.Handle;
+ init.resolution.width = (uint32_t)drawablewidth;
+ init.resolution.height = (uint32_t)drawableheight;
init.resolution.reset = _ResetFlags;
init.callback = &_Callback;
@@ -396,21 +397,21 @@ bool Backend_Set_Frame_Size(int width, int height)
///
-/// Tells the renderer the window's client area changed size.
+/// Tells the renderer the drawable area changed size.
///
-void Backend_On_Resize(int windowwidth, int windowheight)
+void Backend_On_Resize(int drawablewidth, int drawableheight)
{
- if (!_Initialized || windowwidth <= 0 || windowheight <= 0) {
+ if (!_Initialized || drawablewidth <= 0 || drawableheight <= 0) {
return;
}
- if (_WindowWidth == windowwidth && _WindowHeight == windowheight) {
+ if (_DrawableWidth == drawablewidth && _DrawableHeight == drawableheight) {
return;
}
- _WindowWidth = windowwidth;
- _WindowHeight = windowheight;
- bgfx::reset((uint32_t)windowwidth, (uint32_t)windowheight, _ResetFlags);
+ _DrawableWidth = drawablewidth;
+ _DrawableHeight = drawableheight;
+ bgfx::reset((uint32_t)drawablewidth, (uint32_t)drawableheight, _ResetFlags);
}
@@ -431,7 +432,7 @@ void Backend_Present(void const * pixels, int pitch, int destx, int desty, int d
}
// A minimized window has no client area to present into.
- if (_WindowWidth <= 0 || _WindowHeight <= 0) {
+ if (_DrawableWidth <= 0 || _DrawableHeight <= 0) {
return;
}
@@ -483,7 +484,7 @@ void Backend_Present(void const * pixels, int pitch, int destx, int desty, int d
// share the window's shape.
bgfx::setViewFrameBuffer(VIEW_PRESENT, BGFX_INVALID_HANDLE);
bgfx::setViewClear(VIEW_PRESENT, BGFX_CLEAR_COLOR, 0x000000FF);
- Set_View_Transform(VIEW_PRESENT, _WindowWidth, _WindowHeight);
+ Set_View_Transform(VIEW_PRESENT, _DrawableWidth, _DrawableHeight);
Submit_Quad(VIEW_PRESENT, source, (float)destx, (float)desty, (float)destwidth, (float)destheight, samplerflags);
bgfx::frame();
diff --git a/code/bgfxbackend.h b/code/bgfxbackend.h
index a6cadfc..8f3cb41 100644
--- a/code/bgfxbackend.h
+++ b/code/bgfxbackend.h
@@ -13,7 +13,7 @@
#pragma once
-#include
+#include "nativewindow.hh"
enum BackendRenderer {
@@ -32,11 +32,12 @@ enum BackendScaleMode {
};
-bool Backend_Init(HWND window, int windowwidth, int windowheight, BackendRenderer renderer, bool vsync);
+// Drawable sizes are physical pixel dimensions supplied by the application shell.
+bool Backend_Init(NativeWindow const & window, int drawablewidth, int drawableheight, BackendRenderer renderer, bool vsync);
void Backend_Shutdown(void);
bool Backend_Set_Frame_Size(int width, int height);
-void Backend_On_Resize(int windowwidth, int windowheight);
+void Backend_On_Resize(int drawablewidth, int drawableheight);
// Uploads the frame and presents it. The pixels are 16 bit 565 and stay owned by the
// caller; they are consumed before this returns.
diff --git a/code/mainopt.cpp b/code/mainopt.cpp
index cb66370..cc332df 100644
--- a/code/mainopt.cpp
+++ b/code/mainopt.cpp
@@ -26,6 +26,7 @@
#include "language\language.h"
#include "misc.h"
#include "video.h"
+#include "winstub.h"
#include "mixfile.h"
#include "msgbox.h"
#include "newmenu.h"
@@ -195,7 +196,7 @@ bool Change_Display_Mode(int width, int height)
Hide_Mouse();
- if (!Video_Set_Mode(width, height)) {
+ if (!Video_Set_Mode(width, height, Win_Window_Refresh_Rate(MainWindow))) {
DebugString("Video_Set_Mode failed.\n");
Show_Mouse();
return(false);
diff --git a/code/nativewindow.hh b/code/nativewindow.hh
new file mode 100644
index 0000000..468875d
--- /dev/null
+++ b/code/nativewindow.hh
@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * O P E N T S
+ *******************************************************************************
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ * Copyright 2026 OpenTS contributors
+ *
+ * See LICENSE.md for applicable additional terms and warranty disclaimers.
+ ******************************************************************************/
+
+#pragma once
+
+
+// The native handles bgfx needs to present into a window supplied by the application shell.
+// Display is unused on platforms where the window identifies its display by itself.
+struct NativeWindow
+{
+ void * Display;
+ void * Handle;
+};
diff --git a/code/startup.cpp b/code/startup.cpp
index b467150..2b2bc1b 100644
--- a/code/startup.cpp
+++ b/code/startup.cpp
@@ -574,7 +574,12 @@ int CALLBACK WinMain ( HINSTANCE instance , HINSTANCE , char * command_line , in
Audio.Init(MainWindow, 16, 0, 22050);
- if (!Video_Init(MainWindow)) {
+ int drawablewidth = 0;
+ int drawableheight = 0;
+ int refreshrate = Win_Window_Refresh_Rate(MainWindow);
+ NativeWindow nativewindow = Win_Native_Window(MainWindow);
+ if (!Win_Window_Drawable_Size(MainWindow, drawablewidth, drawableheight)
+ || !Video_Init(nativewindow, drawablewidth, drawableheight, refreshrate)) {
MessageBox(MainWindow, Fetch_String(TXT_VIDEO_ERROR), Fetch_String(TXT_SHORT_TITLE), MB_ICONWARNING);
exit(EXIT_FAILURE);
}
diff --git a/code/video.cpp b/code/video.cpp
index e25f6fa..0683055 100644
--- a/code/video.cpp
+++ b/code/video.cpp
@@ -41,7 +41,6 @@ int VideoModeHeight = 0;
*/
bool WindowedMode = false;
-static HWND _Window = NULL;
static bool _Initialized = false;
static VideoScaleInfo _ScaleInfo;
@@ -60,21 +59,13 @@ static bool _Presenting = false;
///
/// Works out the shortest sensible gap between presents from the display's refresh rate.
///
-static void Update_Present_Interval(void)
+static void Update_Present_Interval(int refreshrate)
{
- int refresh = 0;
- HDC dc = GetDC(_Window);
-
- if (dc != NULL) {
- refresh = GetDeviceCaps(dc, VREFRESH);
- ReleaseDC(_Window, dc);
- }
-
- if (refresh <= 1) {
- refresh = 60;
+ if (refreshrate <= 1) {
+ refreshrate = 60;
}
- _PresentInterval = (unsigned int)(1000 / refresh);
+ _PresentInterval = (unsigned int)(1000 / refreshrate);
if (_PresentInterval < 3) {
_PresentInterval = 3;
}
@@ -91,33 +82,21 @@ static void Update_Present_Interval(void)
///
static void Update_Scale_Info(void)
{
- RECT client;
-
_ScaleInfo.GameWidth = VideoModeWidth;
_ScaleInfo.GameHeight = VideoModeHeight;
- if (_Window == NULL || !GetClientRect(_Window, &client)) {
- client.left = 0;
- client.top = 0;
- client.right = VideoModeWidth;
- client.bottom = VideoModeHeight;
- }
-
- _ScaleInfo.WindowWidth = client.right - client.left;
- _ScaleInfo.WindowHeight = client.bottom - client.top;
-
- if (_ScaleInfo.GameWidth <= 0 || _ScaleInfo.GameHeight <= 0 || _ScaleInfo.WindowWidth <= 0 || _ScaleInfo.WindowHeight <= 0) {
+ if (_ScaleInfo.GameWidth <= 0 || _ScaleInfo.GameHeight <= 0 || _ScaleInfo.DrawableWidth <= 0 || _ScaleInfo.DrawableHeight <= 0) {
_ScaleInfo.DestX = 0;
_ScaleInfo.DestY = 0;
- _ScaleInfo.DestWidth = _ScaleInfo.WindowWidth;
- _ScaleInfo.DestHeight = _ScaleInfo.WindowHeight;
+ _ScaleInfo.DestWidth = _ScaleInfo.DrawableWidth;
+ _ScaleInfo.DestHeight = _ScaleInfo.DrawableHeight;
_ScaleInfo.ScaleX = 1.0f;
_ScaleInfo.ScaleY = 1.0f;
return;
}
- double scalex = (double)_ScaleInfo.WindowWidth / (double)_ScaleInfo.GameWidth;
- double scaley = (double)_ScaleInfo.WindowHeight / (double)_ScaleInfo.GameHeight;
+ double scalex = (double)_ScaleInfo.DrawableWidth / (double)_ScaleInfo.GameWidth;
+ double scaley = (double)_ScaleInfo.DrawableHeight / (double)_ScaleInfo.GameHeight;
double scale = (scalex < scaley) ? scalex : scaley;
if (Options.IntegerScaling && scale >= 1.0) {
@@ -126,8 +105,8 @@ static void Update_Scale_Info(void)
_ScaleInfo.DestWidth = (int)((double)_ScaleInfo.GameWidth * scale);
_ScaleInfo.DestHeight = (int)((double)_ScaleInfo.GameHeight * scale);
- _ScaleInfo.DestX = (_ScaleInfo.WindowWidth - _ScaleInfo.DestWidth) / 2;
- _ScaleInfo.DestY = (_ScaleInfo.WindowHeight - _ScaleInfo.DestHeight) / 2;
+ _ScaleInfo.DestX = (_ScaleInfo.DrawableWidth - _ScaleInfo.DestWidth) / 2;
+ _ScaleInfo.DestY = (_ScaleInfo.DrawableHeight - _ScaleInfo.DestHeight) / 2;
_ScaleInfo.ScaleX = (float)((double)_ScaleInfo.DestWidth / (double)_ScaleInfo.GameWidth);
_ScaleInfo.ScaleY = (float)((double)_ScaleInfo.DestHeight / (double)_ScaleInfo.GameHeight);
}
@@ -154,24 +133,26 @@ static BackendScaleMode Backend_Scale_Mode(void)
///
/// Starts the presenter on the game's window.
///
-/// The main window. Its client area receives the frame.
+/// The native window whose drawable area receives the frame.
+/// The drawable area's width in physical pixels.
+/// The drawable area's height in physical pixels.
+/// The display refresh rate in hertz, or zero when unknown.
/// bool; Did the presenter start? A false return is fatal to the game.
-bool Video_Init(HWND window)
+bool Video_Init(NativeWindow const & window, int drawablewidth, int drawableheight, int refreshrate)
{
- RECT client;
-
if (_Initialized) {
return(true);
}
- if (window == NULL || !GetClientRect(window, &client)) {
+ if (window.Handle == nullptr || drawablewidth <= 0 || drawableheight <= 0) {
return(false);
}
- _Window = window;
+ _ScaleInfo.DrawableWidth = drawablewidth;
+ _ScaleInfo.DrawableHeight = drawableheight;
BackendRenderer renderer = (BackendRenderer)Options.Renderer;
- if (!Backend_Init(window, client.right - client.left, client.bottom - client.top, renderer, Options.VSync)) {
+ if (!Backend_Init(window, drawablewidth, drawableheight, renderer, Options.VSync)) {
return(false);
}
@@ -186,7 +167,7 @@ bool Video_Init(HWND window)
}
Update_Scale_Info();
- Update_Present_Interval();
+ Update_Present_Interval(refreshrate);
return(true);
}
@@ -203,7 +184,6 @@ void Video_Shutdown(void)
Win_Cursor_Shutdown();
Backend_Shutdown();
_Initialized = false;
- _Window = NULL;
_FrameIsDirty = false;
}
@@ -215,8 +195,9 @@ void Video_Shutdown(void)
///
/// The new frame width.
/// The new frame height.
+/// The display refresh rate in hertz, or zero when unknown.
/// bool; Was the mode changed?
-bool Video_Set_Mode(int width, int height)
+bool Video_Set_Mode(int width, int height, int refreshrate)
{
if (!_Initialized || width <= 0 || height <= 0) {
return(false);
@@ -230,7 +211,7 @@ bool Video_Set_Mode(int width, int height)
VideoModeHeight = height;
Update_Scale_Info();
- Update_Present_Interval();
+ Update_Present_Interval(refreshrate);
Win_Cursor_Refresh();
_FrameIsDirty = true;
return(true);
@@ -238,17 +219,19 @@ bool Video_Set_Mode(int width, int height)
///
-/// Tells the presenter the window's client area changed size.
+/// Tells the presenter the drawable area or display timing changed.
///
-void Video_On_Resize(int width, int height)
+void Video_On_Resize(int drawablewidth, int drawableheight, int refreshrate)
{
- if (!_Initialized || width <= 0 || height <= 0) {
+ if (!_Initialized || drawablewidth <= 0 || drawableheight <= 0) {
return;
}
- Backend_On_Resize(width, height);
+ _ScaleInfo.DrawableWidth = drawablewidth;
+ _ScaleInfo.DrawableHeight = drawableheight;
+ Backend_On_Resize(drawablewidth, drawableheight);
Update_Scale_Info();
- Update_Present_Interval();
+ Update_Present_Interval(refreshrate);
Win_Cursor_Refresh();
Video_Mark_Dirty();
}
@@ -258,13 +241,13 @@ void Video_On_Resize(int width, int height)
/// Tells the presenter the desktop's display settings changed.
/// The window may now be on a monitor that refreshes at a different rate.
///
-void Video_On_Display_Change(void)
+void Video_On_Display_Change(int refreshrate)
{
if (!_Initialized) {
return;
}
- Update_Present_Interval();
+ Update_Present_Interval(refreshrate);
Video_Mark_Dirty();
}
diff --git a/code/video.h b/code/video.h
index 332b433..fe81a49 100644
--- a/code/video.h
+++ b/code/video.h
@@ -9,7 +9,7 @@
#pragma once
-#include "win.h"
+#include "nativewindow.hh"
// How the presented frame is filtered when the window is larger than it.
@@ -22,12 +22,13 @@ enum VideoScaleMode {
// Where the game's frame lands inside the window. The frame keeps its aspect ratio, so
// the destination is centered and the window may show bars on two of its sides.
+// Drawable dimensions and the destination rectangle are measured in physical pixels.
struct VideoScaleInfo
{
int GameWidth;
int GameHeight;
- int WindowWidth;
- int WindowHeight;
+ int DrawableWidth;
+ int DrawableHeight;
int DestX;
int DestY;
int DestWidth;
@@ -37,12 +38,12 @@ struct VideoScaleInfo
};
-bool Video_Init(HWND window);
+bool Video_Init(NativeWindow const & window, int drawablewidth, int drawableheight, int refreshrate);
void Video_Shutdown(void);
-bool Video_Set_Mode(int width, int height);
-void Video_On_Resize(int width, int height);
-void Video_On_Display_Change(void);
+bool Video_Set_Mode(int width, int height, int refreshrate);
+void Video_On_Resize(int drawablewidth, int drawableheight, int refreshrate);
+void Video_On_Display_Change(int refreshrate);
void Video_Mark_Dirty(void);
void Video_Present(void);
diff --git a/code/winstub.cpp b/code/winstub.cpp
index ab9f5d7..fb70b5d 100644
--- a/code/winstub.cpp
+++ b/code/winstub.cpp
@@ -64,6 +64,7 @@
#include "movie.h"
#include "movies.h"
#include "msgroute.h"
+#include "nativewindow.hh"
#include "pcx.h"
#include "resource.h"
#include "theme.h"
@@ -273,7 +274,7 @@ LRESULT CALLBACK /*_export*/ Windows_Procedure(HWND hwnd, UINT message, UINT wPa
case WM_SIZE:
if (wParam != SIZE_MINIMIZED) {
- Video_On_Resize(LOWORD(lParam), HIWORD(lParam));
+ Video_On_Resize(LOWORD(lParam), HIWORD(lParam), Win_Window_Refresh_Rate(hwnd));
if (MouseCursor != NULL) {
((WWMouseClass *)MouseCursor)->Calc_Confining_Rect();
}
@@ -281,7 +282,7 @@ LRESULT CALLBACK /*_export*/ Windows_Procedure(HWND hwnd, UINT message, UINT wPa
break;
case WM_DISPLAYCHANGE:
- Video_On_Display_Change();
+ Video_On_Display_Change(Win_Window_Refresh_Rate(hwnd));
break;
case WM_CLOSE:
@@ -393,6 +394,40 @@ LRESULT CALLBACK /*_export*/ Windows_Procedure(HWND hwnd, UINT message, UINT wPa
}
+NativeWindow Win_Native_Window(HWND window)
+{
+ return(NativeWindow{ nullptr, window });
+}
+
+
+// Client dimensions are physical pixels because the process is per-monitor DPI aware.
+bool Win_Window_Drawable_Size(HWND window, int & width, int & height)
+{
+ RECT client;
+ if (window == NULL || !GetClientRect(window, &client)) {
+ return(false);
+ }
+
+ width = client.right - client.left;
+ height = client.bottom - client.top;
+ return(width > 0 && height > 0);
+}
+
+
+int Win_Window_Refresh_Rate(HWND window)
+{
+ int refreshrate = 0;
+ HDC dc = GetDC(window);
+
+ if (dc != NULL) {
+ refreshrate = GetDeviceCaps(dc, VREFRESH);
+ ReleaseDC(window, dc);
+ }
+
+ return(refreshrate);
+}
+
+
///
/// Fetches the build number of this executable.
/// This routine is used by the network code to check that every machine joining a
diff --git a/code/winstub.h b/code/winstub.h
index 6cbd3b8..ea3ef45 100644
--- a/code/winstub.h
+++ b/code/winstub.h
@@ -17,8 +17,12 @@
class Surface;
class PaletteClass;
+struct NativeWindow;
void Create_Main_Window ( HINSTANCE instance , int command_show , int width , int height);
+NativeWindow Win_Native_Window(HWND window);
+bool Win_Window_Drawable_Size(HWND window, int & width, int & height);
+int Win_Window_Refresh_Rate(HWND window);
void Load_Title_Screen(char const * name, Surface * surface, PaletteClass * palette);