diff --git a/code/bgfxbackend.cpp b/code/bgfxbackend.cpp index b771f99..2fc384c 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,18 @@ 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.platformData.type = window.Type == NATIVE_WINDOW_WAYLAND + ? bgfx::NativeWindowHandleType::Wayland + : bgfx::NativeWindowHandleType::Default; + init.resolution.width = (uint32_t)drawablewidth; + init.resolution.height = (uint32_t)drawableheight; init.resolution.reset = _ResetFlags; init.callback = &_Callback; @@ -396,21 +400,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 +435,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 +487,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/nativewindow.hh b/code/nativewindow.hh new file mode 100644 index 0000000..8a98d0c --- /dev/null +++ b/code/nativewindow.hh @@ -0,0 +1,27 @@ +/******************************************************************************* + * 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 + + +enum NativeWindowType +{ + NATIVE_WINDOW_DEFAULT, + NATIVE_WINDOW_WAYLAND, +}; + + +// 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 +{ + NativeWindowType Type; + 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..cdc999f 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; } @@ -230,7 +210,6 @@ bool Video_Set_Mode(int width, int height) VideoModeHeight = height; Update_Scale_Info(); - Update_Present_Interval(); Win_Cursor_Refresh(); _FrameIsDirty = true; return(true); @@ -238,17 +217,18 @@ bool Video_Set_Mode(int width, int height) /// -/// Tells the presenter the window's client area changed size. +/// Tells the presenter the drawable area changed size. /// -void Video_On_Resize(int width, int height) +void Video_On_Resize(int drawablewidth, int drawableheight) { - 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(); Win_Cursor_Refresh(); Video_Mark_Dirty(); } @@ -258,13 +238,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..08f8039 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); +void Video_On_Resize(int drawablewidth, int drawableheight); +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..c9c6311 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" @@ -274,6 +275,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_Display_Change(Win_Window_Refresh_Rate(hwnd)); if (MouseCursor != NULL) { ((WWMouseClass *)MouseCursor)->Calc_Confining_Rect(); } @@ -281,7 +283,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 +395,40 @@ LRESULT CALLBACK /*_export*/ Windows_Procedure(HWND hwnd, UINT message, UINT wPa } +NativeWindow Win_Native_Window(HWND window) +{ + return(NativeWindow{ NATIVE_WINDOW_DEFAULT, 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);