Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,44 @@ inline Int64 getPerformanceCounterFrequency()
return tmp;
}

// Startup splash window created by WinMain; the game window stays hidden until handed over here
HWND ApplicationSplashHWnd = nullptr;

// Show the game window and close the splash
static void finishStartupSplash()
{
extern HWND ApplicationHWnd;
if (ApplicationSplashHWnd == nullptr)
return;

if (ApplicationHWnd != nullptr)
{
::ShowWindow(ApplicationHWnd, SW_SHOW);
::SetForegroundWindow(ApplicationHWnd);
::SetFocus(ApplicationHWnd);
}
::DestroyWindow(ApplicationSplashHWnd);
ApplicationSplashHWnd = nullptr;
}

// Windowed hand-over, once per frame until done: the first frame shows the game window behind the
// splash without activating it, the next one (after a presented frame) closes the splash
static void updateStartupSplash()
{
extern HWND ApplicationHWnd;
if (ApplicationSplashHWnd == nullptr || ApplicationHWnd == nullptr)
return;

if (!::IsWindowVisible(ApplicationHWnd))
{
::SetWindowPos(ApplicationHWnd, ApplicationSplashHWnd, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
return;
}

finishStartupSplash();
}

// W3DDisplay::W3DDisplay =====================================================
/** */
//=============================================================================
Expand Down Expand Up @@ -889,6 +927,13 @@ void W3DDisplay::init()
WW3D::Set_MSAA_Mode(WW3D::MultiSampleModeEnum::MULTISAMPLE_MODE_NONE);
#endif

// exclusive fullscreen needs the game window visible and in the foreground when the device is
// created, and the mode switch blanks the screen anyway, so hand over from the splash now
if (!getWindowed())
{
finishStartupSplash();
}

renderDeviceError = WW3D::Set_Render_Device(
0,
getWidth(),
Expand Down Expand Up @@ -1811,6 +1856,8 @@ void W3DDisplay::draw()
if (TheGlobalData->m_headless)
return;

updateStartupSplash();

// TheSuperHackers @feature bobtista 10/07/2026 Show messages for screenshots finished by the screenshot thread.
W3D_UpdateScreenshotMessages();

Expand Down
106 changes: 99 additions & 7 deletions GeneralsMD/Code/Main/WinMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,82 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message,

}

// SplashWndProc ==============================================================
/** Paints the load screen bitmap; the bitmap lives exactly as long as this window. */
//=============================================================================
static LRESULT CALLBACK SplashWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_PAINT:
{
PAINTSTRUCT paint;
HDC dc = ::BeginPaint(hWnd, &paint);
if (gLoadScreenBitmap != nullptr)
{
RECT client;
::GetClientRect(hWnd, &client);
HDC tmpDC = ::CreateCompatibleDC(dc);
HBITMAP savBitmap = (HBITMAP)::SelectObject(tmpDC, gLoadScreenBitmap);
::BitBlt(dc, 0, 0, client.right, client.bottom, tmpDC, 0, 0, SRCCOPY);
::SelectObject(tmpDC, savBitmap);
::DeleteDC(tmpDC);
}
::EndPaint(hWnd, &paint);
return 0;
}

case WM_ERASEBKGND:
return TRUE; // the bitmap covers the whole window

case WM_NCDESTROY:
if (gLoadScreenBitmap != nullptr)
{
::DeleteObject(gLoadScreenBitmap);
gLoadScreenBitmap = nullptr;
}
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}

// createSplashWindow =========================================================
/** Frameless window with the load screen bitmap, centred on the primary monitor. */
//=============================================================================
static HWND createSplashWindow(HINSTANCE hInstance, HICON icon)
{
BITMAP bitmap;
if (gLoadScreenBitmap == nullptr || ::GetObject(gLoadScreenBitmap, sizeof(bitmap), &bitmap) == 0
|| bitmap.bmWidth <= 0 || bitmap.bmHeight <= 0)
{
return nullptr;
}

WNDCLASS splashClass = { 0, SplashWndProc, 0, 0, hInstance, icon,
LoadCursor(nullptr, IDC_APPSTARTING),
(HBRUSH)GetStockObject(BLACK_BRUSH), nullptr,
TEXT("Game Splash") };
if (RegisterClass(&splashClass) == 0)
{
return nullptr;
}

const POINT origin = { 0, 0 };
MONITORINFO monitorInfo = { sizeof(MONITORINFO) };
GetMonitorInfo(MonitorFromPoint(origin, MONITOR_DEFAULTTOPRIMARY), &monitorInfo);
const Int x = (monitorInfo.rcWork.left + monitorInfo.rcWork.right - bitmap.bmWidth) / 2;
const Int y = (monitorInfo.rcWork.top + monitorInfo.rcWork.bottom - bitmap.bmHeight) / 2;

HWND hWnd = CreateWindow(TEXT("Game Splash"), TEXT("Command and Conquer Generals"), WS_POPUP | WS_VISIBLE,
x, y, bitmap.bmWidth, bitmap.bmHeight, nullptr, nullptr, hInstance, nullptr);
if (hWnd != nullptr)
{
SetForegroundWindow(hWnd);
UpdateWindow(hWnd);
}
return hWnd;
}

// initializeAppWindows =======================================================
/** Register windows class and create application windows. */
//=============================================================================
Expand All @@ -709,8 +785,14 @@ static Bool initializeAppWindows(HINSTANCE hInstance, Int nCmdShow, Bool runWind
TEXT("Game Window") };
RegisterClass(&wndClass);

// The splash gets its own frameless window; the game window stays hidden until W3DDisplay hands over,
// so it never shows stale splash pixels while its frame, size and render device are set up
ApplicationSplashHWnd = createSplashWindow(hInstance, wndClass.hIcon);

// Create our main window
windowStyle = WS_POPUP | WS_VISIBLE;
windowStyle = WS_POPUP;
if (ApplicationSplashHWnd == nullptr)
windowStyle |= WS_VISIBLE;
if (runWindowed)
windowStyle |= WS_MINIMIZEBOX | WS_SYSMENU | WS_DLGFRAME | WS_CAPTION;
else
Expand Down Expand Up @@ -759,16 +841,19 @@ static Bool initializeAppWindows(HINSTANCE hInstance, Int nCmdShow, Bool runWind
else
SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);

SetFocus(hWnd);
if (ApplicationSplashHWnd == nullptr)
{
SetFocus(hWnd);

SetForegroundWindow(hWnd);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
SetForegroundWindow(hWnd);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
}

// save our application window handle for future use
ApplicationHWnd = hWnd;
gInitializing = false;
if (!runWindowed) {
if (!runWindowed || ApplicationSplashHWnd != nullptr) {
gDoPaint = false;
}

Expand Down Expand Up @@ -895,7 +980,8 @@ Int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
// save our application instance for future use
ApplicationHInstance = hInstance;

if (gLoadScreenBitmap != nullptr) {
// the splash window owns the bitmap while it exists
if (gLoadScreenBitmap != nullptr && ApplicationSplashHWnd == nullptr) {
::DeleteObject(gLoadScreenBitmap);
gLoadScreenBitmap = nullptr;
}
Expand Down Expand Up @@ -946,6 +1032,12 @@ Int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
// run the game main loop
exitcode = GameMain();

// startup ended before the first frame was drawn
if (ApplicationSplashHWnd != nullptr) {
::DestroyWindow(ApplicationSplashHWnd);
ApplicationSplashHWnd = nullptr;
}

delete TheVersion;
TheVersion = nullptr;

Expand Down
1 change: 1 addition & 0 deletions GeneralsMD/Code/Main/WinMain.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@
// EXTERNAL ///////////////////////////////////////////////////////////////////
extern HINSTANCE ApplicationHInstance; ///< our application instance
extern HWND ApplicationHWnd; ///< our application window handle
extern HWND ApplicationSplashHWnd; ///< startup splash window, until W3DDisplay hands over to the game window
extern Win32Mouse *TheWin32Mouse; ///< global for win32 mouse only!
Loading