diff --git a/code/bgfxbackend.cpp b/code/bgfxbackend.cpp index b771f99b..2fc384ca 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 a6cadfcf..8f3cb418 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 00000000..8a98d0c9 --- /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 b4671501..2b2bc1ba 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 e25f6fa8..cdc999f3 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 332b433e..08f80390 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 ab9f5d72..c9c63111 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 6cbd3b86..ea3ef451 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); diff --git a/manual/changes/native-window-boundary.md b/manual/changes/native-window-boundary.md new file mode 100644 index 00000000..1b7b4357 --- /dev/null +++ b/manual/changes/native-window-boundary.md @@ -0,0 +1,9 @@ +--- +title: Separate native window handling from video presentation +category: internal +release: 0.2.0 +targets: [] +credit: [Krisztiaan] +--- + +The application shell now supplies the native window handle, physical drawable size, and display refresh rate to the video presenter. The presenter no longer owns Win32 window queries, and the bgfx backend alone translates the native handle into bgfx platform data. This is an internal boundary change; the supported target and video configuration are unchanged.