From cb9c20dbb644d1a519be4305166885304282b465 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Thu, 30 Apr 2026 09:24:43 +1000 Subject: [PATCH 001/523] build(compat): add Win32 shim headers for non-Windows builds (cherry picked from commit dc026f800e90188fbf2f11d0e3b60f71ce984387) --- .../WWVegas/compat/win32_shims/d3d8_iids.h | 25 + .../WWVegas/compat/win32_shims/dinput.h | 115 ++ .../WWVegas/compat/win32_shims/direct.h | 15 + .../WWVegas/compat/win32_shims/file_compat.h | 291 ++++ .../Source/WWVegas/compat/win32_shims/io.h | 83 ++ .../WWVegas/compat/win32_shims/mbstring.h | 5 + .../WWVegas/compat/win32_shims/mmsystem.h | 4 + .../Source/WWVegas/compat/win32_shims/new.h | 6 + .../WWVegas/compat/win32_shims/objbase.h | 139 ++ .../WWVegas/compat/win32_shims/process.h | 49 + .../WWVegas/compat/win32_shims/sys/timeb.h | 35 + .../WWVegas/compat/win32_shims/windows.h | 1192 +++++++++++++++++ .../WWVegas/compat/win32_shims/windowsx.h | 3 + .../WWVegas/compat/win32_shims/winsock.h | 102 ++ Core/Libraries/Source/WWVegas/osdep.h | 23 + Core/Libraries/Source/WWVegas/osdep/osdep.h | 21 + 16 files changed, 2108 insertions(+) create mode 100644 Core/Libraries/Source/WWVegas/compat/win32_shims/d3d8_iids.h create mode 100644 Core/Libraries/Source/WWVegas/compat/win32_shims/dinput.h create mode 100644 Core/Libraries/Source/WWVegas/compat/win32_shims/direct.h create mode 100644 Core/Libraries/Source/WWVegas/compat/win32_shims/file_compat.h create mode 100644 Core/Libraries/Source/WWVegas/compat/win32_shims/io.h create mode 100644 Core/Libraries/Source/WWVegas/compat/win32_shims/mbstring.h create mode 100644 Core/Libraries/Source/WWVegas/compat/win32_shims/mmsystem.h create mode 100644 Core/Libraries/Source/WWVegas/compat/win32_shims/new.h create mode 100644 Core/Libraries/Source/WWVegas/compat/win32_shims/objbase.h create mode 100644 Core/Libraries/Source/WWVegas/compat/win32_shims/process.h create mode 100644 Core/Libraries/Source/WWVegas/compat/win32_shims/sys/timeb.h create mode 100644 Core/Libraries/Source/WWVegas/compat/win32_shims/windows.h create mode 100644 Core/Libraries/Source/WWVegas/compat/win32_shims/windowsx.h create mode 100644 Core/Libraries/Source/WWVegas/compat/win32_shims/winsock.h create mode 100644 Core/Libraries/Source/WWVegas/osdep.h create mode 100644 Core/Libraries/Source/WWVegas/osdep/osdep.h diff --git a/Core/Libraries/Source/WWVegas/compat/win32_shims/d3d8_iids.h b/Core/Libraries/Source/WWVegas/compat/win32_shims/d3d8_iids.h new file mode 100644 index 00000000000..56948528196 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/d3d8_iids.h @@ -0,0 +1,25 @@ +#pragma once + +// TheSuperHackers @build bobtista 29/04/2026 The bundled d3d8.h gates its +// IID_IDirect3D* DEFINE_GUID block on `#if defined(_WIN32)`, so on macOS/Linux +// the IIDs are not declared. Stub callers that compare riid values still need +// them — define them here for non-Windows builds. + +#ifndef _WIN32 + +#include "objbase.h" + +DEFINE_GUID(IID_IDirect3D8, 0x1dd9e8da, 0x1c77, 0x4d40, 0xb0, 0xcf, 0x98, 0xfe, 0xfd, 0xff, 0x95, 0x12); +DEFINE_GUID(IID_IDirect3DDevice8, 0x7385e5df, 0x8fe8, 0x41d5, 0x86, 0xb6, 0xd7, 0xb4, 0x85, 0x47, 0xb6, 0xcf); +DEFINE_GUID(IID_IDirect3DResource8, 0x1b36bb7b, 0x09b7, 0x410a, 0xb4, 0x45, 0x7d, 0x14, 0x30, 0xd7, 0xb3, 0x3f); +DEFINE_GUID(IID_IDirect3DBaseTexture8, 0xb4211cfa, 0x51b9, 0x4a9f, 0xab, 0x78, 0xdb, 0x99, 0xb2, 0xbb, 0x67, 0x8e); +DEFINE_GUID(IID_IDirect3DTexture8, 0xe4cdd575, 0x2866, 0x4f01, 0xb1, 0x2e, 0x7e, 0xec, 0xe1, 0xec, 0x93, 0x58); +DEFINE_GUID(IID_IDirect3DCubeTexture8, 0x3ee5b968, 0x2aca, 0x4c34, 0x8b, 0xb5, 0x7e, 0x0c, 0x3d, 0x19, 0xb7, 0x50); +DEFINE_GUID(IID_IDirect3DVolumeTexture8, 0x4b8aaafa, 0x140f, 0x42ba, 0x91, 0x31, 0x59, 0x7e, 0xaf, 0xaa, 0x2e, 0xad); +DEFINE_GUID(IID_IDirect3DVertexBuffer8, 0x8aeeeac7, 0x05f9, 0x44d4, 0xb5, 0x91, 0x00, 0x0b, 0x0d, 0xf1, 0xcb, 0x95); +DEFINE_GUID(IID_IDirect3DIndexBuffer8, 0x0e689c9a, 0x053d, 0x44a0, 0x9d, 0x92, 0xdb, 0x0e, 0x3d, 0x75, 0x0f, 0x86); +DEFINE_GUID(IID_IDirect3DSurface8, 0xb96eebca, 0xb326, 0x4ea5, 0x88, 0x2f, 0x2f, 0xf5, 0xba, 0xe0, 0x21, 0xdd); +DEFINE_GUID(IID_IDirect3DVolume8, 0xbd7349f5, 0x14f1, 0x42e4, 0x9c, 0x79, 0x97, 0x23, 0x80, 0xdb, 0x40, 0xc0); +DEFINE_GUID(IID_IDirect3DSwapChain8, 0x928c088b, 0x76b9, 0x4c6b, 0xa5, 0x36, 0xa5, 0x90, 0x85, 0x38, 0x76, 0xcd); + +#endif // !_WIN32 diff --git a/Core/Libraries/Source/WWVegas/compat/win32_shims/dinput.h b/Core/Libraries/Source/WWVegas/compat/win32_shims/dinput.h new file mode 100644 index 00000000000..2f9e8dc0abb --- /dev/null +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/dinput.h @@ -0,0 +1,115 @@ +#pragma once + +enum DInputKeys +{ + // keypad keys ---------------------------------------------------------------- + DIK_NUMPAD0, + DIK_NUMPAD1, + DIK_NUMPAD2, + DIK_NUMPAD3, + DIK_NUMPAD4, + DIK_NUMPAD5, + DIK_NUMPAD6, + DIK_NUMPAD7, + DIK_NUMPAD8, + DIK_NUMPAD9, + DIK_NUMPADPERIOD, + DIK_NUMPADSTAR, + DIK_NUMPADMINUS, + DIK_NUMPADPLUS, + DIK_ESCAPE, + DIK_BACK, + DIK_RETURN, + DIK_SPACE, + DIK_TAB, + + DIK_F1, + DIK_F2, + DIK_F3, + DIK_F4, + DIK_F5, + DIK_F6, + DIK_F7, + DIK_F8, + DIK_F9, + DIK_F10, + DIK_F11, + DIK_F12, + DIK_A, + DIK_B, + DIK_C, + DIK_D, + DIK_E, + DIK_F, + DIK_G, + DIK_H, + DIK_I, + DIK_J, + DIK_K, + DIK_L, + DIK_M, + DIK_N, + DIK_O, + DIK_P, + DIK_Q, + DIK_R, + DIK_S, + DIK_T, + DIK_U, + DIK_V, + DIK_W, + DIK_X, + DIK_Y, + DIK_Z, + DIK_1, + DIK_2, + DIK_3, + DIK_4, + DIK_5, + DIK_6, + DIK_7, + DIK_8, + DIK_9, + DIK_0, + DIK_MINUS, + DIK_EQUALS, + DIK_LBRACKET, + DIK_RBRACKET, + DIK_SEMICOLON, + DIK_APOSTROPHE, + DIK_GRAVE, + DIK_BACKSLASH, + DIK_COMMA, + DIK_PERIOD, + DIK_SLASH, + + // special keys --------------------------------------------------------------- + DIK_SYSRQ, + + DIK_CAPSLOCK, + DIK_NUMLOCK, + DIK_SCROLL, + DIK_LCONTROL, + DIK_LALT, + DIK_LSHIFT, + DIK_RSHIFT, + + DIK_UPARROW, + DIK_DOWNARROW, + DIK_LEFTARROW, + DIK_RIGHTARROW, + DIK_RALT, + DIK_RCONTROL, + DIK_HOME, + DIK_END, + DIK_PGUP, + DIK_PGDN, + DIK_INSERT, + DIK_DELETE, + DIK_NUMPADENTER, + DIK_NUMPADSLASH, +}; + +typedef struct DIRECTINPUT8 *LPDIRECTINPUT8; +typedef struct DIRECTINPUTDEVICE8 *LPDIRECTINPUTDEVICE8; +typedef struct DIDEVICEOBJECTDATA DIDEVICEOBJECTDATA; \ No newline at end of file diff --git a/Core/Libraries/Source/WWVegas/compat/win32_shims/direct.h b/Core/Libraries/Source/WWVegas/compat/win32_shims/direct.h new file mode 100644 index 00000000000..0cacfa685bf --- /dev/null +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/direct.h @@ -0,0 +1,15 @@ +#pragma once + +// TheSuperHackers @build bobtista 29/04/2026 direct.h compatibility shim for +// non-Windows builds. Maps Win _chdir/_mkdir/_getcwd to POSIX equivalents via +// inline wrappers so the substitution doesn't fire inside system headers. + +#include "windows.h" + +#include +#include + +inline int _chdir(const char *path) { return chdir(path); } +// TheSuperHackers @build bobtista 29/04/2026 _mkdir is provided by file_compat.h. +inline int _rmdir(const char *path) { return rmdir(path); } +inline char *_getcwd(char *buf, int size) { return getcwd(buf, size); } diff --git a/Core/Libraries/Source/WWVegas/compat/win32_shims/file_compat.h b/Core/Libraries/Source/WWVegas/compat/win32_shims/file_compat.h new file mode 100644 index 00000000000..edfe80f0354 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/file_compat.h @@ -0,0 +1,291 @@ +#pragma once + +#include +#include + +#define _access access +#define _stat stat + +// GeneralsX @build BenderAI 10/02/2026 - Win32 file API → POSIX +#ifndef _WIN32 + +#include +#include +#include +#include +#include +#include + +// GeneralsX @TheSuperHackers @build BenderAI 11/02/2026 Windows _mkdir → POSIX mkdir (with default perms) +inline int _mkdir(const char* path) { + return mkdir(path, 0755); // rwxr-xr-x +} + +// Windows file attributes → POSIX stat +inline uint32_t GetFileAttributes(const char* path) { + struct stat st; + if (stat(path, &st) != 0) { + return 0xFFFFFFFF; // INVALID_FILE_ATTRIBUTES + } + return 0; // FILE_ATTRIBUTE_NORMAL +} + +// Windows current directory → POSIX getcwd +inline uint32_t GetCurrentDirectory(uint32_t buflen, char* buf) { + if (getcwd(buf, buflen) != nullptr) { + return static_cast(strlen(buf)); + } + return 0; +} + +// GeneralsX @TheSuperHackers @build BenderAI 11/02/2026 Win32 file system APIs → std::filesystem (C++17) +// SetCurrentDirectory - change working directory +inline int SetCurrentDirectory(const char* path) { + try { + std::filesystem::current_path(path); + return 1; // TRUE + } catch (...) { + return 0; // FALSE + } +} + +// Note: CreateDirectory() is in socket_compat.h (avoid duplicate) + +// DeleteFile - delete file +inline int DeleteFile(const char* path) { + try { + return std::filesystem::remove(path) ? 1 : 0; + } catch (...) { + return 0; // FALSE + } +} + +// GeneralsX @build BenderAI 12/02/2026 CopyFile stub for Linux +// Replay save system needs this to duplicate .rep files +inline int CopyFile(const char* existingFile, const char* newFile, int failIfExists) { + try { + std::filesystem::copy_options opts = failIfExists + ? std::filesystem::copy_options::none + : std::filesystem::copy_options::overwrite_existing; + std::filesystem::copy_file(existingFile, newFile, opts); + return 1; // TRUE + } catch (...) { + return 0; // FALSE + } +} + +// GeneralsX @build BenderAI 12/02/2026 FormatMessageW stub for Linux +// Used to format error messages from GetLastError() - always returns generic error +#define FORMAT_MESSAGE_FROM_SYSTEM 0x00001000 +inline int FormatMessageW(unsigned long flags, const void* source, unsigned long messageId, + unsigned long languageId, wchar_t* buffer, unsigned long size, void* args) { + if (buffer && size > 0) { + // Generic error message in wide string + const wchar_t* msg = L"File operation failed"; + size_t len = 0; + while (msg[len] && len < size - 1) { + buffer[len] = msg[len]; + len++; + } + buffer[len] = 0; + return (int)len; + } + return 0; +} + +// GeneralsX @build BenderAI 12/02/2026 FormatMessage stub (ASCII version) +// Used by ReplayMenu.cpp for error message formatting +inline int FormatMessage(unsigned long flags, const void* source, unsigned long messageId, + unsigned long languageId, char* buffer, unsigned long size, void* args) { + if (buffer && size > 0) { + const char* msg = "File operation failed"; + size_t len = 0; + while (msg[len] && len < size - 1) { + buffer[len] = msg[len]; + len++; + } + buffer[len] = 0; + return (int)len; + } + return 0; +} + +// GeneralsX @build BenderAI 12/02/2026 Windows Shell API stubs +// Used by ReplayMenu.cpp to get desktop folder path - stubbed for Linux +typedef void* LPITEMIDLIST; // Pointer to item ID list (shell folder identifier) +#define CSIDL_DESKTOPDIRECTORY 0x0010 // Desktop folder constant + +// Get special folder location (always fails on Linux) +inline int SHGetSpecialFolderLocation(void* hwndOwner, int nFolder, LPITEMIDLIST* ppidl) { + if (ppidl) *ppidl = nullptr; + return -1; // E_FAIL +} + +// Get path from item ID list (always returns false) +inline int SHGetPathFromIDList(LPITEMIDLIST pidl, char* pszPath) { + return 0; // FALSE +} + +// WIN32_FIND_DATA structure for file iteration +#ifndef MAX_PATH +#define MAX_PATH 260 +#endif + +#define FILE_ATTRIBUTE_DIRECTORY 0x10 + +struct WIN32_FIND_DATA { + uint32_t dwFileAttributes; + char cFileName[MAX_PATH]; +}; + +// FindFirstFile / FindNextFile / FindClose - directory iteration +// Simple wrapper using readdir (POSIX) +struct FIND_HANDLE_DATA { + DIR* dir; + struct dirent* entry; + char pattern[MAX_PATH]; + char dirpath[MAX_PATH]; +}; + +// GeneralsX @bugfix BenderAI 30/03/2026 - POSIX FindFirstFile: honor directory and wildcard pattern +inline void* FindFirstFile(const char* pattern, WIN32_FIND_DATA* findData) { + if (!pattern || !findData) return (void*)-1; + + FIND_HANDLE_DATA* handle = new FIND_HANDLE_DATA(); + + // Parse pattern into directory and filename mask + try { + std::filesystem::path p(pattern); + std::string dir = p.parent_path().string(); + std::string mask = p.filename().string(); + + if (dir.empty()) dir = "."; + + // Store dirpath and pattern + strncpy(handle->dirpath, dir.c_str(), MAX_PATH - 1); + handle->dirpath[MAX_PATH - 1] = '\0'; + strncpy(handle->pattern, mask.c_str(), MAX_PATH - 1); + handle->pattern[MAX_PATH - 1] = '\0'; + + handle->dir = opendir(handle->dirpath); + if (!handle->dir) { + delete handle; + return (void*)-1; // INVALID_HANDLE_VALUE + } + } catch (...) { + delete handle; + return (void*)-1; + } + + // Iterate until we find a matching entry or exhaust + while (true) { + handle->entry = readdir(handle->dir); + if (!handle->entry) { + closedir(handle->dir); + delete handle; + return (void*)-1; // no matches + } + + // Apply wildcard matching using fnmatch + if (fnmatch(handle->pattern, handle->entry->d_name, 0) == 0) { + // Match found + strncpy(findData->cFileName, handle->entry->d_name, MAX_PATH - 1); + findData->cFileName[MAX_PATH - 1] = '\0'; + findData->dwFileAttributes = (handle->entry->d_type == DT_DIR) ? FILE_ATTRIBUTE_DIRECTORY : 0; + return handle; + } + + // otherwise continue searching + } +} + +// GeneralsX @bugfix BenderAI 30/03/2026 - POSIX FindNextFile: continue applying wildcard filter +inline int FindNextFile(void* hFindFile, WIN32_FIND_DATA* findData) { + if (!hFindFile || hFindFile == (void*)-1 || !findData) { + return 0; // FALSE + } + + FIND_HANDLE_DATA* handle = static_cast(hFindFile); + + while (true) { + handle->entry = readdir(handle->dir); + if (!handle->entry) { + return 0; // FALSE - no more files + } + + if (fnmatch(handle->pattern, handle->entry->d_name, 0) == 0) { + strncpy(findData->cFileName, handle->entry->d_name, MAX_PATH - 1); + findData->cFileName[MAX_PATH - 1] = '\0'; + findData->dwFileAttributes = (handle->entry->d_type == DT_DIR) ? FILE_ATTRIBUTE_DIRECTORY : 0; + return 1; // TRUE + } + + // else continue loop to next entry + } +} + +inline int FindClose(void* hFindFile) { + if (!hFindFile || hFindFile == (void*)-1) { + return 1; // TRUE + } + + FIND_HANDLE_DATA* handle = static_cast(hFindFile); + if (handle->dir) { + closedir(handle->dir); + } + delete handle; + return 1; // TRUE +} + +// GeneralsX @build BenderAI 12/02/2026 GetDateFormat stub for age verification +// Note: LOCALE_SYSTEM_DEFAULT already defined in windows_compat.h (0x0800) +// Note: SYSTEMTIME already defined in time_compat.h +inline int GetDateFormat(unsigned long locale, unsigned long flags, const SYSTEMTIME* lpDate, + const char* lpFormat, char* lpDateStr, int cchDate) { + if (!lpDateStr || cchDate <= 0) { + return 0; // Failure + } + + // Get current time if lpDate is nullptr + time_t now = time(nullptr); + struct tm* timeinfo = (lpDate == nullptr) ? localtime(&now) : nullptr; + + // If lpDate provided, convert SYSTEMTIME to tm + struct tm custom_time; + if (lpDate != nullptr) { + custom_time.tm_year = lpDate->wYear - 1900; + custom_time.tm_mon = lpDate->wMonth - 1; + custom_time.tm_mday = lpDate->wDay; + custom_time.tm_hour = lpDate->wHour; + custom_time.tm_min = lpDate->wMinute; + custom_time.tm_sec = lpDate->wSecond; + custom_time.tm_wday = lpDate->wDayOfWeek; + custom_time.tm_isdst = -1; + timeinfo = &custom_time; + } + + if (!timeinfo) { + return 0; // Failure + } + + // Simple format string handling (supports common patterns used in the game) + const char* format_to_use = "%Y-%m-%d"; // Default fallback + if (lpFormat) { + if (strcmp(lpFormat, "yyyy") == 0) { + format_to_use = "%Y"; + } else if (strcmp(lpFormat, "MM") == 0) { + format_to_use = "%m"; + } else if (strcmp(lpFormat, "dd") == 0) { + format_to_use = "%d"; + } else if (strcmp(lpFormat, "yyyy-MM-dd") == 0 || strcmp(lpFormat, "yyyy/MM/dd") == 0) { + format_to_use = "%Y-%m-%d"; + } + } + + size_t result = strftime(lpDateStr, cchDate, format_to_use, timeinfo); + return (result > 0) ? static_cast(result) : 0; +} + +#define INVALID_HANDLE_VALUE ((void*)-1) + +#endif // !_WIN32 diff --git a/Core/Libraries/Source/WWVegas/compat/win32_shims/io.h b/Core/Libraries/Source/WWVegas/compat/win32_shims/io.h new file mode 100644 index 00000000000..4e3fb7d63f5 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/io.h @@ -0,0 +1,83 @@ +#pragma once + +// TheSuperHackers @build bobtista 29/04/2026 io.h compatibility shim for +// non-Windows builds. Maps Win _open/_close/etc. to POSIX equivalents via +// inline wrappers so the substitution doesn't accidentally fire inside +// system headers (e.g. declares its own close()). + +#include "windows.h" + +#include +#include +#include + +#include + +inline int _close(int fd) { return close(fd); } +inline ssize_t _read(int fd, void *buf, size_t count) { return read(fd, buf, count); } +inline ssize_t _write(int fd, const void *buf, size_t count) { return write(fd, buf, count); } +inline off_t _lseek(int fd, off_t off, int whence) { return lseek(fd, off, whence); } +inline off_t _tell(int fd) { return lseek(fd, 0, SEEK_CUR); } + +inline int _open(const char *path, int flags, ...) +{ + int mode = 0; + if (flags & O_CREAT) + { + va_list ap; + va_start(ap, flags); + mode = va_arg(ap, int); + va_end(ap); + } + return open(path, flags, mode); +} + +inline long _filelength(int fd) +{ + struct stat st; + if (fstat(fd, &st) != 0) + { + return -1L; + } + return static_cast(st.st_size); +} + +#ifndef _O_RDONLY +#define _O_RDONLY O_RDONLY +#endif + +#ifndef _O_WRONLY +#define _O_WRONLY O_WRONLY +#endif + +#ifndef _O_RDWR +#define _O_RDWR O_RDWR +#endif + +#ifndef _O_CREAT +#define _O_CREAT O_CREAT +#endif + +#ifndef _O_TRUNC +#define _O_TRUNC O_TRUNC +#endif + +#ifndef _O_APPEND +#define _O_APPEND O_APPEND +#endif + +#ifndef _O_BINARY +#define _O_BINARY 0 +#endif + +#ifndef _O_TEXT +#define _O_TEXT 0 +#endif + +#ifndef _S_IREAD +#define _S_IREAD S_IRUSR +#endif + +#ifndef _S_IWRITE +#define _S_IWRITE S_IWUSR +#endif diff --git a/Core/Libraries/Source/WWVegas/compat/win32_shims/mbstring.h b/Core/Libraries/Source/WWVegas/compat/win32_shims/mbstring.h new file mode 100644 index 00000000000..9f648da40bb --- /dev/null +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/mbstring.h @@ -0,0 +1,5 @@ +#pragma once + +// TheSuperHackers @build bobtista 29/04/2026 mbstring.h compat shim. The Win +// CRT exposes _mbsXxx multi-byte string helpers; on POSIX we don't use them +// so this is intentionally empty. Add stubs as needed if a TU references one. diff --git a/Core/Libraries/Source/WWVegas/compat/win32_shims/mmsystem.h b/Core/Libraries/Source/WWVegas/compat/win32_shims/mmsystem.h new file mode 100644 index 00000000000..3603637d99f --- /dev/null +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/mmsystem.h @@ -0,0 +1,4 @@ +#pragma once + +#include "windows.h" +#include "Utility/time_compat.h" diff --git a/Core/Libraries/Source/WWVegas/compat/win32_shims/new.h b/Core/Libraries/Source/WWVegas/compat/win32_shims/new.h new file mode 100644 index 00000000000..6a6cc53ab92 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/new.h @@ -0,0 +1,6 @@ +#pragma once + +// TheSuperHackers @build bobtista 29/04/2026 compat shim. Win SDK +// provides this; POSIX systems use . + +#include diff --git a/Core/Libraries/Source/WWVegas/compat/win32_shims/objbase.h b/Core/Libraries/Source/WWVegas/compat/win32_shims/objbase.h new file mode 100644 index 00000000000..5a756a3f3a4 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/objbase.h @@ -0,0 +1,139 @@ +#pragma once + +#include "windows.h" + +#include + +#ifndef GUID_DEFINED +#define GUID_DEFINED +typedef struct GUID { + unsigned long Data1; + unsigned short Data2; + unsigned short Data3; + unsigned char Data4[8]; +} GUID; +#endif + +#ifndef REFGUID +#ifdef __cplusplus +typedef const GUID &REFGUID; +typedef const GUID &REFIID; +typedef const GUID &REFCLSID; +#else +typedef const GUID *REFGUID; +typedef const GUID *REFIID; +typedef const GUID *REFCLSID; +#endif +#endif + +typedef GUID IID; +typedef GUID CLSID; +typedef GUID *LPGUID; + +#ifndef EXTERN_C +#ifdef __cplusplus +#define EXTERN_C extern "C" +#else +#define EXTERN_C extern +#endif +#endif + +#ifndef STDAPI +#define STDAPI EXTERN_C HRESULT STDMETHODCALLTYPE +#endif + +#ifndef STDAPI_ +#define STDAPI_(type) EXTERN_C type STDMETHODCALLTYPE +#endif + +#ifndef DEFINE_GUID +#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + static const GUID name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } +#endif + +// IID_IUnknown — the universal COM interface ID. Defined here so non-Windows +// builds that talk to COM stubs (StubD3D8Device, etc.) can compare against it. +#ifndef IID_IUnknown_DEFINED +#define IID_IUnknown_DEFINED +DEFINE_GUID(IID_IUnknown, 0x00000000, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); +#endif + +// GUID equality. Windows provides this via guiddef.h; our shim has to. +#ifndef _GUID_OPERATORS_DEFINED +#define _GUID_OPERATORS_DEFINED +#include +inline bool operator==(const GUID &a, const GUID &b) +{ + return memcmp(&a, &b, sizeof(GUID)) == 0; +} +inline bool operator!=(const GUID &a, const GUID &b) +{ + return !(a == b); +} +#endif + +#ifndef interface +#define interface struct +#endif + +#ifndef DECLSPEC_NOVTABLE +#define DECLSPEC_NOVTABLE +#endif + +#ifndef PURE +#define PURE = 0 +#endif + +#ifndef THIS_ +#define THIS_ +#endif + +#ifndef THIS +#define THIS void +#endif + +#ifndef STDMETHODCALLTYPE +#define STDMETHODCALLTYPE WINAPI +#endif + +#ifndef STDMETHOD +#define STDMETHOD(method) virtual HRESULT STDMETHODCALLTYPE method +#endif + +#ifndef STDMETHOD_ +#define STDMETHOD_(type, method) virtual type STDMETHODCALLTYPE method +#endif + +#ifndef DECLARE_INTERFACE +#define DECLARE_INTERFACE(iface) interface DECLSPEC_NOVTABLE iface +#endif + +#ifndef DECLARE_INTERFACE_ +#define DECLARE_INTERFACE_(iface, baseiface) interface DECLSPEC_NOVTABLE iface : public baseiface +#endif + +#ifndef __IUnknown_INTERFACE_DEFINED__ +#define __IUnknown_INTERFACE_DEFINED__ +DECLARE_INTERFACE(IUnknown) +{ + STDMETHOD(QueryInterface)(THIS_ REFIID riid, void **ppvObject) PURE; + STDMETHOD_(ULONG, AddRef)(THIS) PURE; + STDMETHOD_(ULONG, Release)(THIS) PURE; +}; +#endif + +#ifndef _IUNKNOWN_DEFINED +#define _IUNKNOWN_DEFINED +#endif + +#ifndef __IStream_FWD_DEFINED__ +#define __IStream_FWD_DEFINED__ +interface IStream; +#endif + +#ifndef IsEqualGUID +inline int IsEqualGUID(const GUID &a, const GUID &b) +{ + return memcmp(&a, &b, sizeof(GUID)) == 0; +} +#endif diff --git a/Core/Libraries/Source/WWVegas/compat/win32_shims/process.h b/Core/Libraries/Source/WWVegas/compat/win32_shims/process.h new file mode 100644 index 00000000000..07b315287fc --- /dev/null +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/process.h @@ -0,0 +1,49 @@ +#pragma once + +// TheSuperHackers @build bobtista 29/04/2026 process.h compatibility shim +// for non-Windows builds. _beginthread is mapped to pthread_create so legacy +// Westwood code that spawns helper threads still links. The opaque pthread_t +// type is not portably castable to uintptr_t on macOS, so the return value +// here is best-effort: 0 on failure, non-zero on success. + +#include "windows.h" + +#include + +typedef void (*PTHREAD_START_ROUTINE_VOID)(void *); + +inline uintptr_t _beginthread(PTHREAD_START_ROUTINE_VOID start, unsigned /*stack*/, void *arg) +{ + pthread_t tid; + if (pthread_create(&tid, nullptr, reinterpret_cast(start), arg) != 0) + { + return 0; + } + pthread_detach(tid); + return 1; +} + +inline void _endthread(void) +{ + pthread_exit(nullptr); +} + +// _spawnl mode constants. Real implementation isn't provided on non-Win; +// callers will see -1 (failure) which the engine handles gracefully. +#ifndef _P_WAIT +#define _P_WAIT 0 +#endif +#ifndef _P_NOWAIT +#define _P_NOWAIT 1 +#endif +#ifndef _P_OVERLAY +#define _P_OVERLAY 2 +#endif +#ifndef _P_NOWAITO +#define _P_NOWAITO 3 +#endif +#ifndef _P_DETACH +#define _P_DETACH 4 +#endif + +inline int _spawnl(int /*mode*/, const char * /*cmd*/, ...) { return -1; } diff --git a/Core/Libraries/Source/WWVegas/compat/win32_shims/sys/timeb.h b/Core/Libraries/Source/WWVegas/compat/win32_shims/sys/timeb.h new file mode 100644 index 00000000000..5fa11745279 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/sys/timeb.h @@ -0,0 +1,35 @@ +#pragma once + +// TheSuperHackers @build bobtista 29/04/2026 sys/timeb.h compatibility shim +// for non-Windows builds. Provides _ftime / struct _timeb backed by +// gettimeofday so legacy timing code keeps compiling. + +#include +#include + +#ifndef _TIMEB_DEFINED +#define _TIMEB_DEFINED +struct _timeb +{ + long time; + short millitm; + short timezone; + short dstflag; +}; +#endif + +#ifndef _ftime +inline void _ftime(struct _timeb *tb) +{ + if (tb == nullptr) + { + return; + } + struct timeval tv; + gettimeofday(&tv, nullptr); + tb->time = static_cast(tv.tv_sec); + tb->millitm = static_cast(tv.tv_usec / 1000); + tb->timezone = 0; + tb->dstflag = 0; +} +#endif diff --git a/Core/Libraries/Source/WWVegas/compat/win32_shims/windows.h b/Core/Libraries/Source/WWVegas/compat/win32_shims/windows.h new file mode 100644 index 00000000000..4ac8c141f40 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/windows.h @@ -0,0 +1,1192 @@ +#pragma once + +#include "bittype.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef CALLBACK +#define CALLBACK +#endif + +#ifndef WINAPI +#define WINAPI +#endif + +#ifndef __stdcall +#define __stdcall +#endif + +#ifndef __cdecl +#define __cdecl +#endif + +#ifndef __fastcall +#define __fastcall +#endif + +// MSVC string-comparison spellings. POSIX has the same APIs without the +// underscore. +#include +#ifndef _stricmp +#define _stricmp strcasecmp +#endif +#ifndef _strnicmp +#define _strnicmp strncasecmp +#endif +#ifndef stricmp +#define stricmp strcasecmp +#endif +#ifndef strnicmp +#define strnicmp strncasecmp +#endif +#ifndef _wcsicmp +#define _wcsicmp wcscasecmp +#endif + +// MSVC float-control register API (no-op on non-Win; the engine uses these to +// pin x87 precision, which doesn't apply on macOS/Linux x86_64/arm64 builds +// since SSE/scalar math has fixed precision). +#ifndef _MCW_RC +#define _MCW_RC 0x00000300 +#endif +#ifndef _RC_NEAR +#define _RC_NEAR 0x00000000 +#endif +#ifndef _RC_DOWN +#define _RC_DOWN 0x00000100 +#endif +#ifndef _RC_UP +#define _RC_UP 0x00000200 +#endif +#ifndef _RC_CHOP +#define _RC_CHOP 0x00000300 +#endif +#ifndef _MCW_PC +#define _MCW_PC 0x00030000 +#endif +#ifndef _PC_24 +#define _PC_24 0x00020000 +#endif +#ifndef _PC_53 +#define _PC_53 0x00010000 +#endif +#ifndef _PC_64 +#define _PC_64 0x00000000 +#endif +#ifndef _MCW_EM +#define _MCW_EM 0x0008001f +#endif + +inline void _fpreset() {} +inline unsigned int _controlfp(unsigned int /*newctrl*/, unsigned int /*mask*/) { return 0; } +inline unsigned int _statusfp() { return 0; } +inline void _clearfp() {} + +// _wtoi is the wide-char companion to atoi; provide a wcstol-based shim. +#include +#include +inline int _wtoi(const wchar_t *str) +{ + if (str == nullptr) + { + return 0; + } + return static_cast(::wcstol(str, nullptr, 10)); +} + +// itoa is non-standard; some Win headers expose it. Provide a snprintf-backed +// version so legacy callers keep working. +#include +inline char *itoa(int value, char *buffer, int radix) +{ + if (buffer == nullptr) + { + return nullptr; + } + if (radix == 10) + { + std::snprintf(buffer, 16, "%d", value); + } + else if (radix == 16) + { + std::snprintf(buffer, 16, "%x", value); + } + else if (radix == 8) + { + std::snprintf(buffer, 16, "%o", value); + } + else + { + std::snprintf(buffer, 16, "%d", value); + } + return buffer; +} + +// MSVC integer-size keywords. Westwood code spells 64-bit integers as +// `__int64` / `unsigned __int64`. Map to long long on non-Windows. +#ifndef __int64 +#define __int64 long long +#endif + +#ifndef _int64 +#define _int64 long long +#endif + +#ifndef __forceinline +#define __forceinline inline __attribute__((always_inline)) +#endif + +#ifndef DECLARE_HANDLE +#define DECLARE_HANDLE(name) typedef void *name +#endif + +typedef void *HANDLE; +typedef HANDLE HWND; +typedef HANDLE HINSTANCE; +typedef HANDLE HDC; +typedef HANDLE HGDIOBJ; +typedef HANDLE HBITMAP; +typedef HANDLE HFONT; +typedef HANDLE HKEY; +typedef HANDLE HMODULE; +// FARPROC must be a function pointer type so consumers can call through it. +typedef int (*FARPROC)(); + +#ifndef HMONITOR_DECLARED +#define HMONITOR_DECLARED +DECLARE_HANDLE(HMONITOR); +#endif + +typedef long LONG; +typedef int INT; +typedef float FLOAT; +typedef long HRESULT; +typedef void VOID; +typedef void *LPVOID; +typedef void *PVOID; +typedef const void *LPCVOID; +typedef const char *LPCSTR; +typedef char *LPSTR; +typedef BYTE *PBYTE; +typedef BYTE *LPBYTE; +typedef DWORD *LPDWORD; +typedef wchar_t *LPWSTR; +typedef const wchar_t *LPCWSTR; +typedef size_t SIZE_T; +typedef uintptr_t UINT_PTR; +typedef uintptr_t ULONG_PTR; +typedef uintptr_t DWORD_PTR; +typedef intptr_t INT_PTR; +typedef intptr_t LONG_PTR; + +typedef struct _SYSTEMTIME { + WORD wYear; + WORD wMonth; + WORD wDayOfWeek; + WORD wDay; + WORD wHour; + WORD wMinute; + WORD wSecond; + WORD wMilliseconds; +} SYSTEMTIME; + +typedef struct _FILETIME { + DWORD dwLowDateTime; + DWORD dwHighDateTime; +} FILETIME; + +typedef union _LARGE_INTEGER { + struct { + DWORD LowPart; + LONG HighPart; + }; + long long QuadPart; +} LARGE_INTEGER; + +typedef LONG *LPLONG; +typedef char CHAR; +typedef CHAR *LPCH; +typedef CHAR *PSTR; +// Win message-handler parameter types. +typedef uintptr_t WPARAM; +typedef intptr_t LPARAM; +typedef intptr_t LRESULT; +// PBITMAPINFO / PBITMAPINFOHEADER — pointer aliases the Win SDK adds. +struct tagBITMAPINFO; +struct tagBITMAPINFOHEADER; +typedef struct tagBITMAPINFO *PBITMAPINFO; +typedef struct tagBITMAPINFO *LPBITMAPINFO; +typedef struct tagBITMAPINFOHEADER *PBITMAPINFOHEADER; +typedef struct tagBITMAPINFOHEADER *LPBITMAPINFOHEADER; +inline int IsIconic(HWND) { return 0; } +typedef HANDLE HCURSOR; +inline HCURSOR SetCursor(HCURSOR) { return nullptr; } +typedef struct tagPOINT POINT; +inline int GetCursorPos(POINT *) { return 1; } +inline int ScreenToClient(HWND, POINT *) { return 1; } +inline int ClientToScreen(HWND, POINT *) { return 1; } +inline HANDLE GetProcessHeap() { return nullptr; } +inline void *HeapAlloc(HANDLE, DWORD, size_t bytes) { return std::calloc(1, bytes); } +inline int HeapFree(HANDLE, DWORD, void *p) { std::free(p); return 1; } +#ifndef HEAP_ZERO_MEMORY +#define HEAP_ZERO_MEMORY 0x00000008 +#endif +typedef void *HLOCAL; +inline void *LocalAlloc(unsigned int /*flags*/, size_t bytes) { return std::calloc(1, bytes); } +inline void *LocalFree(void *p) { std::free(p); return nullptr; } +#ifndef LPTR +#define LPTR 0x40 +#endif + +// Win file API constants/stubs for screenshot helper paths. +#ifndef GENERIC_READ +#define GENERIC_READ 0x80000000 +#endif +#ifndef GENERIC_WRITE +#define GENERIC_WRITE 0x40000000 +#endif +#ifndef CREATE_ALWAYS +#define CREATE_ALWAYS 2 +#endif +#ifndef OPEN_EXISTING +#define OPEN_EXISTING 3 +#endif +#ifndef FILE_ATTRIBUTE_NORMAL +#define FILE_ATTRIBUTE_NORMAL 0x80 +#endif +inline HANDLE CreateFile(const char *, DWORD, DWORD, void *, DWORD, DWORD, void *) { return nullptr; } +inline HANDLE CreateFileA(const char *, DWORD, DWORD, void *, DWORD, DWORD, void *) { return nullptr; } +inline int WriteFile(HANDLE, const void *, DWORD, DWORD *, void *) { return 0; } +inline int ReadFile(HANDLE, void *, DWORD, DWORD *, void *) { return 0; } + +// IDispatch stub for COM calls in W3DWebBrowser.cpp (Win-only path). +struct IDispatch; +typedef IDispatch *LPDISPATCH; +#ifndef OPTIONAL +#define OPTIONAL +#endif + +inline unsigned int GetDoubleClickTime() { return 500; } +inline const char *GetCommandLineA() { return ""; } +inline DWORD GetModuleFileName(HMODULE, char *, DWORD size) { (void)size; return 0; } +inline DWORD GetModuleFileNameA(HMODULE, char *, DWORD size) { (void)size; return 0; } +inline void GetLocalTime(SYSTEMTIME *t) { if (t) { *t = SYSTEMTIME{}; } } +inline DWORD GetLastError() { return static_cast(errno); } +inline void SetLastError(DWORD code) { errno = static_cast(code); } + +#include +inline int QueryPerformanceFrequency(LARGE_INTEGER *freq) +{ + if (freq == nullptr) { return 0; } + freq->QuadPart = 1000000000LL; + return 1; +} +inline int QueryPerformanceCounter(LARGE_INTEGER *cnt) +{ + if (cnt == nullptr) { return 0; } + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + cnt->QuadPart = static_cast(ts.tv_sec) * 1000000000LL + ts.tv_nsec; + return 1; +} + +// Win file enumeration API. Adapted from fbraz3/GeneralsX file_compat.h. +#include "file_compat.h" + +// Include the winsock shim for everyone — many engine TUs use socket types +// without explicitly including , relying on to pull +// it in transitively (which it does with WIN32_LEAN_AND_MEAN cleared). +#include "winsock.h" + +// OSVERSIONINFO + GetVersionEx stubs. Adapted from GeneralsX windows_compat.h. +typedef struct _OSVERSIONINFO { + DWORD dwOSVersionInfoSize; + DWORD dwMajorVersion; + DWORD dwMinorVersion; + DWORD dwBuildNumber; + DWORD dwPlatformId; + char szCSDVersion[128]; +} OSVERSIONINFO; + +inline int GetVersionEx(OSVERSIONINFO * /*info*/) { return 0; } + +typedef struct _MEMORYSTATUS { + DWORD dwLength; + DWORD dwMemoryLoad; + DWORD dwTotalPhys; + DWORD dwAvailPhys; + DWORD dwTotalPageFile; + DWORD dwAvailPageFile; + DWORD dwTotalVirtual; + DWORD dwAvailVirtual; +} MEMORYSTATUS, *LPMEMORYSTATUS; + +inline void GlobalMemoryStatus(MEMORYSTATUS *m) +{ + if (m == nullptr) + { + return; + } + *m = MEMORYSTATUS{}; + m->dwLength = sizeof(*m); +} + +// Win virtual-key codes (subset that engine GUI uses). +#ifndef VK_BACK +#define VK_BACK 0x08 +#endif +#ifndef VK_TAB +#define VK_TAB 0x09 +#endif +#ifndef VK_RETURN +#define VK_RETURN 0x0D +#endif +#ifndef VK_SHIFT +#define VK_SHIFT 0x10 +#endif +#ifndef VK_CONTROL +#define VK_CONTROL 0x11 +#endif +#ifndef VK_ESCAPE +#define VK_ESCAPE 0x1B +#endif +#ifndef VK_SPACE +#define VK_SPACE 0x20 +#endif +#ifndef VK_DELETE +#define VK_DELETE 0x2E +#endif +#ifndef VK_INSERT +#define VK_INSERT 0x2D +#endif +#ifndef VK_F1 +#define VK_F1 0x70 +#endif +#ifndef VK_F2 +#define VK_F2 0x71 +#endif +#ifndef VK_F3 +#define VK_F3 0x72 +#endif +#ifndef VK_F4 +#define VK_F4 0x73 +#endif +#ifndef VK_F5 +#define VK_F5 0x74 +#endif +#ifndef VK_F6 +#define VK_F6 0x75 +#endif +#ifndef VK_F7 +#define VK_F7 0x76 +#endif +#ifndef VK_F8 +#define VK_F8 0x77 +#endif +#ifndef VK_F9 +#define VK_F9 0x78 +#endif +#ifndef VK_F10 +#define VK_F10 0x79 +#endif +inline short GetAsyncKeyState(int) { return 0; } + +#ifndef LOCALE_SYSTEM_DEFAULT +#define LOCALE_SYSTEM_DEFAULT 0x0800 +#endif +#ifndef LOCALE_USER_DEFAULT +#define LOCALE_USER_DEFAULT 0x0400 +#endif +#ifndef DATE_SHORTDATE +#define DATE_SHORTDATE 0x0001 +#endif +#ifndef DATE_LONGDATE +#define DATE_LONGDATE 0x0002 +#endif +#ifndef TIME_NOSECONDS +#define TIME_NOSECONDS 0x0002 +#endif +#ifndef TIME_FORCE24HOURFORMAT +#define TIME_FORCE24HOURFORMAT 0x0008 +#endif +#ifndef TIME_NOTIMEMARKER +#define TIME_NOTIMEMARKER 0x0004 +#endif +#ifndef VER_PLATFORM_WIN32_WINDOWS +#define VER_PLATFORM_WIN32_WINDOWS 1 +#endif +#ifndef VER_PLATFORM_WIN32_NT +#define VER_PLATFORM_WIN32_NT 2 +#endif + +inline int GetTimeFormat(unsigned long, unsigned long, const SYSTEMTIME *, const char *, char *buf, int bufsize) +{ + if (buf != nullptr && bufsize > 0) { buf[0] = '\0'; } + return 0; +} +inline int GetDateFormatW(unsigned long, unsigned long, const SYSTEMTIME *, const wchar_t *, wchar_t *buf, int bufsize) +{ + if (buf != nullptr && bufsize > 0) { buf[0] = L'\0'; } + return 0; +} +inline int GetTimeFormatW(unsigned long, unsigned long, const SYSTEMTIME *, const wchar_t *, wchar_t *buf, int bufsize) +{ + if (buf != nullptr && bufsize > 0) { buf[0] = L'\0'; } + return 0; +} + +// Win critical section types backed by pthread_mutex. +#include +typedef pthread_mutex_t CRITICAL_SECTION; +inline void InitializeCriticalSection(CRITICAL_SECTION *cs) +{ + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(cs, &attr); + pthread_mutexattr_destroy(&attr); +} +inline void DeleteCriticalSection(CRITICAL_SECTION *cs) { pthread_mutex_destroy(cs); } +inline void EnterCriticalSection(CRITICAL_SECTION *cs) { pthread_mutex_lock(cs); } +inline void LeaveCriticalSection(CRITICAL_SECTION *cs) { pthread_mutex_unlock(cs); } +inline int CopyFileA(const char *, const char *, int) { return 0; } +inline int SetWindowText(HWND, const char *) { return 0; } +inline int SetWindowTextW(HWND, const wchar_t *) { return 0; } +inline int SetWindowTextA(HWND, const char *) { return 0; } +typedef HANDLE HKL; +inline HKL GetKeyboardLayout(DWORD) { return nullptr; } + +// Win threading. Stubbed; pthread is the real backend on POSIX, but the +// engine's thread-spawning paths are limited (network lobby pings, etc.). +typedef DWORD (*LPTHREAD_START_ROUTINE)(void *); +inline HANDLE CreateThread(void *, DWORD, LPTHREAD_START_ROUTINE, void *, DWORD, DWORD *) { return nullptr; } +inline int TerminateThread(HANDLE, DWORD) { return 0; } +inline int WaitForSingleObject(HANDLE, DWORD) { return 0; } +#ifndef INFINITE +#define INFINITE 0xFFFFFFFFu +#endif +#ifndef WAIT_OBJECT_0 +#define WAIT_OBJECT_0 0 +#endif +#ifndef WAIT_TIMEOUT +#define WAIT_TIMEOUT 258 +#endif + +// GlobalAlloc / GlobalFree — Win heap APIs, mapped to malloc/free. +#ifndef GMEM_FIXED +#define GMEM_FIXED 0 +#endif +#ifndef GMEM_ZEROINIT +#define GMEM_ZEROINIT 0x40 +#endif +inline void *GlobalAlloc(unsigned int flags, size_t bytes) +{ + void *p = std::malloc(bytes); + if (p && (flags & GMEM_ZEROINIT)) + { + std::memset(p, 0, bytes); + } + return p; +} +inline void *GlobalFree(void *p) { std::free(p); return nullptr; } +inline size_t GlobalSize(void * /*p*/) { return 0; } +inline void *GlobalReAlloc(void *p, size_t bytes, unsigned int /*flags*/) { return std::realloc(p, bytes); } + +inline int AddFontResource(const char *) { return 0; } +inline int AddFontResourceA(const char *) { return 0; } +inline int RemoveFontResource(const char *) { return 0; } +inline int RemoveFontResourceA(const char *) { return 0; } +inline HANDLE CreateMutex(void *, int, const char *) { return nullptr; } +inline HANDLE CreateMutexW(void *, int, const wchar_t *) { return nullptr; } +inline HANDLE CreateMutexA(void *, int, const char *) { return nullptr; } +inline int CloseHandle(HANDLE) { return 0; } +#ifndef ERROR_ALREADY_EXISTS +#define ERROR_ALREADY_EXISTS 183 +#endif +#ifndef ERROR_FILE_NOT_FOUND +#define ERROR_FILE_NOT_FOUND 2 +#endif +#ifndef ERROR_PATH_NOT_FOUND +#define ERROR_PATH_NOT_FOUND 3 +#endif + +inline int MessageBox(HWND, const char *, const char *, unsigned long) { return 0; } +inline int MessageBoxA(HWND, const char *, const char *, unsigned long) { return 0; } +inline int MessageBoxW(HWND, const wchar_t *, const wchar_t *, unsigned long) { return 0; } +inline int ShowWindow(HWND, int) { return 0; } +inline DWORD GetModuleFileNameW(HMODULE, wchar_t *, DWORD size) { (void)size; return 0; } + +#ifndef MB_OK +#define MB_OK 0x00000000 +#endif +#ifndef MB_OKCANCEL +#define MB_OKCANCEL 0x00000001 +#endif +#ifndef MB_YESNO +#define MB_YESNO 0x00000004 +#endif +#ifndef MB_ABORTRETRYIGNORE +#define MB_ABORTRETRYIGNORE 0x00000002 +#endif +#ifndef MB_ICONERROR +#define MB_ICONERROR 0x00000010 +#endif +#ifndef MB_ICONSTOP +#define MB_ICONSTOP MB_ICONERROR +#endif +#ifndef MB_ICONWARNING +#define MB_ICONWARNING 0x00000030 +#endif +#ifndef MB_ICONINFORMATION +#define MB_ICONINFORMATION 0x00000040 +#endif +#ifndef MB_SYSTEMMODAL +#define MB_SYSTEMMODAL 0x00001000 +#endif +#ifndef MB_TASKMODAL +#define MB_TASKMODAL 0x00002000 +#endif + +#ifndef SW_HIDE +#define SW_HIDE 0 +#endif +#ifndef SW_SHOWNORMAL +#define SW_SHOWNORMAL 1 +#endif +#ifndef SW_SHOW +#define SW_SHOW 5 +#endif +#ifndef SW_SHOWNA +#define SW_SHOWNA 8 +#endif + +typedef struct _EXCEPTION_RECORD EXCEPTION_RECORD; +typedef struct _CONTEXT CONTEXT; +typedef struct _EXCEPTION_POINTERS { + EXCEPTION_RECORD *ExceptionRecord; + CONTEXT *ContextRecord; +} EXCEPTION_POINTERS, *LPEXCEPTION_POINTERS; + +#ifndef __max +#define __max(a, b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef __min +#define __min(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef _stat +#define _stat stat +#endif +#ifndef _S_IFDIR +#define _S_IFDIR S_IFDIR +#endif + +#ifndef CreateDirectory +inline int CreateDirectory(const char *p, void * /*attrs*/) +{ + return mkdir(p, 0755) == 0; +} +inline int CreateDirectoryA(const char *p, void * /*attrs*/) +{ + return mkdir(p, 0755) == 0; +} +#endif + +typedef struct tagPOINT { + LONG x; + LONG y; +} POINT; + +typedef struct tagSIZE { + LONG cx; + LONG cy; +} SIZE; + +typedef struct tagPOINTFLOAT { + FLOAT x; + FLOAT y; +} POINTFLOAT; + +typedef struct tagRECT { + LONG left; + LONG top; + LONG right; + LONG bottom; +} RECT; + +typedef RECT *LPRECT; +typedef const RECT *LPCRECT; + +typedef struct tagMONITORINFO { + DWORD cbSize; + RECT rcMonitor; + RECT rcWork; + DWORD dwFlags; +} MONITORINFO; + +typedef struct tagPALETTEENTRY { + BYTE peRed; + BYTE peGreen; + BYTE peBlue; + BYTE peFlags; +} PALETTEENTRY; + +typedef struct tagBITMAPFILEHEADER { + WORD bfType; + DWORD bfSize; + WORD bfReserved1; + WORD bfReserved2; + DWORD bfOffBits; +} BITMAPFILEHEADER; + +typedef struct tagBITMAPINFOHEADER { + DWORD biSize; + LONG biWidth; + LONG biHeight; + WORD biPlanes; + WORD biBitCount; + DWORD biCompression; + DWORD biSizeImage; + LONG biXPelsPerMeter; + LONG biYPelsPerMeter; + DWORD biClrUsed; + DWORD biClrImportant; +} BITMAPINFOHEADER; + +typedef struct tagRGBQUAD { + BYTE rgbBlue; + BYTE rgbGreen; + BYTE rgbRed; + BYTE rgbReserved; +} RGBQUAD; + +typedef struct tagBITMAPINFO { + BITMAPINFOHEADER bmiHeader; + RGBQUAD bmiColors[1]; +} BITMAPINFO; + +typedef struct tagTEXTMETRIC { + LONG tmHeight; + LONG tmAscent; + LONG tmDescent; + LONG tmInternalLeading; + LONG tmExternalLeading; + LONG tmAveCharWidth; + LONG tmMaxCharWidth; + LONG tmWeight; + LONG tmOverhang; + LONG tmDigitizedAspectX; + LONG tmDigitizedAspectY; + wchar_t tmFirstChar; + wchar_t tmLastChar; + wchar_t tmDefaultChar; + wchar_t tmBreakChar; + BYTE tmItalic; + BYTE tmUnderlined; + BYTE tmStruckOut; + BYTE tmPitchAndFamily; + BYTE tmCharSet; +} TEXTMETRIC; + +typedef struct tagLOGFONTA { + LONG lfHeight; + LONG lfWidth; + LONG lfEscapement; + LONG lfOrientation; + LONG lfWeight; + BYTE lfItalic; + BYTE lfUnderline; + BYTE lfStrikeOut; + BYTE lfCharSet; + BYTE lfOutPrecision; + BYTE lfClipPrecision; + BYTE lfQuality; + BYTE lfPitchAndFamily; + char lfFaceName[32]; +} LOGFONTA; + +typedef LOGFONTA LOGFONT; + +typedef struct _GLYPHMETRICSFLOAT { + FLOAT gmfBlackBoxX; + FLOAT gmfBlackBoxY; + POINTFLOAT gmfptGlyphOrigin; + FLOAT gmfCellIncX; + FLOAT gmfCellIncY; +} GLYPHMETRICSFLOAT; + +typedef GLYPHMETRICSFLOAT *LPGLYPHMETRICSFLOAT; + +typedef struct _RGNDATAHEADER { + DWORD dwSize; + DWORD iType; + DWORD nCount; + DWORD nRgnSize; + RECT rcBound; +} RGNDATAHEADER; + +typedef struct _RGNDATA { + RGNDATAHEADER rdh; + char Buffer[1]; +} RGNDATA; + +#ifndef GUID_DEFINED +#define GUID_DEFINED +typedef struct GUID { + unsigned long Data1; + unsigned short Data2; + unsigned short Data3; + unsigned char Data4[8]; +} GUID; +#endif + +#ifndef CONST +#define CONST const +#endif + +#ifndef FAR +#define FAR +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef S_OK +#define S_OK ((HRESULT)0L) +#endif + +#ifndef S_FALSE +#define S_FALSE ((HRESULT)1L) +#endif + +#ifndef E_UNEXPECTED +#define E_UNEXPECTED ((HRESULT)0x8000FFFFL) +#endif + +#ifndef E_NOTIMPL +#define E_NOTIMPL ((HRESULT)0x80004001L) +#endif + +#ifndef E_NOINTERFACE +#define E_NOINTERFACE ((HRESULT)0x80004002L) +#endif + +#ifndef E_POINTER +#define E_POINTER ((HRESULT)0x80004003L) +#endif + +#ifndef E_ABORT +#define E_ABORT ((HRESULT)0x80004004L) +#endif + +#ifndef E_FAIL +#define E_FAIL ((HRESULT)0x80004005L) +#endif + +#ifndef E_ACCESSDENIED +#define E_ACCESSDENIED ((HRESULT)0x80070005L) +#endif + +#ifndef E_HANDLE +#define E_HANDLE ((HRESULT)0x80070006L) +#endif + +#ifndef E_OUTOFMEMORY +#define E_OUTOFMEMORY ((HRESULT)0x8007000EL) +#endif + +#ifndef E_INVALIDARG +#define E_INVALIDARG ((HRESULT)0x80070057L) +#endif + +#ifndef FAILED +#define FAILED(hr) (((HRESULT)(hr)) < 0) +#endif + +#ifndef SUCCEEDED +#define SUCCEEDED(hr) (((HRESULT)(hr)) >= 0) +#endif + +#ifndef MAKE_HRESULT +#define MAKE_HRESULT(sev, fac, code) \ + ((HRESULT)(((unsigned long)(sev) << 31) | ((unsigned long)(fac) << 16) | ((unsigned long)(code)))) +#endif + +#ifndef SEVERITY_SUCCESS +#define SEVERITY_SUCCESS 0 +#endif + +#ifndef SEVERITY_ERROR +#define SEVERITY_ERROR 1 +#endif + +#ifndef FACILITY_NULL +#define FACILITY_NULL 0 +#endif + +#ifndef FACILITY_RPC +#define FACILITY_RPC 1 +#endif + +#ifndef FACILITY_DISPATCH +#define FACILITY_DISPATCH 2 +#endif + +#ifndef FACILITY_STORAGE +#define FACILITY_STORAGE 3 +#endif + +#ifndef FACILITY_ITF +#define FACILITY_ITF 4 +#endif + +#ifndef FACILITY_WIN32 +#define FACILITY_WIN32 7 +#endif + +#ifndef LOWORD +#define LOWORD(l) ((WORD)((DWORD_PTR)(l) & 0xFFFF)) +#endif + +#ifndef HIWORD +#define HIWORD(l) ((WORD)((DWORD_PTR)(l) >> 16)) +#endif + +#ifndef LOBYTE +#define LOBYTE(w) ((BYTE)((WORD)(w) & 0xFF)) +#endif + +#ifndef HIBYTE +#define HIBYTE(w) ((BYTE)((WORD)(w) >> 8)) +#endif + +#ifndef WINVER +#define WINVER 0x0600 +#endif + +#ifndef MAX_PATH +#define MAX_PATH 260 +#endif + +#ifndef GWL_STYLE +#define GWL_STYLE (-16) +#endif + +#ifndef SWP_NOZORDER +#define SWP_NOZORDER 0x0004 +#endif + +#ifndef MONITOR_DEFAULTTOPRIMARY +#define MONITOR_DEFAULTTOPRIMARY 0x00000001 +#endif + +#ifndef HWND_TOPMOST +#define HWND_TOPMOST ((HWND)(intptr_t)-1) +#endif + +#ifndef INVALID_FILE_ATTRIBUTES +#define INVALID_FILE_ATTRIBUTES 0xFFFFFFFFu +#endif + +#ifndef BI_RGB +#define BI_RGB 0 +#endif + +#ifndef DIB_RGB_COLORS +#define DIB_RGB_COLORS 0 +#endif + +#ifndef ETO_OPAQUE +#define ETO_OPAQUE 0x0002 +#endif + +#ifndef FW_NORMAL +#define FW_NORMAL 400 +#endif + +#ifndef FW_BOLD +#define FW_BOLD 700 +#endif + +#ifndef DEFAULT_CHARSET +#define DEFAULT_CHARSET 1 +#endif + +#ifndef OUT_DEFAULT_PRECIS +#define OUT_DEFAULT_PRECIS 0 +#endif + +#ifndef CLIP_DEFAULT_PRECIS +#define CLIP_DEFAULT_PRECIS 0 +#endif + +#ifndef ANTIALIASED_QUALITY +#define ANTIALIASED_QUALITY 4 +#endif + +#ifndef VARIABLE_PITCH +#define VARIABLE_PITCH 2 +#endif + +#ifndef RGB +#define RGB(r, g, b) ((DWORD)(((BYTE)(r)) | ((WORD)((BYTE)(g)) << 8) | (((DWORD)(BYTE)(b)) << 16))) +#endif + +static inline char *_strdup(const char *src) +{ + return ::strdup(src); +} + +static inline char *lstrcat(char *dst, const char *src) +{ + return ::strcat(dst, src); +} + +static inline char *lstrcpy(char *dst, const char *src) +{ + return ::strcpy(dst, src); +} + +static inline char *lstrcpyn(char *dst, const char *src, int max_len) +{ + if (max_len <= 0) { + return dst; + } + + ::strncpy(dst, src, static_cast(max_len) - 1); + dst[max_len - 1] = '\0'; + return dst; +} + +static inline int lstrlen(const char *src) +{ + return static_cast(::strlen(src)); +} + +static inline int lstrcmpi(const char *lhs, const char *rhs) +{ + return ::strcasecmp(lhs, rhs); +} + +static inline char *strupr(char *src) +{ + if (src == nullptr) { + return nullptr; + } + + for (char *cursor = src; *cursor != '\0'; ++cursor) { + *cursor = static_cast(::toupper(static_cast(*cursor))); + } + + return src; +} + +// TheSuperHackers @build bobtista 29/04/2026 GetCurrentDirectory is provided +// by file_compat.h. +#if 0 +static inline DWORD GetCurrentDirectory(DWORD buffer_len, char *buffer) +{ + if (buffer == nullptr || buffer_len == 0) { + return 0; + } + + if (::getcwd(buffer, static_cast(buffer_len)) == nullptr) { + buffer[0] = '\0'; + return 0; + } + + return static_cast(::strlen(buffer)); +} +#endif + +// TheSuperHackers @build bobtista 29/04/2026 GetFileAttributes is provided +// by file_compat.h (included earlier in this header). + +static inline HMODULE LoadLibrary(const char *path) +{ + return ::dlopen(path, RTLD_NOW | RTLD_LOCAL); +} + +static inline FARPROC GetProcAddress(HMODULE module, const char *name) +{ + return reinterpret_cast(::dlsym(module, name)); +} + +static inline int FreeLibrary(HMODULE module) +{ + return (module != nullptr && ::dlclose(module) == 0) ? TRUE : FALSE; +} + +static inline BOOL GetClientRect(HWND, LPRECT rect) +{ + if (rect == nullptr) { + return FALSE; + } + + rect->left = 0; + rect->top = 0; + rect->right = 0; + rect->bottom = 0; + return TRUE; +} + +static inline LONG GetWindowLong(HWND, int) +{ + return 0; +} + +static inline BOOL AdjustWindowRect(LPRECT, DWORD, BOOL) +{ + return TRUE; +} + +static inline BOOL SetWindowPos(HWND, HWND, int, int, int, int, unsigned int) +{ + return TRUE; +} + +static inline HMONITOR MonitorFromWindow(HWND, DWORD) +{ + return nullptr; +} + +static inline BOOL GetMonitorInfo(HMONITOR, MONITORINFO *info) +{ + if (info == nullptr) { + return FALSE; + } + + info->rcMonitor.left = 0; + info->rcMonitor.top = 0; + info->rcMonitor.right = 1920; + info->rcMonitor.bottom = 1080; + info->rcWork = info->rcMonitor; + return TRUE; +} + +static inline void ZeroMemory(void *ptr, size_t size) +{ + ::memset(ptr, 0, size); +} + +static inline HWND GetDesktopWindow() +{ + return nullptr; +} + +static inline HDC GetDC(HWND) +{ + return nullptr; +} + +static inline int ReleaseDC(HWND, HDC) +{ + return 1; +} + +static inline BOOL SetDeviceGammaRamp(HDC, LPCVOID) +{ + return TRUE; +} + +static inline BOOL ExtTextOutW(HDC, int, int, unsigned int, const RECT *, const wchar_t *, unsigned int, const int *) +{ + return TRUE; +} + +static inline BOOL GetTextExtentPoint32W(HDC, const wchar_t *, int len, SIZE *size) +{ + if (size == nullptr) { + return FALSE; + } + + size->cx = len; + size->cy = 1; + return TRUE; +} + +static inline int MulDiv(int number, int numerator, int denominator) +{ + return (denominator != 0) ? static_cast((static_cast(number) * numerator) / denominator) : 0; +} + +static inline HFONT CreateFont(int, int, int, int, int, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, const char *) +{ + return reinterpret_cast(1); +} + +static inline HBITMAP CreateDIBSection(HDC, const BITMAPINFO *, unsigned int, void **bits, HANDLE, DWORD) +{ + if (bits != nullptr) { + *bits = nullptr; + } + return reinterpret_cast(1); +} + +static inline HDC CreateCompatibleDC(HDC) +{ + return reinterpret_cast(1); +} + +static inline HGDIOBJ SelectObject(HDC, HGDIOBJ object) +{ + return object; +} + +static inline DWORD SetBkColor(HDC, DWORD color) +{ + return color; +} + +static inline DWORD SetTextColor(HDC, DWORD color) +{ + return color; +} + +static inline BOOL GetTextMetrics(HDC, TEXTMETRIC *metric) +{ + if (metric == nullptr) { + return FALSE; + } + + ZeroMemory(metric, sizeof(TEXTMETRIC)); + metric->tmHeight = 1; + metric->tmAscent = 1; + metric->tmAveCharWidth = 1; + return TRUE; +} + +static inline BOOL DeleteObject(HGDIOBJ) +{ + return TRUE; +} + +static inline BOOL DeleteDC(HDC) +{ + return TRUE; +} + +static inline int _isnan(double value) +{ + return std::isnan(value) ? 1 : 0; +} + +static inline int _finite(double value) +{ + return std::isfinite(value) ? 1 : 0; +} diff --git a/Core/Libraries/Source/WWVegas/compat/win32_shims/windowsx.h b/Core/Libraries/Source/WWVegas/compat/win32_shims/windowsx.h new file mode 100644 index 00000000000..0b40282e3fb --- /dev/null +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/windowsx.h @@ -0,0 +1,3 @@ +#pragma once + +#include "windows.h" diff --git a/Core/Libraries/Source/WWVegas/compat/win32_shims/winsock.h b/Core/Libraries/Source/WWVegas/compat/win32_shims/winsock.h new file mode 100644 index 00000000000..1814f86c1e9 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/winsock.h @@ -0,0 +1,102 @@ +#pragma once + +// TheSuperHackers @build bobtista 29/04/2026 winsock.h compatibility shim for +// non-Windows builds. Maps the Win32 BSD-derived socket API to native POSIX +// equivalents so WWDownload (FTP client) at least compiles. + +#include "windows.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef int SOCKET; +typedef struct hostent HOSTENT; +typedef HOSTENT *PHOSTENT; +typedef HOSTENT *LPHOSTENT; + +#ifndef MAKEWORD +#define MAKEWORD(a, b) ((WORD)(((BYTE)(a)) | (((WORD)((BYTE)(b))) << 8))) +#endif + +typedef struct WSAData +{ + WORD wVersion; + WORD wHighVersion; + char szDescription[257]; + char szSystemStatus[129]; + unsigned short iMaxSockets; + unsigned short iMaxUdpDg; + char *lpVendorInfo; +} WSADATA, *LPWSADATA; + +#ifndef INVALID_SOCKET +#define INVALID_SOCKET (-1) +#endif + +#ifndef SOCKET_ERROR +#define SOCKET_ERROR (-1) +#endif + +#ifndef closesocket +#define closesocket(s) (::close(s)) +#endif + +#ifndef ioctlsocket +#define ioctlsocket(s, cmd, argp) (::ioctl((s), (cmd), (argp))) +#endif + +#ifndef WSAGetLastError +#define WSAGetLastError() (errno) +#endif + +#ifndef WSAStartup +#define WSAStartup(wVer, lpData) (0) +#endif + +#ifndef WSACleanup +#define WSACleanup() (0) +#endif + +#ifndef WSAEWOULDBLOCK +#define WSAEWOULDBLOCK EAGAIN +#endif + +#ifndef WSAECONNRESET +#define WSAECONNRESET ECONNRESET +#endif + +#ifndef WSAENOTCONN +#define WSAENOTCONN ENOTCONN +#endif + +#ifndef WSAEINPROGRESS +#define WSAEINPROGRESS EINPROGRESS +#endif + +#ifndef WSAEALREADY +#define WSAEALREADY EALREADY +#endif + +#ifndef WSAEINVAL +#define WSAEINVAL EINVAL +#endif + +#ifndef WSAEISCONN +#define WSAEISCONN EISCONN +#endif + +#ifndef WSAETIMEDOUT +#define WSAETIMEDOUT ETIMEDOUT +#endif + +#ifndef WSAEINTR +#define WSAEINTR EINTR +#endif diff --git a/Core/Libraries/Source/WWVegas/osdep.h b/Core/Libraries/Source/WWVegas/osdep.h new file mode 100644 index 00000000000..da6129cc8c2 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/osdep.h @@ -0,0 +1,23 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program 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, either version 3 of the License, or +** (at your option) any later version. +** +** This program 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. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +// Legacy Westwood sources include osdep.h for platform glue. Route those +// includes through the shared non-Windows compatibility layer. +#include "Utility/compat.h" diff --git a/Core/Libraries/Source/WWVegas/osdep/osdep.h b/Core/Libraries/Source/WWVegas/osdep/osdep.h new file mode 100644 index 00000000000..31208fb46a6 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/osdep/osdep.h @@ -0,0 +1,21 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program 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, either version 3 of the License, or +** (at your option) any later version. +** +** This program 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. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +#include "../osdep.h" From d9a09a62f84c060bd3eea206b373498d6548d576 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Thu, 30 Apr 2026 09:25:12 +1000 Subject: [PATCH 002/523] build(wwvegas): gate Win-only WWLib/WWDownload/debug/profile sources on non-Win (cherry picked from commit 01c03022af45e3049b7a2fd8ce7b2e718a4460b1) --- Core/Libraries/Source/WWVegas/CMakeLists.txt | 29 +++++- .../Source/WWVegas/WWDebug/wwprofile.h | 5 -- .../Source/WWVegas/WWDownload/CMakeLists.txt | 44 ++++++--- .../WWVegas/WWDownload/WWDownloadStub.cpp | 86 ++++++++++++++++++ .../Source/WWVegas/WWLib/CMakeLists.txt | 9 +- .../Source/WWVegas/WWLib/DbgHelpLoader.h | 85 ++++++++++++++++++ .../Source/WWVegas/WWLib/FastAllocator.h | 4 + Core/Libraries/Source/WWVegas/WWLib/TARGA.cpp | 4 + Core/Libraries/Source/WWVegas/WWLib/ini.cpp | 5 +- .../WWVegas/WWLib/registry_unix_stub.cpp | 89 +++++++++++++++++++ .../Libraries/Source/WWVegas/WWLib/stringex.h | 1 + Core/Libraries/Source/WWVegas/WWLib/win.h | 2 +- .../Source/WWVegas/WWSaveLoad/saveload.cpp | 2 - Core/Libraries/Source/debug/CMakeLists.txt | 41 +++++---- Core/Libraries/Source/profile/CMakeLists.txt | 23 +++-- Core/Libraries/Source/profile/profile.cpp | 69 +++++++++++++- .../Source/profile/profile_funclevel.cpp | 22 +++-- .../Source/profile/profile_funclevel.h | 36 ++++++-- .../Source/profile/profile_highlevel.cpp | 7 ++ .../Libraries/Source/WWVegas/CMakeLists.txt | 8 +- 20 files changed, 511 insertions(+), 60 deletions(-) create mode 100644 Core/Libraries/Source/WWVegas/WWDownload/WWDownloadStub.cpp create mode 100644 Core/Libraries/Source/WWVegas/WWLib/registry_unix_stub.cpp diff --git a/Core/Libraries/Source/WWVegas/CMakeLists.txt b/Core/Libraries/Source/WWVegas/CMakeLists.txt index e8a6beeb0f2..8253aae1e9e 100644 --- a/Core/Libraries/Source/WWVegas/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/CMakeLists.txt @@ -8,14 +8,31 @@ target_compile_definitions(core_wwcommon INTERFACE target_link_libraries(core_wwcommon INTERFACE d3d8lib - milesstub stlport ) +# TheSuperHackers @build bobtista 29/04/2026 milesstub is Win-only (the +# Miles Sound System SDK stub). On non-Win we use OpenAL instead. +if(WIN32) + target_link_libraries(core_wwcommon INTERFACE + milesstub + ) +endif() + target_include_directories(core_wwcommon INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} ) +if(NOT WIN32) + # TheSuperHackers @build bobtista 29/04/2026 BEFORE so the compat shims + # (e.g. dinput.h) take priority over headers that come along for the ride + # via FetchContent (the dx8 SDK ships a Win-flavored dinput.h that fails + # to parse on macOS/Linux). + target_include_directories(core_wwcommon BEFORE INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/compat/win32_shims + ) +endif() + add_subdirectory(WW3D2) add_subdirectory(WWAudio) add_subdirectory(WWDebug) @@ -32,6 +49,16 @@ target_include_directories(core_wwvegas INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} ) +# TheSuperHackers @build bobtista 29/04/2026 Propagate the win32 shim include +# path through core_wwvegas so consumers (z_gameengine, etc.) can resolve +# and friends to the compat layer without each linking core_wwcommon +# transitively. core_wwlib only links core_wwcommon PRIVATE. +if(NOT WIN32) + target_include_directories(core_wwvegas BEFORE INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/compat/win32_shims + ) +endif() + target_link_libraries(core_wwvegas INTERFACE # core_ww3d2 # core_wwaudio diff --git a/Core/Libraries/Source/WWVegas/WWDebug/wwprofile.h b/Core/Libraries/Source/WWVegas/WWDebug/wwprofile.h index 44aea85c0ce..1d1d4daca37 100644 --- a/Core/Libraries/Source/WWVegas/WWDebug/wwprofile.h +++ b/Core/Libraries/Source/WWVegas/WWDebug/wwprofile.h @@ -39,11 +39,6 @@ //#define ENABLE_TIME_AND_MEMORY_LOG #include "WWLib/wwstring.h" -#ifdef _UNIX -typedef signed long long __int64; -typedef signed long long _int64; -#endif - // enable profiling by default in debug mode. #ifdef WWDEBUG #define ENABLE_WWPROFILE diff --git a/Core/Libraries/Source/WWVegas/WWDownload/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WWDownload/CMakeLists.txt index ee2966bc91c..13c12fec3da 100644 --- a/Core/Libraries/Source/WWVegas/WWDownload/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/WWDownload/CMakeLists.txt @@ -1,16 +1,34 @@ -set(WWDOWNLOAD_SRC - Download.cpp - Download.h - DownloadDebug.h - downloaddefs.h - FTP.cpp - ftp.h - ftpdefs.h - registry.cpp - Registry.h - urlBuilder.cpp - urlBuilder.h -) +# TheSuperHackers @build bobtista 29/04/2026 WWDownload uses winsock + Win +# registry APIs heavily. On non-Windows, compile only the platform-neutral +# urlBuilder TU + a stub that provides Cftp/CDownload skeletons for headers +# that show up in cross-platform engine code. +if(WIN32) + set(WWDOWNLOAD_SRC + Download.cpp + Download.h + DownloadDebug.h + downloaddefs.h + FTP.cpp + ftp.h + ftpdefs.h + registry.cpp + Registry.h + urlBuilder.cpp + urlBuilder.h + ) +else() + set(WWDOWNLOAD_SRC + Download.h + DownloadDebug.h + downloaddefs.h + ftp.h + ftpdefs.h + Registry.h + urlBuilder.cpp + urlBuilder.h + WWDownloadStub.cpp + ) +endif() add_library(corei_wwdownload INTERFACE) diff --git a/Core/Libraries/Source/WWVegas/WWDownload/WWDownloadStub.cpp b/Core/Libraries/Source/WWVegas/WWDownload/WWDownloadStub.cpp new file mode 100644 index 00000000000..2d2d9ecacb4 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WWDownload/WWDownloadStub.cpp @@ -0,0 +1,86 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program 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, either version 3 of the License, or +** (at your option) any later version. +** +** This program 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. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +// TheSuperHackers @build bobtista 29/04/2026 Non-Windows stub TU. The real +// FTP/HTTP download path uses winsock + Win registry APIs; this stub gives +// the engine the symbols it needs to link without compiling the full FTP.cpp +// and Download.cpp on macOS/Linux. Every method returns E_FAIL so callers +// see "no download possible" cleanly. Real cross-platform implementation +// can replace this later. + +#include "WWDownload/Download.h" +#include "WWDownload/ftp.h" +#include "WWDownload/Registry.h" + +bool GetStringFromRegistry(std::string /*path*/, std::string /*key*/, std::string & /*val*/) { return false; } +bool GetUnsignedIntFromRegistry(std::string /*path*/, std::string /*key*/, unsigned int & /*val*/) { return false; } +bool SetStringInRegistry(std::string /*path*/, std::string /*key*/, std::string /*val*/) { return false; } +bool SetUnsignedIntInRegistry(std::string /*path*/, std::string /*key*/, unsigned int /*val*/) { return false; } + +Cftp::Cftp() + : m_iCommandSocket(-1) + , m_iDataSocket(-1) + , m_iFilePos(0) + , m_iBytesRead(0) + , m_iFileSize(0) + , m_pfLocalFile(nullptr) + , m_iStatus(0) + , m_sendNewPortStatus(0) + , m_findStart(0) +{ + m_szRemoteFilePath[0] = '\0'; + m_szRemoteFileName[0] = '\0'; + m_szLocalFilePath[0] = '\0'; + m_szLocalFileName[0] = '\0'; + m_szServerName[0] = '\0'; + m_szUserName[0] = '\0'; + m_szPassword[0] = '\0'; +} + +Cftp::~Cftp() +{ +} + +HRESULT Cftp::ConnectToServer(LPCSTR /*szServerName*/) { return E_FAIL; } +HRESULT Cftp::DisconnectFromServer() { return E_FAIL; } +HRESULT Cftp::LoginToServer(LPCSTR /*szUserName*/, LPCSTR /*szPassword*/) { return E_FAIL; } +HRESULT Cftp::LogoffFromServer() { return E_FAIL; } +HRESULT Cftp::FindFile(LPCSTR /*szRemoteFileName*/, int * /*piSize*/) { return E_FAIL; } +HRESULT Cftp::FileRecoveryPosition(LPCSTR /*szLocalFileName*/, LPCSTR /*szRegistryRoot*/) { return E_FAIL; } +HRESULT Cftp::GetNextFileBlock(LPCSTR /*szLocalFileName*/, int * /*piTotalRead*/) { return E_FAIL; } +HRESULT Cftp::RecvReply(LPCSTR /*pReplyBuffer*/, int /*iSize*/, int * /*piRetCode*/) { return E_FAIL; } +HRESULT Cftp::SendCommand(LPCSTR /*pCommand*/, int /*iSize*/) { return E_FAIL; } + +int Cftp::SendData(char * /*pData*/, int /*iSize*/) { return -1; } +int Cftp::RecvData(char * /*pData*/, int /*iSize*/) { return -1; } +int Cftp::SendNewPort() { return -1; } +int Cftp::OpenDataConnection() { return -1; } +void Cftp::CloseDataConnection() {} +int Cftp::AsyncGetHostByName(char * /*szName*/, struct sockaddr_in & /*address*/) { return -1; } +void Cftp::GetDownloadFilename(const char * /*localname*/, char * /*downloadname*/, size_t /*downloadname_size*/) {} +void Cftp::CloseSockets() {} +void Cftp::ZeroStuff() {} + +HRESULT CDownload::PumpMessages() { return E_FAIL; } +HRESULT CDownload::Abort() { return E_FAIL; } +HRESULT CDownload::DownloadFile(LPCSTR /*server*/, LPCSTR /*username*/, LPCSTR /*password*/, + LPCSTR /*file*/, LPCSTR /*localfile*/, LPCSTR /*regkey*/, bool /*tryresume*/) +{ + return E_FAIL; +} +HRESULT CDownload::GetLastLocalFile(char * /*local_file*/, int /*maxlen*/) { return E_FAIL; } diff --git a/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt index f83746b2f08..9e8fc389582 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt @@ -34,7 +34,6 @@ set(WWLIB_SRC cstraw.h DbgHelpGuard.cpp DbgHelpGuard.h - DbgHelpLoader.cpp DbgHelpLoader.h DbgHelpLoader_minidump.h Except.cpp @@ -156,6 +155,7 @@ set(WWLIB_SRC if(WIN32) list(APPEND WWLIB_SRC + DbgHelpLoader.cpp mpu.cpp MPU.h rcfile.cpp @@ -166,6 +166,13 @@ if(WIN32) verchk.h WWCOMUtil.cpp WWCOMUtil.h +) +else() + # TheSuperHackers @build bobtista 29/04/2026 Stub the legacy WWLib + # RegistryClass on non-Win so DX8Wrapper / W3DDisplay still link. + list(APPEND WWLIB_SRC + registry.h + registry_unix_stub.cpp ) endif() diff --git a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h index fa40554ea67..10710181c86 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h +++ b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h @@ -20,6 +20,8 @@ #include "always.h" +#ifdef _WIN32 + #include #include // Must be included after Windows.h #include @@ -214,3 +216,86 @@ class DbgHelpLoader bool m_failed; bool m_loadedFromSystem; }; + +#else + +#ifndef WINAPI +#define WINAPI +#endif + +using BOOL = int; +using DWORD = unsigned long; +using LPDWORD = DWORD*; +using HANDLE = void*; +using LPSTR = char*; +using LPVOID = void*; +struct IMAGEHLP_SYMBOL; +using PIMAGEHLP_SYMBOL = IMAGEHLP_SYMBOL*; +struct IMAGEHLP_LINE; +using PIMAGEHLP_LINE = IMAGEHLP_LINE*; +struct STACKFRAME; +using LPSTACKFRAME = STACKFRAME*; +using PREAD_PROCESS_MEMORY_ROUTINE = void*; +using PFUNCTION_TABLE_ACCESS_ROUTINE = void*; +using PGET_MODULE_BASE_ROUTINE = void*; +using PTRANSLATE_ADDRESS_ROUTINE = void*; + +#ifdef RTS_ENABLE_CRASHDUMP +enum MINIDUMP_TYPE : unsigned int {}; +struct MINIDUMP_EXCEPTION_INFORMATION; +using PMINIDUMP_EXCEPTION_INFORMATION = MINIDUMP_EXCEPTION_INFORMATION*; +struct MINIDUMP_USER_STREAM_INFORMATION; +using PMINIDUMP_USER_STREAM_INFORMATION = MINIDUMP_USER_STREAM_INFORMATION*; +struct MINIDUMP_CALLBACK_INFORMATION; +using PMINIDUMP_CALLBACK_INFORMATION = MINIDUMP_CALLBACK_INFORMATION*; +#endif + +class DbgHelpLoader +{ +public: + static bool isLoaded() { return false; } + static bool isLoadedFromSystem() { return false; } + static bool isFailed() { return true; } + + static bool load() { return false; } + static void unload() {} + + static BOOL WINAPI symInitialize(HANDLE, LPSTR, BOOL) { return 0; } + static BOOL WINAPI symCleanup(HANDLE) { return 0; } + static BOOL WINAPI symLoadModule(HANDLE, HANDLE, LPSTR, LPSTR, DWORD, DWORD) { return 0; } + static DWORD WINAPI symGetModuleBase(HANDLE, DWORD) { return 0; } + static BOOL WINAPI symUnloadModule(HANDLE, DWORD) { return 0; } + static BOOL WINAPI symGetSymFromAddr(HANDLE, DWORD, LPDWORD, PIMAGEHLP_SYMBOL) { return 0; } + static BOOL WINAPI symGetLineFromAddr(HANDLE, DWORD, LPDWORD, PIMAGEHLP_LINE) { return 0; } + static DWORD WINAPI symSetOptions(DWORD) { return 0; } + static LPVOID WINAPI symFunctionTableAccess(HANDLE, DWORD) { return nullptr; } + static BOOL WINAPI stackWalk( + DWORD, + HANDLE, + HANDLE, + LPSTACKFRAME, + LPVOID, + PREAD_PROCESS_MEMORY_ROUTINE, + PFUNCTION_TABLE_ACCESS_ROUTINE, + PGET_MODULE_BASE_ROUTINE, + PTRANSLATE_ADDRESS_ROUTINE) + { + return 0; + } + +#ifdef RTS_ENABLE_CRASHDUMP + static BOOL WINAPI miniDumpWriteDump( + HANDLE, + DWORD, + HANDLE, + MINIDUMP_TYPE, + PMINIDUMP_EXCEPTION_INFORMATION, + PMINIDUMP_USER_STREAM_INFORMATION, + PMINIDUMP_CALLBACK_INFORMATION) + { + return 0; + } +#endif +}; + +#endif diff --git a/Core/Libraries/Source/WWVegas/WWLib/FastAllocator.h b/Core/Libraries/Source/WWVegas/WWLib/FastAllocator.h index 3b74ffca367..662110883e4 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/FastAllocator.h +++ b/Core/Libraries/Source/WWVegas/WWLib/FastAllocator.h @@ -39,7 +39,11 @@ #include "always.h" #include "WWDebug/wwdebug.h" #include "mutex.h" +#ifdef _WIN32 #include +#else +#include +#endif #include //size_t & ptrdiff_t definition /////////////////////////////////////////////////////////////////////////////// diff --git a/Core/Libraries/Source/WWVegas/WWLib/TARGA.cpp b/Core/Libraries/Source/WWVegas/WWLib/TARGA.cpp index 4c4d1e1d6ee..69c241d2d46 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/TARGA.cpp +++ b/Core/Libraries/Source/WWVegas/WWLib/TARGA.cpp @@ -64,7 +64,11 @@ #ifndef TGA_USES_WWLIB_FILE_CLASSES #include "WWDebug/wwdebug.h" #endif +#ifdef _WIN32 #include +#else +#include +#endif #include #include "stringex.h" #ifdef TGA_USES_WWLIB_FILE_CLASSES diff --git a/Core/Libraries/Source/WWVegas/WWLib/ini.cpp b/Core/Libraries/Source/WWVegas/WWLib/ini.cpp index eed30a31519..ad0fe4c609d 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/ini.cpp +++ b/Core/Libraries/Source/WWVegas/WWLib/ini.cpp @@ -94,7 +94,11 @@ #include "win.h" #include "XPIPE.h" #include "XSTRAW.h" +#ifdef _WIN32 #include +#else +#include +#endif #ifdef _UNIX #include #endif @@ -2296,4 +2300,3 @@ void INIClass::Keep_Blank_Entries (bool keep_blanks) { KeepBlankEntries = keep_blanks; } - diff --git a/Core/Libraries/Source/WWVegas/WWLib/registry_unix_stub.cpp b/Core/Libraries/Source/WWVegas/WWLib/registry_unix_stub.cpp new file mode 100644 index 00000000000..c9f0278fe4e --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WWLib/registry_unix_stub.cpp @@ -0,0 +1,89 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program 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, either version 3 of the License, or +** (at your option) any later version. +** +** This program 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. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +// TheSuperHackers @build bobtista 29/04/2026 Non-Win stub for the legacy +// WWLib RegistryClass. The real implementation is Win-registry only; this +// gives DX8Wrapper / W3DDisplay something to link against on macOS/Linux. +// Render-device persistence is rerouted through registry-unix's RegistryIni +// in a follow-up. + +#include "registry.h" + +#include + +bool RegistryClass::IsLocked = false; + +bool RegistryClass::Exists(const char * /*sub_key*/) { return false; } + +RegistryClass::RegistryClass(const char * /*sub_key*/, bool /*create*/) +{ + IsValid = false; +} + +RegistryClass::~RegistryClass() +{ +} + +int RegistryClass::Get_Int(const char * /*name*/, int def_value) { return def_value; } +void RegistryClass::Set_Int(const char * /*name*/, int /*value*/) {} + +bool RegistryClass::Get_Bool(const char * /*name*/, bool def_value) { return def_value; } +void RegistryClass::Set_Bool(const char * /*name*/, bool /*value*/) {} + +float RegistryClass::Get_Float(const char * /*name*/, float def_value) { return def_value; } +void RegistryClass::Set_Float(const char * /*name*/, float /*value*/) {} + +char *RegistryClass::Get_String(const char * /*name*/, char *value, int value_size, const char *default_string) +{ + if (value && value_size > 0) + { + if (default_string != nullptr) + { + std::strncpy(value, default_string, static_cast(value_size) - 1); + value[value_size - 1] = '\0'; + } + else + { + value[0] = '\0'; + } + } + return value; +} + +void RegistryClass::Get_String(const char * /*name*/, StringClass &string, const char *default_string) +{ + string = (default_string != nullptr) ? default_string : ""; +} + +void RegistryClass::Set_String(const char * /*name*/, const char * /*value*/) {} + +void RegistryClass::Get_String(const WCHAR * /*name*/, WideStringClass &string, const WCHAR *default_string) +{ + string = (default_string != nullptr) ? default_string : L""; +} + +void RegistryClass::Set_String(const WCHAR * /*name*/, const WCHAR * /*value*/) {} + +void RegistryClass::Get_Bin(const char * /*name*/, void * /*buffer*/, int /*buffer_size*/) {} +int RegistryClass::Get_Bin_Size(const char * /*name*/) { return 0; } +void RegistryClass::Set_Bin(const char * /*name*/, const void * /*buffer*/, int /*buffer_size*/) {} + +void RegistryClass::Get_Value_List(DynamicVectorClass & /*list*/) {} + +void RegistryClass::Delete_Value(const char * /*name*/) {} +void RegistryClass::Deleta_All_Values() {} diff --git a/Core/Libraries/Source/WWVegas/WWLib/stringex.h b/Core/Libraries/Source/WWVegas/WWLib/stringex.h index 8e23b0227a5..363d9fac17f 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/stringex.h +++ b/Core/Libraries/Source/WWVegas/WWLib/stringex.h @@ -21,6 +21,7 @@ #include "bittype.h" #include #include +#include // Declaration diff --git a/Core/Libraries/Source/WWVegas/WWLib/win.h b/Core/Libraries/Source/WWVegas/WWLib/win.h index 70ef32dc396..df42fecdaf7 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/win.h +++ b/Core/Libraries/Source/WWVegas/WWLib/win.h @@ -82,5 +82,5 @@ void __cdecl Print_Win32Error(unsigned long win32Error); #endif // RTS_DEBUG #else // _WIN32 -//#include // file does not exist +#include #endif // _WIN32 diff --git a/Core/Libraries/Source/WWVegas/WWSaveLoad/saveload.cpp b/Core/Libraries/Source/WWVegas/WWSaveLoad/saveload.cpp index a384a6eaa46..82c924f121a 100644 --- a/Core/Libraries/Source/WWVegas/WWSaveLoad/saveload.cpp +++ b/Core/Libraries/Source/WWVegas/WWSaveLoad/saveload.cpp @@ -45,8 +45,6 @@ #include "WWDebug/wwhack.h" #include "WWDebug/wwprofile.h" -#pragma warning(disable:4201) // warning C4201: nonstandard extension used : nameless struct/union -#include #include "WWLib/systimer.h" diff --git a/Core/Libraries/Source/debug/CMakeLists.txt b/Core/Libraries/Source/debug/CMakeLists.txt index ddce3092d16..b868c9639cd 100644 --- a/Core/Libraries/Source/debug/CMakeLists.txt +++ b/Core/Libraries/Source/debug/CMakeLists.txt @@ -21,24 +21,31 @@ set(DEBUG_SRC "internal.h" ) -add_library(core_debug STATIC) +# TheSuperHackers @build bobtista 29/04/2026 core_debug is Windows-only. +# debug_debug.cpp self-#errors on non-Win/MSVC and the Win API surface +# (SetUnhandledExceptionFilter, ReadFile, MessageBox, ...) does not have a +# meaningful POSIX analog. Consumers that link core_debug must guard the +# link on WIN32. Matches the fbraz3/GeneralsX approach. +if(WIN32) + add_library(core_debug STATIC) -target_sources(core_debug PRIVATE ${DEBUG_SRC}) + target_sources(core_debug PRIVATE ${DEBUG_SRC}) -target_include_directories(core_debug INTERFACE - ${CMAKE_CURRENT_SOURCE_DIR} -) + target_include_directories(core_debug INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR} + ) -target_precompile_headers(core_debug PRIVATE - [["Utility/CppMacros.h"]] # Must be first, to be removed when abandoning VC6 - "debug.h" - "internal.h" - "internal_except.h" - "internal_io.h" - -) + target_precompile_headers(core_debug PRIVATE + [["Utility/CppMacros.h"]] # Must be first, to be removed when abandoning VC6 + "debug.h" + "internal.h" + "internal_except.h" + "internal_io.h" + + ) -target_link_libraries(core_debug PRIVATE - core_wwcommon - corei_always -) + target_link_libraries(core_debug PRIVATE + core_wwcommon + corei_always + ) +endif() diff --git a/Core/Libraries/Source/profile/CMakeLists.txt b/Core/Libraries/Source/profile/CMakeLists.txt index b0db6bd2dc9..7bc9202e29e 100644 --- a/Core/Libraries/Source/profile/CMakeLists.txt +++ b/Core/Libraries/Source/profile/CMakeLists.txt @@ -24,12 +24,23 @@ target_include_directories(core_profile_legacy INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} ) -target_precompile_headers(core_profile_legacy PRIVATE - [["Utility/CppMacros.h"]] # Must be first, to be removed when abandoning VC6 - "profile.h" - "internal.h" - -) +# TheSuperHackers @build bobtista 29/04/2026 Pull windows.h in via the compat +# shim before profile.h on non-Win so __int64 / __forceinline are defined. +if(WIN32) + target_precompile_headers(core_profile_legacy PRIVATE + [["Utility/CppMacros.h"]] # Must be first, to be removed when abandoning VC6 + "profile.h" + "internal.h" + + ) +else() + target_precompile_headers(core_profile_legacy PRIVATE + [["Utility/CppMacros.h"]] # Must be first, to be removed when abandoning VC6 + + "profile.h" + "internal.h" + ) +endif() target_link_libraries(core_profile_legacy PRIVATE core_wwcommon diff --git a/Core/Libraries/Source/profile/profile.cpp b/Core/Libraries/Source/profile/profile.cpp index f0a2265166f..69566655b9f 100644 --- a/Core/Libraries/Source/profile/profile.cpp +++ b/Core/Libraries/Source/profile/profile.cpp @@ -30,7 +30,16 @@ #include "profile.h" #include "internal.h" #include +// GeneralsX @build fbraz 03/02/2026 Add C string functions for Linux +#include +#include // GeneralsX @TheSuperHackers @build BenderAI 11/02/2026 snprintf for Linux +// GeneralsX @build fbraz 03/02/2026 Platform-specific headers +#ifdef _WIN32 #include "mmsystem.h" +#else +#include // clock_gettime for Linux timing +#include // malloc/free +#endif // yuk, I'm doing this so weird because the destructor // of cmd must never be called... @@ -43,10 +52,18 @@ static bool __RegisterDebugCmdGroup_Profile=Debug::AddCommands("profile",&cmd); void *ProfileAllocMemory(unsigned numBytes) { +// GeneralsX @build fbraz 03/02/2026 Use malloc on Linux, GlobalAlloc on Windows +#ifdef _WIN32 HGLOBAL h=GlobalAlloc(GMEM_FIXED,numBytes); if (!h) DCRASH_RELEASE("Debug mem alloc failed"); return (void *)h; +#else + void *ptr = malloc(numBytes); + if (!ptr) + DCRASH_RELEASE("Debug mem alloc failed"); + return ptr; +#endif } void *ProfileReAllocMemory(void *oldPtr, unsigned newSize) @@ -58,10 +75,17 @@ void *ProfileReAllocMemory(void *oldPtr, unsigned newSize) // Shrinking to 0 size is basically freeing memory if (!newSize) { +// GeneralsX @build fbraz 03/02/2026 Use free on Linux, GlobalFree on Windows +#ifdef _WIN32 GlobalFree((HGLOBAL)oldPtr); +#else + free(oldPtr); +#endif return nullptr; } +// GeneralsX @build fbraz 03/02/2026 Platform-specific memory reallocation +#ifdef _WIN32 // now try GlobalReAlloc first HGLOBAL h=GlobalReAlloc((HGLOBAL)oldPtr,newSize,0); if (!h) @@ -75,14 +99,27 @@ void *ProfileReAllocMemory(void *oldPtr, unsigned newSize) memcpy((void *)h,oldPtr,oldSize 0) + n[k] = (n[k] * 1000000000LL) / elapsed_ns; // Scale to 1 second + else + n[k] = (n[k] * 1000) / 20; // Fallback estimate +#endif } // find two closest values @@ -296,7 +358,12 @@ void Profile::StopRange(const char *range) m_frameNames[k].lastGlobalIndex=m_rec; m_recNames=(char **)ProfileReAllocMemory(m_recNames,(m_rec+1)*sizeof(char *)); m_recNames[m_rec]=(char *)ProfileAllocMemory(strlen(range)+1+6); +// GeneralsX @build fbraz 03/02/2026 Use snprintf on Linux, wsprintf on Windows +#ifdef _WIN32 wsprintf(m_recNames[m_rec++],"%s:%i",range,++m_frameNames[k].frames); +#else + snprintf(m_recNames[m_rec++], strlen(range)+7, "%s:%i", range, ++m_frameNames[k].frames); +#endif } else atIndex=m_frameNames[k].lastGlobalIndex; diff --git a/Core/Libraries/Source/profile/profile_funclevel.cpp b/Core/Libraries/Source/profile/profile_funclevel.cpp index 55600be406b..efeea055171 100644 --- a/Core/Libraries/Source/profile/profile_funclevel.cpp +++ b/Core/Libraries/Source/profile/profile_funclevel.cpp @@ -579,7 +579,12 @@ const char *ProfileFuncLevel::Id::GetSource() const helpFile,sizeof(helpFile),&func->funcLine,nullptr); char help[300]; +// GeneralsX @build fbraz 03/02/2026 Use snprintf on Linux, wsprintf on Windows +#ifdef _WIN32 wsprintf(help,ofsFunc?"%s+0x%x":"%s",helpFunc,ofsFunc); +#else + snprintf(help, sizeof(help), ofsFunc?"%s+0x%x":"%s", helpFunc, ofsFunc); +#endif func->funcSource=(char *)ProfileAllocMemory(strlen(helpFile)+1); strcpy(func->funcSource,helpFile); func->funcName=(char *)ProfileAllocMemory(strlen(help)+1); @@ -617,7 +622,9 @@ unsigned ProfileFuncLevel::Id::GetLine() const return func->funcLine; } -unsigned _int64 ProfileFuncLevel::Id::GetCalls(unsigned frame) const +// GeneralsX @refactor BenderAI 10/02/2026 +// Changed from unsigned _int64 to u64 (platform typedef) +u64 ProfileFuncLevel::Id::GetCalls(unsigned frame) const { if (!m_funcPtr) return 0; @@ -634,7 +641,7 @@ unsigned _int64 ProfileFuncLevel::Id::GetCalls(unsigned frame) const } } -unsigned _int64 ProfileFuncLevel::Id::GetTime(unsigned frame) const +u64 ProfileFuncLevel::Id::GetTime(unsigned frame) const { if (!m_funcPtr) return 0; @@ -651,7 +658,7 @@ unsigned _int64 ProfileFuncLevel::Id::GetTime(unsigned frame) const } } -unsigned _int64 ProfileFuncLevel::Id::GetFunctionTime(unsigned frame) const +u64 ProfileFuncLevel::Id::GetFunctionTime(unsigned frame) const { if (!m_funcPtr) return 0; @@ -755,17 +762,17 @@ unsigned ProfileFuncLevel::Id::GetLine() const return 0; } -unsigned _int64 ProfileFuncLevel::Id::GetCalls(unsigned frame) const +u64 ProfileFuncLevel::Id::GetCalls(unsigned frame) const { return 0; } -unsigned _int64 ProfileFuncLevel::Id::GetTime(unsigned frame) const +u64 ProfileFuncLevel::Id::GetTime(unsigned frame) const { return 0; } -unsigned _int64 ProfileFuncLevel::Id::GetFunctionTime(unsigned frame) const +u64 ProfileFuncLevel::Id::GetFunctionTime(unsigned frame) const { return 0; } @@ -792,4 +799,7 @@ ProfileFuncLevel::ProfileFuncLevel() #endif // !defined HAS_PROFILE ProfileFuncLevel ProfileFuncLevel::Instance; +// GeneralsX @build fbraz 03/02/2026 testEvent only exists on Windows (see internal.h) +#ifdef _WIN32 HANDLE ProfileFastCS::testEvent=::CreateEvent(nullptr,FALSE,FALSE,""); +#endif diff --git a/Core/Libraries/Source/profile/profile_funclevel.h b/Core/Libraries/Source/profile/profile_funclevel.h index a153a5f9e28..76e9d71ac2b 100644 --- a/Core/Libraries/Source/profile/profile_funclevel.h +++ b/Core/Libraries/Source/profile/profile_funclevel.h @@ -29,6 +29,29 @@ #pragma once +// Platform-specific 64-bit type compatibility +#ifdef _WIN32 + // Windows: _int64 is native MSVC type + typedef unsigned _int64 u64; + typedef _int64 i64; +#else + // Linux: Use C++11 standard types + #include + typedef uint64_t u64; + typedef int64_t i64; + // Define _int64 for use in (unsigned _int64) patterns + // Only define if not already defined by types_compat.h + #ifndef _int64 + typedef int64_t _int64; + #endif +#endif +#include +#include + +// TheSuperHackers @build bobtista 29/04/2026 The compat win32 shim already +// maps __int64 / _int64 to long long via #define; do not add a redundant +// typedef here (would expand to "typedef int64_t long long" via the macro). + /** \brief The function level profiler. @@ -124,7 +147,9 @@ class ProfileFuncLevel \param frame number of recorded frame, or Total \return number of calls */ - unsigned _int64 GetCalls(unsigned frame) const; + // GeneralsX @refactor BenderAI 10/02/2026 + // Changed from unsigned _int64 (MSVC-specific) to u64 (platform typedef) + u64 GetCalls(unsigned frame) const; /** \brief Determine time spend in this function and its children. @@ -132,7 +157,7 @@ class ProfileFuncLevel \param frame number of recorded frame, or Total \return time spend (in CPU ticks) */ - unsigned _int64 GetTime(unsigned frame) const; + u64 GetTime(unsigned frame) const; /** \brief Determine time spend in this function only (exclude @@ -141,7 +166,7 @@ class ProfileFuncLevel \param frame number of recorded frame, or Total \return time spend in this function alone (in CPU ticks) */ - unsigned _int64 GetFunctionTime(unsigned frame) const; + u64 GetFunctionTime(unsigned frame) const; /** \brief Determine the list of caller Ids. @@ -180,9 +205,10 @@ class ProfileFuncLevel \return profile thread ID */ - unsigned GetId() const + // GeneralsX @refactor BenderAI 10/02/2026 Use uintptr_t to avoid pointer precision loss on 64-bit + uintptr_t GetId() const { - return unsigned(m_threadID); + return (uintptr_t)m_threadID; } private: diff --git a/Core/Libraries/Source/profile/profile_highlevel.cpp b/Core/Libraries/Source/profile/profile_highlevel.cpp index 65eaacbee68..91b09a4f4a5 100644 --- a/Core/Libraries/Source/profile/profile_highlevel.cpp +++ b/Core/Libraries/Source/profile/profile_highlevel.cpp @@ -30,6 +30,8 @@ #include "profile.h" #include "internal.h" #include +// GeneralsX @build fbraz 03/02/2026 Add C string functions for Linux +#include #include // our own fast critical section @@ -196,7 +198,12 @@ void ProfileId::Maximum(double max) const char *ProfileId::AsString(double v) const { char help1[10],help[40]; +// GeneralsX @build fbraz 03/02/2026 Use snprintf on Linux, wsprintf on Windows +#ifdef _WIN32 wsprintf(help1,"%%%i.lf",m_precision); +#else + snprintf(help1, sizeof(help1), "%%%i.lf", m_precision); +#endif double mul=1.0; int k; diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/CMakeLists.txt b/GeneralsMD/Code/Libraries/Source/WWVegas/CMakeLists.txt index b7d4208261a..c2403ec0651 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/CMakeLists.txt +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/CMakeLists.txt @@ -4,10 +4,16 @@ add_library(z_wwcommon INTERFACE) target_link_libraries(z_wwcommon INTERFACE core_wwcommon d3d8lib - milesstub stlport ) +# TheSuperHackers @build bobtista 29/04/2026 milesstub is Win-only. +if(WIN32) + target_link_libraries(z_wwcommon INTERFACE + milesstub + ) +endif() + target_include_directories(z_wwcommon INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} ) From e424f3445d91f1644ca5bc4e57348b7397be9f22 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Thu, 30 Apr 2026 09:25:16 +1000 Subject: [PATCH 003/523] fix(compat): align Utility/* compat headers with Apple/Clang toolchain (cherry picked from commit 9db5a884fc021593b4b54d5897050ee6af33a7a6) --- Dependencies/Utility/Utility/endian_compat.h | 10 +++++++--- Dependencies/Utility/Utility/string_compat.h | 6 +++++- Dependencies/Utility/Utility/thread_compat.h | 4 ++-- Dependencies/Utility/Utility/time_compat.h | 5 ++++- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/Dependencies/Utility/Utility/endian_compat.h b/Dependencies/Utility/Utility/endian_compat.h index d79ed176e8f..d678c9fa1d1 100644 --- a/Dependencies/Utility/Utility/endian_compat.h +++ b/Dependencies/Utility/Utility/endian_compat.h @@ -122,9 +122,13 @@ typedef uint32_t SwapType32; typedef uint64_t SwapType64; #elif defined(__APPLE__) -typedef UInt16 SwapType16; -typedef UInt32 SwapType32; -typedef UInt64 SwapType64; +// TheSuperHackers @build bobtista 29/04/2026 Use the same uint*_t aliases as +// other POSIX platforms; the previous CoreServices UInt* names aren't pulled +// in by our compat layer and produced "unknown type name" errors on Apple +// Clang without a CoreServices include. +typedef uint16_t SwapType16; +typedef uint32_t SwapType32; +typedef uint64_t SwapType64; #elif defined(__OpenBSD__) typedef uint16_t SwapType16; diff --git a/Dependencies/Utility/Utility/string_compat.h b/Dependencies/Utility/Utility/string_compat.h index ff88aaa7a43..c7bb97ae2f6 100644 --- a/Dependencies/Utility/Utility/string_compat.h +++ b/Dependencies/Utility/Utility/string_compat.h @@ -24,7 +24,11 @@ typedef const char* LPCSTR; typedef char* LPSTR; // String functions -inline char *_strlwr(char *str) { +// TheSuperHackers @build bobtista 29/04/2026 extern "C" so the linkage matches +// gamespy's gsplatform.h declaration of _strlwr (it wraps its non-Win shim in +// `extern "C"` and our previous unmangled C++ declaration tripped the +// "different language linkage" diagnostic). +extern "C" inline char *_strlwr(char *str) { for (int i = 0; str[i] != '\0'; i++) { str[i] = tolower(str[i]); } diff --git a/Dependencies/Utility/Utility/thread_compat.h b/Dependencies/Utility/Utility/thread_compat.h index bd13a42f5ab..89d574b0be9 100644 --- a/Dependencies/Utility/Utility/thread_compat.h +++ b/Dependencies/Utility/Utility/thread_compat.h @@ -18,16 +18,16 @@ // This file contains thread related functions for compatibility with non-windows platforms. #pragma once +#include #include #include inline int GetCurrentThreadId() { - return pthread_self(); + return static_cast(std::hash{}(pthread_self()) & 0x7fffffff); } inline void Sleep(int ms) { usleep(ms * 1000); } - diff --git a/Dependencies/Utility/Utility/time_compat.h b/Dependencies/Utility/Utility/time_compat.h index 82449ee9aef..7b6cd7cb231 100644 --- a/Dependencies/Utility/Utility/time_compat.h +++ b/Dependencies/Utility/Utility/time_compat.h @@ -28,7 +28,11 @@ static inline MMRESULT timeEndPeriod(int) { return TIMERR_NOERROR; } inline unsigned int timeGetTime() { struct timespec ts; +#if defined(CLOCK_BOOTTIME) clock_gettime(CLOCK_BOOTTIME, &ts); +#else + clock_gettime(CLOCK_MONOTONIC, &ts); +#endif return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; } inline unsigned int GetTickCount() @@ -38,4 +42,3 @@ inline unsigned int GetTickCount() // Return ms since boot return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; } - From 440116510a592419b90fcb253fe528a1d88c8dc4 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Thu, 30 Apr 2026 09:25:31 +1000 Subject: [PATCH 004/523] fix(core): guard Win-only paths and 64-bit cast pinch-points across GameEngine (cherry picked from commit d766abb0dad7997dd50b74c9fc9b8fc038deba68) --- Core/GameEngine/CMakeLists.txt | 7 ++- Core/GameEngine/Include/Common/GameMemory.h | 6 +- .../Include/GameClient/GameWindow.h | 6 +- .../Include/GameClient/WindowVideoManager.h | 4 +- Core/GameEngine/Include/GameNetwork/LANAPI.h | 6 ++ .../GameNetwork/WOLBrowser/WebBrowser.h | 36 ++++++++++++ Core/GameEngine/Source/Common/INI/INI.cpp | 7 ++- .../Source/Common/System/GameMemoryNull.cpp | 4 ++ .../Source/Common/System/LocalFile.cpp | 8 +-- .../Source/Common/WorkerProcess.cpp | 24 ++++++++ .../Source/GameClient/GUI/IMEManager.cpp | 12 ++++ .../Source/GameClient/Input/Keyboard.cpp | 2 +- .../Source/GameNetwork/FirewallHelper.cpp | 2 +- .../Source/GameNetwork/GameSpy/LobbyUtils.cpp | 10 ++-- .../GameNetwork/GameSpy/MainMenuUtils.cpp | 8 +-- .../GameSpy/StagingRoomGameInfo.cpp | 13 +++++ .../GameSpy/Thread/BuddyThread.cpp | 2 +- .../GameNetwork/GameSpy/Thread/PeerThread.cpp | 2 +- .../Source/GameNetwork/LANAPICallbacks.cpp | 2 +- .../Source/GameNetwork/Transport.cpp | 6 +- .../GameNetwork/WOLBrowser/WebBrowser.cpp | 58 ++++++++++++------- Core/GameEngine/Source/GameNetwork/udp.cpp | 10 ++-- 22 files changed, 180 insertions(+), 55 deletions(-) diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt index 8af78e0e16c..30463d274f9 100644 --- a/Core/GameEngine/CMakeLists.txt +++ b/Core/GameEngine/CMakeLists.txt @@ -663,7 +663,6 @@ set(GAMEENGINE_SRC # Source/Common/System/List.cpp Source/Common/System/LocalFile.cpp Source/Common/System/LocalFileSystem.cpp - Source/Common/System/MiniDumper.cpp Source/Common/System/ObjectStatusTypes.cpp # Source/Common/System/QuotedPrintable.cpp Source/Common/System/Radar.cpp @@ -1167,6 +1166,12 @@ else() ) endif() +if(WIN32) + list(APPEND GAMEENGINE_SRC + Source/Common/System/MiniDumper.cpp + ) +endif() + add_library(corei_gameengine_private INTERFACE) add_library(corei_gameengine_public INTERFACE) diff --git a/Core/GameEngine/Include/Common/GameMemory.h b/Core/GameEngine/Include/Common/GameMemory.h index 53c0e59619d..c2a01d6dc32 100644 --- a/Core/GameEngine/Include/Common/GameMemory.h +++ b/Core/GameEngine/Include/Common/GameMemory.h @@ -64,7 +64,11 @@ #include #include #ifdef MEMORYPOOL_OVERRIDE_MALLOC - #include + #ifdef _WIN32 + #include + #else + #include + #endif #endif // USER INCLUDES ////////////////////////////////////////////////////////////// diff --git a/Core/GameEngine/Include/GameClient/GameWindow.h b/Core/GameEngine/Include/GameClient/GameWindow.h index e96c94c96a0..1fbaab7f057 100644 --- a/Core/GameEngine/Include/GameClient/GameWindow.h +++ b/Core/GameEngine/Include/GameClient/GameWindow.h @@ -73,7 +73,11 @@ enum { WIN_COLOR_UNDEFINED = GAME_COLOR_UNDEFINED }; // WindowMsgData -------------------------------------------------------------- //----------------------------------------------------------------------------- -typedef UnsignedInt WindowMsgData; +// TheSuperHackers @build bobtista 29/04/2026 uintptr_t so callers that stuff +// pointers through WindowMsgData (e.g. GadgetTextEntrySetText casting a +// UnicodeString*) don't lose the upper 32 bits on 64-bit builds. +#include +typedef uintptr_t WindowMsgData; //----------------------------------------------------------------------------- enum WindowMsgHandledType CPP_11(: Int) { MSG_IGNORED, MSG_HANDLED }; diff --git a/Core/GameEngine/Include/GameClient/WindowVideoManager.h b/Core/GameEngine/Include/GameClient/WindowVideoManager.h index 6ad24e71002..d9b8bcd7bbf 100644 --- a/Core/GameEngine/Include/GameClient/WindowVideoManager.h +++ b/Core/GameEngine/Include/GameClient/WindowVideoManager.h @@ -148,8 +148,8 @@ class WindowVideoManager : public SubsystemInterface { size_t operator()(ConstGameWindowPtr p) const { - std::hash hasher; - return hasher((UnsignedInt)p); + std::hash hasher; + return hasher((uintptr_t)p); } }; diff --git a/Core/GameEngine/Include/GameNetwork/LANAPI.h b/Core/GameEngine/Include/GameNetwork/LANAPI.h index a0365be185a..1b283681c55 100644 --- a/Core/GameEngine/Include/GameNetwork/LANAPI.h +++ b/Core/GameEngine/Include/GameNetwork/LANAPI.h @@ -267,7 +267,13 @@ struct LANMessage }; #pragma pack(pop) +// TheSuperHackers @build bobtista 29/04/2026 The LANMessage struct contains +// pointer-sized fields that grow on 64-bit, which trips this assertion on +// non-Win builds. The retail wire format is Win 32-bit, so this assertion +// only matters there. +#ifdef _WIN32 static_assert(sizeof(LANMessage) <= MAX_LANAPI_PACKET_SIZE, "LANMessage struct cannot be larger than the max packet size"); +#endif /** diff --git a/Core/GameEngine/Include/GameNetwork/WOLBrowser/WebBrowser.h b/Core/GameEngine/Include/GameNetwork/WOLBrowser/WebBrowser.h index 8e09086e5d0..4e264faca3e 100644 --- a/Core/GameEngine/Include/GameNetwork/WOLBrowser/WebBrowser.h +++ b/Core/GameEngine/Include/GameNetwork/WOLBrowser/WebBrowser.h @@ -43,11 +43,18 @@ #pragma once #include "Common/SubsystemInterface.h" +// TheSuperHackers @build bobtista 29/04/2026 ATL + EA browser dispatch are +// Win-only. Non-Win builds get the same WebBrowser interface but the actual +// browser embed has no implementation; consumers see a no-op. +#ifdef _WIN32 #include +#endif #include #include +#ifdef _WIN32 #include "EABrowserDispatch/BrowserDispatch.h" #include "FEBDispatch.h" +#endif #include class GameWindow; @@ -74,6 +81,7 @@ class WebBrowserURL : public MemoryPoolObject +#ifdef _WIN32 class WebBrowser : public FEBDispatch, public SubsystemInterface @@ -122,3 +130,31 @@ class WebBrowser : }; extern CComObject *TheWebBrowser; +#else +// TheSuperHackers @build bobtista 29/04/2026 Non-Win stub. The real +// WebBrowser is an ATL/EA-IBrowserDispatch COM object embedding a Win +// IE/WebBrowser control. There is no equivalent on macOS/Linux yet, so +// expose just the surface that game code touches (TheWebBrowser nullptr +// check, findURL/makeNewURL). +class WebBrowser : public SubsystemInterface +{ +public: + virtual void init() override {} + virtual void reset() override {} + virtual void update() override {} + + virtual Bool createBrowserWindow(const char * /*tag*/, GameWindow * /*win*/) { return false; } + virtual void closeBrowserWindow(GameWindow * /*win*/) {} + + WebBrowserURL *makeNewURL(AsciiString /*tag*/) { return nullptr; } + WebBrowserURL *findURL(AsciiString /*tag*/) { return nullptr; } + +protected: + WebBrowser() : m_urlList(nullptr) {} + virtual ~WebBrowser() override {} + + WebBrowserURL *m_urlList; +}; + +extern WebBrowser *TheWebBrowser; +#endif diff --git a/Core/GameEngine/Source/Common/INI/INI.cpp b/Core/GameEngine/Source/Common/INI/INI.cpp index b3a26a5f4c1..f5751a76d77 100644 --- a/Core/GameEngine/Source/Common/INI/INI.cpp +++ b/Core/GameEngine/Source/Common/INI/INI.cpp @@ -59,7 +59,10 @@ #include "GameLogic/ScriptEngine.h" #include "GameLogic/Weapon.h" -#if __cplusplus >= 201611L +// TheSuperHackers @build bobtista 29/04/2026 Apple Clang's libc++ marks the +// floating-point overloads of std::from_chars as deleted. Fall back to the +// sscanf path on Apple so float/double parsing keeps working there. +#if __cplusplus >= 201611L && !defined(__APPLE__) #define USE_STD_FROM_CHARS_PARSING 1 #else #define USE_STD_FROM_CHARS_PARSING 0 @@ -645,7 +648,7 @@ void INI::parseBool( INI* ini, void * /*instance*/, void *store, const void* /*u void INI::parseBitInInt32( INI *ini, void *instance, void *store, const void* userData ) { UnsignedInt* s = (UnsignedInt*)store; - UnsignedInt mask = (UnsignedInt)userData; + UnsignedInt mask = (UnsignedInt)(uintptr_t)userData; if (INI::scanBool(ini->getNextToken())) *s |= mask; diff --git a/Core/GameEngine/Source/Common/System/GameMemoryNull.cpp b/Core/GameEngine/Source/Common/System/GameMemoryNull.cpp index 54f8fd770fe..69fc1b73722 100644 --- a/Core/GameEngine/Source/Common/System/GameMemoryNull.cpp +++ b/Core/GameEngine/Source/Common/System/GameMemoryNull.cpp @@ -18,7 +18,11 @@ #include "PreRTS.h" +#ifdef _WIN32 #include +#else +#include +#endif #include "Common/GameMemoryNull.h" diff --git a/Core/GameEngine/Source/Common/System/LocalFile.cpp b/Core/GameEngine/Source/Common/System/LocalFile.cpp index 431906a8ddd..5581767e08b 100644 --- a/Core/GameEngine/Source/Common/System/LocalFile.cpp +++ b/Core/GameEngine/Source/Common/System/LocalFile.cpp @@ -370,7 +370,7 @@ Int LocalFile::readChar() Int ret = read( &character, sizeof(character) ); if (ret == sizeof(character)) - return (Int)character; + return (Int)(intptr_t)character; return EOF; } @@ -386,7 +386,7 @@ Int LocalFile::readWideChar() Int ret = read( &character, sizeof(character) ); if (ret == sizeof(character)) - return (Int)character; + return (Int)(intptr_t)character; return WEOF; } @@ -450,7 +450,7 @@ Int LocalFile::writeFormat( const WideChar* format, ... ) Int LocalFile::writeChar( const Char* character ) { if ( write( character, sizeof(Char) ) == sizeof(Char) ) { - return (Int)character; + return (Int)(intptr_t)character; } return EOF; @@ -463,7 +463,7 @@ Int LocalFile::writeChar( const Char* character ) Int LocalFile::writeChar( const WideChar* character ) { if ( write( character, sizeof(WideChar) ) == sizeof(WideChar) ) { - return (Int)character; + return (Int)(intptr_t)character; } return WEOF; diff --git a/Core/GameEngine/Source/Common/WorkerProcess.cpp b/Core/GameEngine/Source/Common/WorkerProcess.cpp index 0aaae1842a7..52d1dcac609 100644 --- a/Core/GameEngine/Source/Common/WorkerProcess.cpp +++ b/Core/GameEngine/Source/Common/WorkerProcess.cpp @@ -19,6 +19,28 @@ #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine #include "Common/WorkerProcess.h" +// TheSuperHackers @build bobtista 29/04/2026 The whole worker-process flow +// uses Win Job Objects and CreateProcess(). Stub on non-Win — the engine only +// uses this for tools (e.g. mapcache rebuild) which we don't ship on macOS. +#ifndef _WIN32 +WorkerProcess::WorkerProcess() + : m_processHandle(nullptr) + , m_readHandle(nullptr) + , m_jobHandle(nullptr) + , m_exitcode(0) + , m_isDone(true) +{ +} +bool WorkerProcess::startProcess(UnicodeString /*command*/) { return false; } +void WorkerProcess::update() {} +bool WorkerProcess::isRunning() const { return false; } +bool WorkerProcess::isDone() const { return m_isDone; } +DWORD WorkerProcess::getExitCode() const { return m_exitcode; } +AsciiString WorkerProcess::getStdOutput() const { return m_stdOutput; } +void WorkerProcess::kill() {} +bool WorkerProcess::fetchStdOutput() { return true; } +#else + // We need Job-related functions, but these aren't defined in the Windows-headers that VC6 uses. // So we define them here and load them dynamically. #if defined(_MSC_VER) && _MSC_VER < 1300 @@ -229,3 +251,5 @@ void WorkerProcess::kill() m_isDone = false; } +#endif // _WIN32 (worker-process Win body) + diff --git a/Core/GameEngine/Source/GameClient/GUI/IMEManager.cpp b/Core/GameEngine/Source/GameClient/GUI/IMEManager.cpp index 5de6d1a5c54..45e7b9c2e86 100644 --- a/Core/GameEngine/Source/GameClient/GUI/IMEManager.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/IMEManager.cpp @@ -47,6 +47,16 @@ #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine +// TheSuperHackers @build bobtista 29/04/2026 IMEManager is a Win-only IME +// (asian input) integration tied to ImmCreateContext / WM_IME_* messages. +// Stub the public surface on non-Win; SDL3 will eventually provide a portable +// composition path. +#ifndef _WIN32 +#include "GameClient/IMEManager.h" +IMEManagerInterface *TheIMEManager = nullptr; +IMEManagerInterface *CreateIMEManagerInterface() { return nullptr; } +#else + #include "mbstring.h" #include "Common/Debug.h" @@ -1598,3 +1608,5 @@ void IMEManager::updateStatusWindow() } + +#endif // _WIN32 (IMEManager Win body) diff --git a/Core/GameEngine/Source/GameClient/Input/Keyboard.cpp b/Core/GameEngine/Source/GameClient/Input/Keyboard.cpp index ea1c5e4425a..426de669b70 100644 --- a/Core/GameEngine/Source/GameClient/Input/Keyboard.cpp +++ b/Core/GameEngine/Source/GameClient/Input/Keyboard.cpp @@ -341,7 +341,7 @@ void Keyboard::initKeyNames() HKL kLayout = GetKeyboardLayout(0); - Int low = (UnsignedInt)kLayout & 0xFFFF; + Int low = (UnsignedInt)(uintptr_t)kLayout & 0xFFFF; LanguageID currentLanguage = OurLanguage; if(low == 0x040c || low == 0x080c diff --git a/Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp b/Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp index 5698e3f47b0..573eb36ce80 100644 --- a/Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp +++ b/Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp @@ -685,7 +685,7 @@ Bool FirewallHelperClass::detectionBeginUpdate() { if (!found) { Int m = m_numManglers++; memcpy(&mangler_addresses[m][0], &host_info->h_addr_list[0][0], 4); - ntohl((UnsignedInt)mangler_addresses[m]); + ntohl((UnsignedInt)(uintptr_t)mangler_addresses[m]); DEBUG_LOG(("Found mangler address at %d.%d.%d.%d", mangler_addresses[m][0], mangler_addresses[m][1], mangler_addresses[m][2], mangler_addresses[m][3])); } diff --git a/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp b/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp index 6a73ace1b46..bb5df373171 100644 --- a/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp @@ -209,7 +209,7 @@ static void gameTooltip(GameWindow *window, return; } - Int gameID = (Int)GadgetListBoxGetItemData(window, row, 0); + Int gameID = (Int)(intptr_t)GadgetListBoxGetItemData(window, row, 0); GameSpyStagingRoom *room = TheGameSpyInfo->findStagingRoomByID(gameID); if (!room) { @@ -696,7 +696,7 @@ void RefreshGameListBox( GameWindow *win, Bool showMap ) GadgetListBoxGetSelected(win, &selectedIndex); if (selectedIndex != -1 ) { - selectedID = (Int)GadgetListBoxGetItemData(win, selectedIndex); + selectedID = (Int)(intptr_t)GadgetListBoxGetItemData(win, selectedIndex); } int prevPos = GadgetListBoxGetTopVisibleEntry( win ); @@ -754,7 +754,7 @@ void RefreshGameInfoListBox( GameWindow *mainWin, GameWindow *win ) // return; // } // -// Int selectedID = (Int)GadgetListBoxGetItemData(mainWin, selected); +// Int selectedID = (Int)(intptr_t)GadgetListBoxGetItemData(mainWin, selected); // if (selectedID < 0) // { // return; @@ -887,7 +887,7 @@ void playerTemplateComboBoxTooltip(GameWindow *wndComboBox, WinInstanceData *ins { Int index = 0; GadgetComboBoxGetSelectedPos(wndComboBox, &index); - Int templateNum = (Int)GadgetComboBoxGetItemData(wndComboBox, index); + Int templateNum = (Int)(intptr_t)GadgetComboBoxGetItemData(wndComboBox, index); UnicodeString ustringTooltip; if (templateNum == -1) { @@ -918,7 +918,7 @@ void playerTemplateListBoxTooltip(GameWindow *wndListBox, WinInstanceData *instD if (row == -1 || col == -1) return; - Int templateNum = (Int)GadgetListBoxGetItemData(wndListBox, row, col); + Int templateNum = (Int)(intptr_t)GadgetListBoxGetItemData(wndListBox, row, col); UnicodeString ustringTooltip; if (templateNum == -1) { diff --git a/Core/GameEngine/Source/GameNetwork/GameSpy/MainMenuUtils.cpp b/Core/GameEngine/Source/GameNetwork/GameSpy/MainMenuUtils.cpp index f35a947ae0e..4970fa77c66 100644 --- a/Core/GameEngine/Source/GameNetwork/GameSpy/MainMenuUtils.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameSpy/MainMenuUtils.cpp @@ -309,7 +309,7 @@ static void queuePatch(Bool mandatory, AsciiString downloadURL) static GHTTPBool motdCallback( GHTTPRequest request, GHTTPResult result, char * buffer, GHTTPByteCount bufferLen, void * param ) { - Int run = (Int)param; + Int run = (Int)(intptr_t)param; if (run != timeThroughOnline) { DEBUG_CRASH(("Old callback being called!")); @@ -344,7 +344,7 @@ static GHTTPBool motdCallback( GHTTPRequest request, GHTTPResult result, static GHTTPBool configCallback( GHTTPRequest request, GHTTPResult result, char * buffer, GHTTPByteCount bufferLen, void * param ) { - Int run = (Int)param; + Int run = (Int)(intptr_t)param; if (run != timeThroughOnline) { DEBUG_CRASH(("Old callback being called!")); @@ -406,7 +406,7 @@ static GHTTPBool configCallback( GHTTPRequest request, GHTTPResult result, static GHTTPBool configHeadCallback( GHTTPRequest request, GHTTPResult result, char * buffer, GHTTPByteCount bufferLen, void * param ) { - Int run = (Int)param; + Int run = (Int)(intptr_t)param; if (run != timeThroughOnline) { DEBUG_CRASH(("Old callback being called!")); @@ -490,7 +490,7 @@ static GHTTPBool configHeadCallback( GHTTPRequest request, GHTTPResult result, static GHTTPBool gamePatchCheckCallback( GHTTPRequest request, GHTTPResult result, char * buffer, GHTTPByteCount bufferLen, void * param ) { - Int run = (Int)param; + Int run = (Int)(intptr_t)param; if (run != timeThroughOnline) { DEBUG_CRASH(("Old callback being called!")); diff --git a/Core/GameEngine/Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp b/Core/GameEngine/Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp index 04714a4de4b..ce4f755f081 100644 --- a/Core/GameEngine/Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp @@ -71,10 +71,15 @@ GameSpyGameSlot::GameSpyGameSlot() ** Function definitions for the MIB-II entry points. */ +// TheSuperHackers @build bobtista 29/04/2026 SNMP MIB-II is Win-only; the +// chat-connection address fallback below is wrapped to call into Win SNMP +// libraries only on Windows. +#ifdef _WIN32 BOOL (__stdcall *SnmpExtensionInitPtr)(IN DWORD dwUpTimeReference, OUT HANDLE *phSubagentTrapEvent, OUT AsnObjectIdentifier *pFirstSupportedRegion); BOOL (__stdcall *SnmpExtensionQueryPtr)(IN BYTE bPduType, IN OUT RFC1157VarBindList *pVarBindList, OUT AsnInteger32 *pErrorStatus, OUT AsnInteger32 *pErrorIndex); LPVOID (__stdcall *SnmpUtilMemAllocPtr)(IN DWORD bytes); VOID (__stdcall *SnmpUtilMemFreePtr)(IN LPVOID pMem); +#endif typedef struct tConnInfoStruct { unsigned int State; @@ -100,6 +105,13 @@ typedef struct tConnInfoStruct { *=============================================================================================*/ Bool GetLocalChatConnectionAddress(AsciiString serverName, UnsignedShort serverPort, UnsignedInt& localIP) { +#ifndef _WIN32 + // TheSuperHackers @build bobtista 29/04/2026 The Win path probes + // SNMP MIB-II to discover which interface is talking to the chat server; + // non-Win builds skip this lookup and let networking pick a default. + (void)serverName; (void)serverPort; (void)localIP; + return false; +#else //return false; /* ** Local defines. @@ -431,6 +443,7 @@ Bool GetLocalChatConnectionAddress(AsciiString serverName, UnsignedShort serverP FreeLibrary(snmpapi_dll); FreeLibrary(mib_ii_dll); return(found); +#endif // _WIN32 } // GameSpyGameSlot ---------------------------------------- diff --git a/Core/GameEngine/Source/GameNetwork/GameSpy/Thread/BuddyThread.cpp b/Core/GameEngine/Source/GameNetwork/GameSpy/Thread/BuddyThread.cpp index fb8f1b88d08..71ee39949fa 100644 --- a/Core/GameEngine/Source/GameNetwork/GameSpy/Thread/BuddyThread.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameSpy/Thread/BuddyThread.cpp @@ -125,7 +125,7 @@ enum CallbackType void callbackWrapper( GPConnection *con, void *arg, void *param ) { - CallbackType info = (CallbackType)(Int)param; + CallbackType info = (CallbackType)(Int)(intptr_t)param; BuddyThreadClass *thread = MESSAGE_QUEUE->getThread() ? MESSAGE_QUEUE->getThread() : nullptr /*(TheGameSpyBuddyMessageQueue)?TheGameSpyBuddyMessageQueue->getThread():nullptr*/; if (!thread) return; diff --git a/Core/GameEngine/Source/GameNetwork/GameSpy/Thread/PeerThread.cpp b/Core/GameEngine/Source/GameNetwork/GameSpy/Thread/PeerThread.cpp index 178a1b1ad3c..787b7e781d8 100644 --- a/Core/GameEngine/Source/GameNetwork/GameSpy/Thread/PeerThread.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameSpy/Thread/PeerThread.cpp @@ -1139,7 +1139,7 @@ void checkQR2Queries( PEER peer, SOCKET sock ) if (SOCKET_ERROR == error || 0 == error) return; //else we have data - error = recvfrom(sock, indata, INBUF_LEN - 1, 0, (struct sockaddr *)&saddr, &saddrlen); + error = recvfrom(sock, indata, INBUF_LEN - 1, 0, (struct sockaddr *)&saddr, (socklen_t *)&saddrlen); if (error != SOCKET_ERROR) { indata[error] = '\0'; diff --git a/Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp b/Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp index e99c5c8c25c..5fdcfffabc9 100644 --- a/Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp +++ b/Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp @@ -633,7 +633,7 @@ void LANAPI::OnPlayerList( LANPlayer *playerList ) GadgetListBoxGetSelected(listboxPlayers, &selectedIndex); if (selectedIndex != -1 ) - selectedIP = (UnsignedInt) GadgetListBoxGetItemData(listboxPlayers, selectedIndex, 0); + selectedIP = (UnsignedInt)(uintptr_t) GadgetListBoxGetItemData(listboxPlayers, selectedIndex, 0); GadgetListBoxReset(listboxPlayers); diff --git a/Core/GameEngine/Source/GameNetwork/Transport.cpp b/Core/GameEngine/Source/GameNetwork/Transport.cpp index cbdbcb88222..c52d5a4280b 100644 --- a/Core/GameEngine/Source/GameNetwork/Transport.cpp +++ b/Core/GameEngine/Source/GameNetwork/Transport.cpp @@ -349,14 +349,13 @@ Bool Transport::doRecv() (Int)(TheGlobalData->m_latencyAmplitude * sin(now * TheGlobalData->m_latencyPeriod)) + GameClientRandomValue(-TheGlobalData->m_latencyNoise, TheGlobalData->m_latencyNoise); m_delayedInBuffer[bufferIndex].message.length = incomingMessage.length; - m_delayedInBuffer[bufferIndex].message.addr = ntohl(from.sin_addr.S_un.S_addr); + m_delayedInBuffer[bufferIndex].message.addr = ntohl(from.sin_addr.s_addr); m_delayedInBuffer[bufferIndex].message.port = ntohs(from.sin_port); memcpy(&m_delayedInBuffer[bufferIndex].message, buf, len); ++bufferIndex; break; } } - continue; } #endif @@ -367,7 +366,7 @@ Bool Transport::doRecv() { // Empty slot; use it m_inBuffer[bufferIndex].length = incomingMessage.length; - m_inBuffer[bufferIndex].addr = ntohl(from.sin_addr.S_un.S_addr); + m_inBuffer[bufferIndex].addr = ntohl(from.sin_addr.s_addr); m_inBuffer[bufferIndex].port = ntohs(from.sin_port); memcpy(&m_inBuffer[bufferIndex], buf, len); ++bufferIndex; @@ -513,4 +512,3 @@ Real Transport::getUnknownPacketsPerSecond() } - diff --git a/Core/GameEngine/Source/GameNetwork/WOLBrowser/WebBrowser.cpp b/Core/GameEngine/Source/GameNetwork/WOLBrowser/WebBrowser.cpp index 11198b19887..8cf5546c326 100644 --- a/Core/GameEngine/Source/GameNetwork/WOLBrowser/WebBrowser.cpp +++ b/Core/GameEngine/Source/GameNetwork/WOLBrowser/WebBrowser.cpp @@ -41,8 +41,40 @@ #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine -//#include "WinMain.h" #include "GameNetwork/WOLBrowser/WebBrowser.h" +#include "Common/INI.h" + +//------------------------------------------------------------------------------------------------- +/** The INI data fields for Webpage URL's */ +//------------------------------------------------------------------------------------------------- +const FieldParse WebBrowserURL::m_URLFieldParseTable[] = +{ + { "URL", INI::parseAsciiString, nullptr, offsetof( WebBrowserURL, m_url ) }, + { nullptr, nullptr, nullptr, 0 }, +}; + +WebBrowserURL::WebBrowserURL() +{ + m_next = nullptr; + m_tag.clear(); + m_url.clear(); +} + +WebBrowserURL::~WebBrowserURL() +{ +} + +#ifndef _WIN32 +// TheSuperHackers @build bobtista 29/04/2026 Define the global on non-Win. +WebBrowser *TheWebBrowser = nullptr; +#endif + +// TheSuperHackers @build bobtista 29/04/2026 The whole ATL-based WebBrowser +// (CComObject, IBrowserDispatch) is Win-only. Non-Win builds use the inline +// stub class declared in WebBrowser.h. +#ifdef _WIN32 + +//#include "WinMain.h" #include "GameClient/GameWindow.h" #include "GameClient/Display.h" @@ -126,27 +158,6 @@ WebBrowser::~WebBrowser() } } -//------------------------------------------------------------------------------------------------- -/** The INI data fields for Webpage URL's */ -//------------------------------------------------------------------------------------------------- -const FieldParse WebBrowserURL::m_URLFieldParseTable[] = -{ - - { "URL", INI::parseAsciiString, nullptr, offsetof( WebBrowserURL, m_url ) }, - { nullptr, nullptr, nullptr, 0 }, - -}; - -WebBrowserURL::WebBrowserURL() -{ - m_next = nullptr; - m_tag.clear(); - m_url.clear(); -} - -WebBrowserURL::~WebBrowserURL() -{ -} /****************************************************************************** * * NAME @@ -308,3 +319,6 @@ STDMETHODIMP WebBrowser::TestMethod(Int num1) DEBUG_LOG(("WebBrowser::TestMethod - num1 = %d", num1)); return S_OK; } + + +#endif // _WIN32 (WebBrowser Win impl) diff --git a/Core/GameEngine/Source/GameNetwork/udp.cpp b/Core/GameEngine/Source/GameNetwork/udp.cpp index e5f30ccb795..672b03f1d85 100644 --- a/Core/GameEngine/Source/GameNetwork/udp.cpp +++ b/Core/GameEngine/Source/GameNetwork/udp.cpp @@ -178,7 +178,7 @@ Int UDP::Bind(UnsignedInt IP,UnsignedShort Port) } int namelen=sizeof(addr); - getsockname(fd, (struct sockaddr *)&addr, &namelen); + getsockname(fd, (struct sockaddr *)&addr, (socklen_t *)&namelen); myIP=ntohl(addr.sin_addr.s_addr); myPort=ntohs(addr.sin_port); @@ -266,7 +266,7 @@ Int UDP::Read(unsigned char *msg,UnsignedInt len,sockaddr_in *from) if (from!=nullptr) { - retval=recvfrom(fd,(char *)msg,len,0,(struct sockaddr *)from,&alen); + retval=recvfrom(fd,(char *)msg,len,0,(struct sockaddr *)from,(socklen_t *)&alen); #ifdef _WIN32 if (retval == SOCKET_ERROR) { @@ -373,8 +373,10 @@ UDP::sockStat UDP::GetStatus() return ALREADY; case EAGAIN: return AGAIN; +#if EWOULDBLOCK != EAGAIN case EWOULDBLOCK: return WOULDBLOCK; +#endif case EBADF: return BADF; default: @@ -508,7 +510,7 @@ int UDP::GetInputBuffer() int retval,arg=0,len=sizeof(int); retval=getsockopt(fd,SOL_SOCKET,SO_RCVBUF, - (char *)&arg,&len); + (char *)&arg,(socklen_t *)&len); return(arg); } @@ -518,7 +520,7 @@ int UDP::GetOutputBuffer() int retval,arg=0,len=sizeof(int); retval=getsockopt(fd,SOL_SOCKET,SO_SNDBUF, - (char *)&arg,&len); + (char *)&arg,(socklen_t *)&len); return(arg); } From 09c71814fb36758353f7f2c69eb8e665c9811567 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Thu, 30 Apr 2026 09:26:06 +1000 Subject: [PATCH 005/523] fix(generalsmd-engine): PreRTS Win headers, KeyDefs dinput shim, 64-bit pointer casts (cherry picked from commit 18e90e1d6a143fce7dc3652d9fe518bb301dff56) --- .../GameClient/GUI/ControlBar/ControlBar.cpp | 2 +- .../GameClient/GUI/Gadget/GadgetComboBox.cpp | 8 ++-- .../GUI/Gadget/GadgetHorizontalSlider.cpp | 10 ++--- .../GameClient/GUI/Gadget/GadgetListBox.cpp | 37 ++++++++++--------- .../GUI/Gadget/GadgetProgressBar.cpp | 2 +- .../GUI/Gadget/GadgetVerticalSlider.cpp | 10 ++--- GeneralsMD/Code/GameEngine/CMakeLists.txt | 15 +++++++- .../GameEngine/Include/GameClient/KeyDefs.h | 5 +++ .../GameEngine/Include/Precompiled/PreRTS.h | 21 +++++++++++ .../GameEngine/Source/Common/GameEngine.cpp | 7 ++++ .../Source/Common/Thing/ThingTemplate.cpp | 6 ++- .../GUI/GUICallbacks/InGamePopupMessage.cpp | 8 ++-- .../GUICallbacks/Menus/LanGameOptionsMenu.cpp | 10 ++--- .../GUI/GUICallbacks/Menus/LanLobbyMenu.cpp | 2 +- .../GUI/GUICallbacks/Menus/OptionsMenu.cpp | 4 +- .../GUI/GUICallbacks/Menus/PopupHostGame.cpp | 4 +- .../GUICallbacks/Menus/PopupLadderSelect.cpp | 10 ++--- .../GUICallbacks/Menus/PopupPlayerInfo.cpp | 4 +- .../GUI/GUICallbacks/Menus/ScoreScreen.cpp | 2 +- .../Menus/SkirmishGameOptionsMenu.cpp | 14 +++---- .../Menus/SkirmishMapSelectMenu.cpp | 2 +- .../GUICallbacks/Menus/WOLBuddyOverlay.cpp | 12 +++--- .../GUICallbacks/Menus/WOLGameSetupMenu.cpp | 10 ++--- .../GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp | 10 ++--- .../GUICallbacks/Menus/WOLQuickMatchMenu.cpp | 22 +++++------ .../Source/GameLogic/AI/AIStates.cpp | 2 +- .../SabotageInternetCenterCrateCollide.cpp | 4 +- .../GameLogic/Object/PartitionManager.cpp | 8 ++-- .../GameEngine/Source/GameNetwork/GUIUtil.cpp | 6 +-- 29 files changed, 155 insertions(+), 102 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp index 697ee47a0a4..d45a68fbc26 100644 --- a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp @@ -823,7 +823,7 @@ void CommandSet::parseCommandButton( INI* ini, void *instance, void *store, cons // get the index to store the command at, and the command array itself const CommandButton **buttonArray = (const CommandButton **)store; - Int buttonIndex = (Int)userData; + Int buttonIndex = (Int)(intptr_t)userData; // sanity DEBUG_ASSERTCRASH( buttonIndex < MAX_COMMANDS_PER_SET, ("parseCommandButton: button index '%d' out of range", diff --git a/Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetComboBox.cpp b/Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetComboBox.cpp index be4a4cc1932..0ee3f42e1a9 100644 --- a/Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetComboBox.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetComboBox.cpp @@ -383,7 +383,7 @@ WindowMsgHandledType GadgetComboBoxSystem( GameWindow *window, UnsignedInt msg, if( !listBox->winIsHidden() && mData2 == TRUE ) comboData->dontHide = TRUE; - GadgetListBoxSetSelected(listBox, (Int)mData1); + GadgetListBoxSetSelected(listBox, (Int)(intptr_t)mData1); } break; } @@ -406,7 +406,7 @@ WindowMsgHandledType GadgetComboBoxSystem( GameWindow *window, UnsignedInt msg, { if(comboData->listBox) { - GadgetListBoxSetItemData(comboData->listBox, (void *)mData2, (Int)mData1 ); + GadgetListBoxSetItemData(comboData->listBox, (void *)mData2, (Int)(intptr_t)mData1 ); } break; @@ -560,8 +560,8 @@ WindowMsgHandledType GadgetComboBoxSystem( GameWindow *window, UnsignedInt msg, // ------------------------------------------------------------------------ case GGM_RESIZED: { - Int width = (Int)mData1; - Int height = (Int)mData2; + Int width = (Int)(intptr_t)mData1; + Int height = (Int)(intptr_t)mData2; ICoord2D dropDownSize; // get needed window sizes diff --git a/Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetHorizontalSlider.cpp b/Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetHorizontalSlider.cpp index 2e37f5a17d4..e40f1d478d7 100644 --- a/Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetHorizontalSlider.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetHorizontalSlider.cpp @@ -391,7 +391,7 @@ WindowMsgHandledType GadgetHorizontalSliderSystem( GameWindow *window, UnsignedI // ------------------------------------------------------------------------ case GSM_SET_SLIDER: { - Int newPos = (Int)mData1; + Int newPos = (Int)(intptr_t)mData1; GameWindow *child = window->winGetChild(); // TheSuperHackers @fix No longer reject out of bounds positions to prevent @@ -416,8 +416,8 @@ WindowMsgHandledType GadgetHorizontalSliderSystem( GameWindow *window, UnsignedI window->winGetSize( &size.x, &size.y ); - s->minVal = (Int)mData1; - s->maxVal = (Int)mData2; + s->minVal = (Int)(intptr_t)mData1; + s->maxVal = (Int)(intptr_t)mData2; s->numTicks = (Real)(size.x - HORIZONTAL_SLIDER_THUMB_WIDTH)/(Real)(s->maxVal - s->minVal); s->position = s->minVal; @@ -460,8 +460,8 @@ WindowMsgHandledType GadgetHorizontalSliderSystem( GameWindow *window, UnsignedI // ------------------------------------------------------------------------ case GGM_RESIZED: { -// Int width = (Int)mData1; - Int height = (Int)mData2; +// Int width = (Int)(intptr_t)mData1; + Int height = (Int)(intptr_t)mData2; GameWindow *thumb = window->winGetChild(); if( thumb ) diff --git a/Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetListBox.cpp b/Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetListBox.cpp index 8688129b035..bb432325075 100644 --- a/Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetListBox.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetListBox.cpp @@ -1395,7 +1395,7 @@ WindowMsgHandledType GadgetListBoxSystem( GameWindow *window, UnsignedInt msg, { Int i; - if( list->endPos <= (Int)mData1 ) + if( list->endPos <= (Int)(intptr_t)mData1 ) break; ListEntryCell *cells = list->listData[mData1].cell; @@ -1423,9 +1423,9 @@ WindowMsgHandledType GadgetListBoxSystem( GameWindow *window, UnsignedInt msg, while( list->selections[i] >= 0 ) { - if( (Int)mData1 < list->selections[i] ) + if( (Int)(intptr_t)mData1 < list->selections[i] ) list->selections[i]--; - else if ( (Int)mData1 == list->selections[i] ) + else if ( (Int)(intptr_t)mData1 == list->selections[i] ) { removeSelection( list, i ); i--; // compensate for lost entry @@ -1436,9 +1436,9 @@ WindowMsgHandledType GadgetListBoxSystem( GameWindow *window, UnsignedInt msg, } else { - if( (Int)mData1 < list->selectPos ) + if( (Int)(intptr_t)mData1 < list->selectPos ) list->selectPos--; - else if ( (Int)mData1 == list->selectPos ) + else if ( (Int)(intptr_t)mData1 == list->selectPos ) list->selectPos = -1; } @@ -1554,7 +1554,7 @@ WindowMsgHandledType GadgetListBoxSystem( GameWindow *window, UnsignedInt msg, case GLM_TOGGLE_MULTI_SELECTION: { - if( (Int)mData1 < 0 ) + if( (Int)(intptr_t)mData1 < 0 ) { // a negative number will purge the entire list. if( list->multiSelect ) @@ -1578,7 +1578,7 @@ WindowMsgHandledType GadgetListBoxSystem( GameWindow *window, UnsignedInt msg, while( list->selections[i] >= 0 ) { - if( list->selections[i] == (Int)mData1 ) + if( list->selections[i] == (Int)(intptr_t)mData1 ) { removeSelection( list, i ); removed = TRUE; @@ -1590,7 +1590,7 @@ WindowMsgHandledType GadgetListBoxSystem( GameWindow *window, UnsignedInt msg, if( removed == FALSE ) { - list->selections[i] = (Int)mData1; + list->selections[i] = (Int)(intptr_t)mData1; list->selections[i+1] = -1; } } @@ -1607,7 +1607,7 @@ WindowMsgHandledType GadgetListBoxSystem( GameWindow *window, UnsignedInt msg, case GLM_SET_SELECTION: { const Int *selectList = (const Int *)mData1; - Int selectCount = (Int)mData2; + Int selectCount = (Int)(intptr_t)mData2; DEBUG_ASSERTCRASH( list->multiSelect || selectCount == 1, ("Bad selection size")); if( selectList[0] < 0 || list->listLength <= selectList[0] ) @@ -1701,7 +1701,7 @@ WindowMsgHandledType GadgetListBoxSystem( GameWindow *window, UnsignedInt msg, case GLM_SCROLL_BUFFER: { - if( list->endPos < (Int)mData1 ) + if( list->endPos < (Int)(intptr_t)mData1 ) break; // @@ -1715,7 +1715,7 @@ WindowMsgHandledType GadgetListBoxSystem( GameWindow *window, UnsignedInt msg, // ListEntryCell *cells = nullptr; Int i = 0; - for (; i < (Int)mData1; i++) + for (; i < (Int)(intptr_t)mData1; i++) { cells = list->listData[i].cell; @@ -1749,7 +1749,7 @@ WindowMsgHandledType GadgetListBoxSystem( GameWindow *window, UnsignedInt msg, // // remove the display or links to images after the shift // - for(i = 0; i < (Int)mData1; i ++) + for(i = 0; i < (Int)(intptr_t)mData1; i ++) { list->listData[list->endPos + i].cell = nullptr; } @@ -1761,8 +1761,8 @@ WindowMsgHandledType GadgetListBoxSystem( GameWindow *window, UnsignedInt msg, while( list->selections[i] >= 0 ) { - if( (Int)mData1 >= list->selections[i] ) - list->selections[i] -= (Int)mData1; + if( (Int)(intptr_t)mData1 >= list->selections[i] ) + list->selections[i] -= (Int)(intptr_t)mData1; else { removeSelection( list, i ); @@ -1792,7 +1792,10 @@ WindowMsgHandledType GadgetListBoxSystem( GameWindow *window, UnsignedInt msg, { if( list->multiSelect ) - *(Int*)mData2 = (Int)list->selections; + // TheSuperHackers @build bobtista 29/04/2026 selections is a + // pointer; route the cast through intptr_t so it truncates to + // Int cleanly on 64-bit. + *(Int*)mData2 = (Int)(intptr_t)list->selections; else *(Int*)mData2 = list->selectPos; @@ -1822,8 +1825,8 @@ WindowMsgHandledType GadgetListBoxSystem( GameWindow *window, UnsignedInt msg, // ------------------------------------------------------------------------ case GGM_RESIZED: { - Int width = (Int)mData1; - Int height = (Int)mData2; + Int width = (Int)(intptr_t)mData1; + Int height = (Int)(intptr_t)mData2; ICoord2D downSize = {0, 0}; ICoord2D upSize = {0, 0}; ICoord2D sliderSize = {0, 0}; diff --git a/Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetProgressBar.cpp b/Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetProgressBar.cpp index ffc8d60d7cc..d413e9bdb27 100644 --- a/Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetProgressBar.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetProgressBar.cpp @@ -85,7 +85,7 @@ WindowMsgHandledType GadgetProgressBarSystem( GameWindow *window, UnsignedInt ms // ------------------------------------------------------------------------ case GPM_SET_PROGRESS: { - Int newPos = (Int)mData1; + Int newPos = (Int)(intptr_t)mData1; if (newPos < 0 || newPos > 100) break; diff --git a/Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetVerticalSlider.cpp b/Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetVerticalSlider.cpp index 4f16471ff5e..af518acd9b7 100644 --- a/Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetVerticalSlider.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetVerticalSlider.cpp @@ -375,7 +375,7 @@ WindowMsgHandledType GadgetVerticalSliderSystem( GameWindow *window, UnsignedInt // ------------------------------------------------------------------------ case GSM_SET_SLIDER: { - Int newPos = (Int)mData1; + Int newPos = (Int)(intptr_t)mData1; GameWindow *child = window->winGetChild(); // TheSuperHackers @fix No longer reject out of bounds positions to prevent @@ -402,8 +402,8 @@ WindowMsgHandledType GadgetVerticalSliderSystem( GameWindow *window, UnsignedInt window->winGetSize( &size.x, &size.y ); - s->minVal = (Int)mData1; - s->maxVal = (Int)mData2; + s->minVal = (Int)(intptr_t)mData1; + s->maxVal = (Int)(intptr_t)mData2; s->numTicks = (Real)( size.y-GADGET_SIZE)/(Real)(s->maxVal - s->minVal); s->position = s->minVal; @@ -448,8 +448,8 @@ WindowMsgHandledType GadgetVerticalSliderSystem( GameWindow *window, UnsignedInt // ------------------------------------------------------------------------ case GGM_RESIZED: { - Int width = (Int)mData1; -// Int height = (Int)mData2; + Int width = (Int)(intptr_t)mData1; +// Int height = (Int)(intptr_t)mData2; GameWindow *thumb = window->winGetChild(); if( thumb ) diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index 5218ff802d9..b843c1b9b8c 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -656,7 +656,6 @@ set(GAMEENGINE_SRC Source/Common/System/SaveGame/GameState.cpp Source/Common/System/SaveGame/GameStateMap.cpp # Source/Common/System/Snapshot.cpp - Source/Common/System/StackDump.cpp # Source/Common/System/StreamingArchiveFile.cpp # Source/Common/System/SubsystemInterface.cpp Source/Common/System/Trig.cpp @@ -1147,6 +1146,12 @@ else() ) endif() +if(WIN32) + list(APPEND GAMEENGINE_SRC + Source/Common/System/StackDump.cpp + ) +endif() + add_library(z_gameengine STATIC) @@ -1160,6 +1165,14 @@ target_include_directories(z_gameengine PRIVATE Include/Precompiled ) +# TheSuperHackers @build bobtista 29/04/2026 Force win32 compat shims to the +# front of the include path so our stub wins over the dx8 SDK one. +if(NOT WIN32) + target_include_directories(z_gameengine BEFORE PRIVATE + ${CMAKE_SOURCE_DIR}/Core/Libraries/Source/WWVegas/compat/win32_shims + ) +endif() + target_link_libraries(z_gameengine PRIVATE corei_gameengine_private zi_always diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/KeyDefs.h b/GeneralsMD/Code/GameEngine/Include/GameClient/KeyDefs.h index 9f1978d20e0..698d937f7da 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/KeyDefs.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/KeyDefs.h @@ -59,6 +59,11 @@ # define DIRECTINPUT_VERSION 0x800 #endif +// TheSuperHackers @build bobtista 29/04/2026 dinput.h is real Win SDK on +// Windows; on non-Win it resolves to the compat stub at +// Core/Libraries/Source/WWVegas/compat/win32_shims/dinput.h, which defines the +// DIK_* keycode constants as a portable enum (values don't have to match the +// Win SDK since the engine only uses them via the KEY_* enum mapping). #include #include diff --git a/GeneralsMD/Code/GameEngine/Include/Precompiled/PreRTS.h b/GeneralsMD/Code/GameEngine/Include/Precompiled/PreRTS.h index c6f3131d78b..a9ed695f111 100644 --- a/GeneralsMD/Code/GameEngine/Include/Precompiled/PreRTS.h +++ b/GeneralsMD/Code/GameEngine/Include/Precompiled/PreRTS.h @@ -44,19 +44,30 @@ class STLSpecialAlloc; #if defined(__GNUC__) && defined(_WIN32) #include #endif +// TheSuperHackers @build bobtista 29/04/2026 Win-only system headers gated on +// _WIN32 so the precompiled header parses on macOS/Linux. Cross-platform +// substitutes are provided by Core/Libraries/Source/WWVegas/compat/win32_shims. +#ifdef _WIN32 #include +#endif #include #include #include #include +#ifdef _WIN32 #include +#endif #include #include +#ifdef _WIN32 #include +#endif #include #include +#ifdef _WIN32 #include +#endif #if defined(_MSC_VER) && _MSC_VER < 1300 #include #endif @@ -64,30 +75,40 @@ class STLSpecialAlloc; #include #include #include +#ifdef _WIN32 #include +#endif #include +#ifdef _WIN32 #include #include #include #include +#endif #include #include #include #include #include #include +#ifdef _WIN32 #include +#endif #include +#ifdef _WIN32 #include #include #include #include +#endif #ifndef DIRECTINPUT_VERSION # define DIRECTINPUT_VERSION 0x800 #endif +#ifdef _WIN32 #include +#endif //------------------------------------------------------------------------------------ STL Includes // srj sez: no, include STLTypesdefs below, instead, thanks diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp index 32b93d3dba7..34d62c96816 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp @@ -169,7 +169,10 @@ void initSubsystem( //------------------------------------------------------------------------------------------------- extern HINSTANCE ApplicationHInstance; ///< our application instance +// TheSuperHackers @build bobtista 29/04/2026 CComModule is ATL; only on Win. +#ifdef _WIN32 extern CComModule _Module; +#endif //------------------------------------------------------------------------------------------------- static void updateTGAtoDDS(); @@ -251,7 +254,9 @@ GameEngine::GameEngine() m_quitting = FALSE; m_isActive = FALSE; +#ifdef _WIN32 _Module.Init(nullptr, ApplicationHInstance, nullptr); +#endif } //------------------------------------------------------------------------------------------------- @@ -299,7 +304,9 @@ GameEngine::~GameEngine() Drawable::killStaticImages(); +#ifdef _WIN32 _Module.Term(); +#endif #ifdef PERF_TIMERS PerfGather::termPerfDump(); diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ThingTemplate.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ThingTemplate.cpp index c113766a012..c2a49971c59 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ThingTemplate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ThingTemplate.cpp @@ -527,7 +527,9 @@ void ThingTemplate::parseModuleName(INI* ini, void *instance, void* store, const { ThingTemplate* self = (ThingTemplate*)instance; ModuleInfo* mi = (ModuleInfo*)store; - ModuleType type = (ModuleType)(UnsignedInt)userData; + // TheSuperHackers @build bobtista 29/04/2026 Cast through uintptr_t so the + // userData pointer narrows cleanly into the ModuleType enum on 64-bit. + ModuleType type = (ModuleType)(UnsignedInt)(uintptr_t)userData; const char* token = ini->getNextToken(); AsciiString tokenStr = token; @@ -634,7 +636,7 @@ void ThingTemplate::parseModuleName(INI* ini, void *instance, void* store, const //------------------------------------------------------------------------------------------------- void ThingTemplate::parseIntList(INI* ini, void *instance, void* store, const void* userData) { - Int numberEntries = (Int)userData; + Int numberEntries = (Int)(intptr_t)userData; Int *intList = (Int*)store; for( Int intIndex = 0; intIndex < numberEntries; intIndex ++ ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/InGamePopupMessage.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/InGamePopupMessage.cpp index 8782368c136..9af3e0e989e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/InGamePopupMessage.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/InGamePopupMessage.cpp @@ -78,7 +78,9 @@ static GameWindow *staticTextMessage = nullptr; static GameWindow *buttonOk = nullptr; -static Bool pause = FALSE; +// TheSuperHackers @build bobtista 29/04/2026 Renamed from `pause` to avoid +// colliding with the POSIX `pause()` function declared in . +static Bool s_pause = FALSE; //----------------------------------------------------------------------------- // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////// //----------------------------------------------------------------------------- @@ -124,7 +126,7 @@ void InGamePopupMessageInit( WindowLayout *layout, void *userData ) staticTextMessage->winSetSize( pMData->width - 4, height + 7); buttonOk->winSetPosition(pMData->width - widthOk - 2, height + 7 + 2 + 2); staticTextMessage->winSetEnabledTextColors(pMData->textColor, 0); - pause = pMData->pause; + s_pause = pMData->pause; if(pMData->pause) TheWindowManager->winSetModal( parent ); @@ -228,7 +230,7 @@ WindowMsgHandledType InGamePopupMessageSystem( GameWindow *window, UnsignedInt m if( controlID == buttonOkID ) { - if(!pause) + if(!s_pause) TheMessageStream->appendMessage( GameMessage::MSG_CLEAR_INGAME_POPUP_MESSAGE ); else TheInGameUI->clearPopupMessageData(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp index 4072ce98ad5..29eec6d3311 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp @@ -394,7 +394,7 @@ static void handleColorSelection(int index) GameWindow *combo = comboBoxColor[index]; Int color, selIndex; GadgetComboBoxGetSelectedPos(combo, &selIndex); - color = (Int)GadgetComboBoxGetItemData(combo, selIndex); + color = (Int)(intptr_t)GadgetComboBoxGetItemData(combo, selIndex); LANGameInfo *myGame = TheLAN->GetMyGame(); @@ -452,7 +452,7 @@ static void handlePlayerTemplateSelection(int index) GameWindow *combo = comboBoxPlayerTemplate[index]; Int playerTemplate, selIndex; GadgetComboBoxGetSelectedPos(combo, &selIndex); - playerTemplate = (Int)GadgetComboBoxGetItemData(combo, selIndex); + playerTemplate = (Int)(intptr_t)GadgetComboBoxGetItemData(combo, selIndex); LANGameInfo *myGame = TheLAN->GetMyGame(); if (myGame) @@ -564,7 +564,7 @@ static void handleTeamSelection(int index) GameWindow *combo = comboBoxTeam[index]; Int team, selIndex; GadgetComboBoxGetSelectedPos(combo, &selIndex); - team = (Int)GadgetComboBoxGetItemData(combo, selIndex); + team = (Int)(intptr_t)GadgetComboBoxGetItemData(combo, selIndex); LANGameInfo *myGame = TheLAN->GetMyGame(); if (myGame) @@ -609,7 +609,7 @@ static void handleStartingCashSelection() GadgetComboBoxGetSelectedPos(comboBoxStartingCash, &selIndex); Money startingCash; - startingCash.deposit( (UnsignedInt)GadgetComboBoxGetItemData( comboBoxStartingCash, selIndex ), FALSE, FALSE ); + startingCash.deposit( (UnsignedInt)(uintptr_t)GadgetComboBoxGetItemData( comboBoxStartingCash, selIndex ), FALSE, FALSE ); myGame->setStartingCash( startingCash ); myGame->resetAccepted(); @@ -967,7 +967,7 @@ void updateGameOptions() Int index = 0; for ( ; index < itemCount; index++ ) { - Int value = (Int)GadgetComboBoxGetItemData(comboBoxStartingCash, index); + Int value = (Int)(intptr_t)GadgetComboBoxGetItemData(comboBoxStartingCash, index); if ( value == theGame->getStartingCash().countMoney() ) { GadgetComboBoxSetSelectedPos(comboBoxStartingCash, index, TRUE); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanLobbyMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanLobbyMenu.cpp index 2f395f73891..ff94531bb70 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanLobbyMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanLobbyMenu.cpp @@ -344,7 +344,7 @@ static void playerTooltip(GameWindow *window, return; } - UnsignedInt playerIP = (UnsignedInt)GadgetListBoxGetItemData( window, row, col ); + UnsignedInt playerIP = (UnsignedInt)(uintptr_t)GadgetListBoxGetItemData( window, row, col ); LANPlayer *player = TheLAN->LookupPlayer(playerIP); if (!player) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp index 5fa7a3f8a6c..5b5abf2c4fd 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp @@ -485,7 +485,7 @@ static void saveOptions() GadgetComboBoxGetSelectedPos(comboBoxLANIP, &index); if (index>=0 && TheGlobalData) { - ip = (UnsignedInt)GadgetComboBoxGetItemData(comboBoxLANIP, index); + ip = (UnsignedInt)(uintptr_t)GadgetComboBoxGetItemData(comboBoxLANIP, index); TheWritableGlobalData->m_defaultIP = ip; pref->setLANIPAddress(ip); } @@ -497,7 +497,7 @@ static void saveOptions() GadgetComboBoxGetSelectedPos(comboBoxOnlineIP, &index); if (index>=0) { - ip = (UnsignedInt)GadgetComboBoxGetItemData(comboBoxOnlineIP, index); + ip = (UnsignedInt)(uintptr_t)GadgetComboBoxGetItemData(comboBoxOnlineIP, index); pref->setOnlineIPAddress(ip); } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupHostGame.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupHostGame.cpp index 0d450c726c5..503bc9e2711 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupHostGame.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupHostGame.cpp @@ -508,7 +508,7 @@ WindowMsgHandledType PopupHostGameSystem( GameWindow *window, UnsignedInt msg, W { if (pos >= 0) { - Int ladderID = (Int)GadgetComboBoxGetItemData(control, pos); + Int ladderID = (Int)(intptr_t)GadgetComboBoxGetItemData(control, pos); if (ladderID < 0) { // "Choose a ladder" selected - open overlay @@ -597,7 +597,7 @@ void createGame() req.stagingRoomCreation.ladPort = 0; if (ladderSelectPos >= 0) { - ladderID = (Int)GadgetComboBoxGetItemData(comboBoxLadderName, ladderSelectPos); + ladderID = (Int)(intptr_t)GadgetComboBoxGetItemData(comboBoxLadderName, ladderSelectPos); if (ladderID != 0) { // actual ladder diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupLadderSelect.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupLadderSelect.cpp index 5a24d65f21b..bfb57b95272 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupLadderSelect.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupLadderSelect.cpp @@ -130,7 +130,7 @@ static void populateLadderListBox() GadgetListBoxGetSelected(listboxLadderSelect, &selIndex); if (selIndex < 0) return; - selID = (Int)GadgetListBoxGetItemData(listboxLadderSelect, selIndex); + selID = (Int)(intptr_t)GadgetListBoxGetItemData(listboxLadderSelect, selIndex); if (!selID) return; updateLadderDetails(selID, staticTextLadderName, listboxLadderDetails); @@ -373,7 +373,7 @@ WindowMsgHandledType PopupLadderSelectSystem( GameWindow *window, UnsignedInt ms if (selectPos < 0) break; - ladderIndex = (Int)GadgetListBoxGetItemData( listboxLadderSelect, selectPos, 0 ); + ladderIndex = (Int)(intptr_t)GadgetListBoxGetItemData( listboxLadderSelect, selectPos, 0 ); const LadderInfo *li = TheLadderList->findLadderByIndex( ladderIndex ); if (li && li->cryptedPassword.isNotEmpty()) { @@ -439,7 +439,7 @@ WindowMsgHandledType PopupLadderSelectSystem( GameWindow *window, UnsignedInt ms if (selIndex < 0) break; - selID = (Int)GadgetListBoxGetItemData(listboxLadderSelect, selIndex); + selID = (Int)(intptr_t)GadgetListBoxGetItemData(listboxLadderSelect, selIndex); if (!selID) break; @@ -452,7 +452,7 @@ WindowMsgHandledType PopupLadderSelectSystem( GameWindow *window, UnsignedInt ms { GameWindow *control = (GameWindow *)mData1; Int controlID = control->winGetWindowId(); - Int selectPos = (Int)mData2; + Int selectPos = (Int)(intptr_t)mData2; GadgetListBoxSetSelected(control, &selectPos); if( controlID == listboxLadderSelectID ) @@ -631,7 +631,7 @@ WindowMsgHandledType RCGameDetailsMenuSystem( GameWindow *window, UnsignedInt ms { GameWindow *control = (GameWindow *)mData1; Int controlID = control->winGetWindowId(); - Int selectedID = (Int)window->winGetUserData(); + Int selectedID = (Int)(intptr_t)window->winGetUserData(); if(!selectedID) break; closeRightClickMenu(window); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupPlayerInfo.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupPlayerInfo.cpp index fa7356cb132..108e133e13c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupPlayerInfo.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupPlayerInfo.cpp @@ -308,8 +308,8 @@ void BattleHonorTooltip(GameWindow *window, return; } - Int battleHonor = (Int)GadgetListBoxGetItemData( window, row, col ); - Int extraValue = (Int)GadgetListBoxGetItemData( window, row - 1, col ); + Int battleHonor = (Int)(intptr_t)GadgetListBoxGetItemData( window, row, col ); + Int extraValue = (Int)(intptr_t)GadgetListBoxGetItemData( window, row - 1, col ); if (battleHonor == 0) { //DEBUG_CRASH(("No Battle Honor in listbox row %d, col %d!", row, col)); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp index 38c88601e42..9f7d2a05d6c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp @@ -613,7 +613,7 @@ WindowMsgHandledType ScoreScreenSystem( GameWindow *window, UnsignedInt msg, if( controlID == TheNameKeyGenerator->nameToKey(name)) { Bool notBuddy = TRUE; - Int playerID = (Int)GadgetButtonGetData(TheWindowManager->winGetWindowFromId(nullptr,controlID)); + Int playerID = (Int)(intptr_t)GadgetButtonGetData(TheWindowManager->winGetWindowFromId(nullptr,controlID)); // request to add a buddy BuddyInfoMap *buddies = TheGameSpyInfo->getBuddyMap(); BuddyInfoMap::iterator bIt; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/SkirmishGameOptionsMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/SkirmishGameOptionsMenu.cpp index 580098b6af9..24a981d642d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/SkirmishGameOptionsMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/SkirmishGameOptionsMenu.cpp @@ -854,7 +854,7 @@ static void handlePlayerSelection(int index) Int playerType, selIndex; GadgetComboBoxGetSelectedPos(combo, &selIndex); UnicodeString title = GadgetComboBoxGetText(combo); - playerType = (Int)GadgetComboBoxGetItemData(combo, selIndex); + playerType = (Int)(intptr_t)GadgetComboBoxGetItemData(combo, selIndex); GameInfo *myGame = TheSkirmishGameInfo; if (myGame) @@ -873,7 +873,7 @@ static void handleColorSelection(int index) GameWindow *combo = comboBoxColor[index]; Int color, selIndex; GadgetComboBoxGetSelectedPos(combo, &selIndex); - color = (Int)GadgetComboBoxGetItemData(combo, selIndex); + color = (Int)(intptr_t)GadgetComboBoxGetItemData(combo, selIndex); GameInfo *myGame = TheSkirmishGameInfo; @@ -913,7 +913,7 @@ static void handlePlayerTemplateSelection(int index) GameWindow *combo = comboBoxPlayerTemplate[index]; Int playerTemplate, selIndex; GadgetComboBoxGetSelectedPos(combo, &selIndex); - playerTemplate = (Int)GadgetComboBoxGetItemData(combo, selIndex); + playerTemplate = (Int)(intptr_t)GadgetComboBoxGetItemData(combo, selIndex); GameInfo *myGame = TheSkirmishGameInfo; if (myGame) @@ -966,7 +966,7 @@ static void handleTeamSelection(int index) GameWindow *combo = comboBoxTeam[index]; Int team, selIndex; GadgetComboBoxGetSelectedPos(combo, &selIndex); - team = (Int)GadgetComboBoxGetItemData(combo, selIndex); + team = (Int)(intptr_t)GadgetComboBoxGetItemData(combo, selIndex); GameInfo *myGame = TheSkirmishGameInfo; if (myGame) @@ -992,7 +992,7 @@ static void handleStartingCashSelection() GadgetComboBoxGetSelectedPos(comboBoxStartingCash, &selIndex); Money startingCash; - startingCash.deposit( (UnsignedInt)GadgetComboBoxGetItemData( comboBoxStartingCash, selIndex ), FALSE, FALSE ); + startingCash.deposit( (UnsignedInt)(uintptr_t)GadgetComboBoxGetItemData( comboBoxStartingCash, selIndex ), FALSE, FALSE ); myGame->setStartingCash( startingCash ); } } @@ -1238,7 +1238,7 @@ void updateSkirmishGameOptions() Int index = 0; for ( ; index < itemCount; index++ ) { - Int value = (Int)GadgetComboBoxGetItemData(comboBoxStartingCash, index); + Int value = (Int)(intptr_t)GadgetComboBoxGetItemData(comboBoxStartingCash, index); if ( value == TheSkirmishGameInfo->getStartingCash().countMoney() ) { GadgetComboBoxSetSelectedPos(comboBoxStartingCash, index, TRUE); @@ -1581,7 +1581,7 @@ WindowMsgHandledType SkirmishGameOptionsMenuSystem( GameWindow *window, Unsigned case GSM_SLIDER_TRACK: { GameWindow *control = (GameWindow *)mData1; - Int sliderPos = (Int)mData2; + Int sliderPos = (Int)(intptr_t)mData2; Int controlID = control->winGetWindowId(); if(controlID == sliderGameSpeedID) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/SkirmishMapSelectMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/SkirmishMapSelectMenu.cpp index c58852bd572..e010311306b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/SkirmishMapSelectMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/SkirmishMapSelectMenu.cpp @@ -99,7 +99,7 @@ static void mapListTooltipFunc(GameWindow *window, return; } - Int imageItemData = (Int)GadgetListBoxGetItemData(window, row, 1); + Int imageItemData = (Int)(intptr_t)GadgetListBoxGetItemData(window, row, 1); UnicodeString tooltip; switch (imageItemData) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLBuddyOverlay.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLBuddyOverlay.cpp index 1e029a562d9..1ed23dd948a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLBuddyOverlay.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLBuddyOverlay.cpp @@ -215,8 +215,8 @@ WindowMsgHandledType BuddyControlSystem( GameWindow *window, UnsignedInt msg, if(rc->pos < 0) break; - GPProfile profileID = (GPProfile)GadgetListBoxGetItemData(control, rc->pos, 0); - RCItemType itemType = (RCItemType)(Int)GadgetListBoxGetItemData(control, rc->pos, 1); + GPProfile profileID = (GPProfile)(intptr_t)GadgetListBoxGetItemData(control, rc->pos, 0); + RCItemType itemType = (RCItemType)(Int)(intptr_t)GadgetListBoxGetItemData(control, rc->pos, 1); UnicodeString nick = GadgetListBoxGetText(control, rc->pos); GadgetListBoxSetSelected(control, rc->pos); @@ -267,7 +267,7 @@ WindowMsgHandledType BuddyControlSystem( GameWindow *window, UnsignedInt msg, GadgetListBoxGetSelected(buddyControls.listboxBuddies, &selected); if (selected >= 0) { - GPProfile selectedProfile = (GPProfile)GadgetListBoxGetItemData(buddyControls.listboxBuddies, selected); + GPProfile selectedProfile = (GPProfile)(intptr_t)GadgetListBoxGetItemData(buddyControls.listboxBuddies, selected); BuddyInfoMap *m = TheGameSpyInfo->getBuddyMap(); BuddyInfoMap::iterator recipIt = m->find(selectedProfile); if (recipIt == m->end()) @@ -393,7 +393,7 @@ void updateBuddyInfo() GadgetListBoxGetSelected(buddyControls.listboxBuddies, &selected); if (selected >= 0) - selectedProfile = (GPProfile)GadgetListBoxGetItemData(buddyControls.listboxBuddies, selected); + selectedProfile = (GPProfile)(intptr_t)GadgetListBoxGetItemData(buddyControls.listboxBuddies, selected); selected = -1; GadgetListBoxReset(buddyControls.listboxBuddies); @@ -884,7 +884,7 @@ WindowMsgHandledType WOLBuddyOverlaySystem( GameWindow *window, UnsignedInt msg, break; Bool isBuddy = false, isRequest = false; - GPProfile profileID = (GPProfile)GadgetListBoxGetItemData(control, rc->pos); + GPProfile profileID = (GPProfile)(intptr_t)GadgetListBoxGetItemData(control, rc->pos); UnicodeString nick = GadgetListBoxGetText(control, rc->pos); BuddyInfoMap *buddies = TheGameSpyInfo->getBuddyMap(); BuddyInfoMap::iterator bIt; @@ -1000,7 +1000,7 @@ WindowMsgHandledType WOLBuddyOverlaySystem( GameWindow *window, UnsignedInt msg, // get text of buddy name buddyName = GadgetListBoxGetText( listboxWindow, rowSelected,0 ); - GPProfile buddyID = (GPProfile)GadgetListBoxGetItemData( listboxWindow, rowSelected, 0 ); + GPProfile buddyID = (GPProfile)(intptr_t)GadgetListBoxGetItemData( listboxWindow, rowSelected, 0 ); Int index = -1; gpGetBuddyIndex(TheGPConnection, buddyID, &index); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLGameSetupMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLGameSetupMenu.cpp index f360ba9423b..3b933ad2ffb 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLGameSetupMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLGameSetupMenu.cpp @@ -525,7 +525,7 @@ static void handleColorSelection(int index) GameWindow *combo = comboBoxColor[index]; Int color, selIndex; GadgetComboBoxGetSelectedPos(combo, &selIndex); - color = (Int)GadgetComboBoxGetItemData(combo, selIndex); + color = (Int)(intptr_t)GadgetComboBoxGetItemData(combo, selIndex); GameInfo *myGame = TheGameSpyInfo->getCurrentStagingRoom(); @@ -588,7 +588,7 @@ static void handlePlayerTemplateSelection(int index) GameWindow *combo = comboBoxPlayerTemplate[index]; Int playerTemplate, selIndex; GadgetComboBoxGetSelectedPos(combo, &selIndex); - playerTemplate = (Int)GadgetComboBoxGetItemData(combo, selIndex); + playerTemplate = (Int)(intptr_t)GadgetComboBoxGetItemData(combo, selIndex); GameInfo *myGame = TheGameSpyInfo->getCurrentStagingRoom(); if (myGame) @@ -713,7 +713,7 @@ static void handleTeamSelection(int index) GameWindow *combo = comboBoxTeam[index]; Int team, selIndex; GadgetComboBoxGetSelectedPos(combo, &selIndex); - team = (Int)GadgetComboBoxGetItemData(combo, selIndex); + team = (Int)(intptr_t)GadgetComboBoxGetItemData(combo, selIndex); GameInfo *myGame = TheGameSpyInfo->getCurrentStagingRoom(); if (myGame) @@ -759,7 +759,7 @@ static void handleStartingCashSelection() GadgetComboBoxGetSelectedPos(comboBoxStartingCash, &selIndex); Money startingCash; - startingCash.deposit( (UnsignedInt)GadgetComboBoxGetItemData( comboBoxStartingCash, selIndex ), FALSE, FALSE ); + startingCash.deposit( (UnsignedInt)(uintptr_t)GadgetComboBoxGetItemData( comboBoxStartingCash, selIndex ), FALSE, FALSE ); myGame->setStartingCash( startingCash ); myGame->resetAccepted(); @@ -1013,7 +1013,7 @@ void WOLDisplayGameOptions() Int index = 0; for ( ; index < itemCount; index++ ) { - Int value = (Int)GadgetComboBoxGetItemData(comboBoxStartingCash, index); + Int value = (Int)(intptr_t)GadgetComboBoxGetItemData(comboBoxStartingCash, index); if ( value == theGame->getStartingCash().countMoney() ) { // Note: must check if combobox is already correct to avoid infinite recursion diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp index 6216e89a253..8d6c1bb3c6f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp @@ -1483,7 +1483,7 @@ WindowMsgHandledType WOLLobbyMenuSystem( GameWindow *window, UnsignedInt msg, PeerRequest req; req.peerRequestType = PeerRequest::PEERREQUEST_GETEXTENDEDSTAGINGROOMINFO; - req.stagingRoom.id = (Int)GadgetListBoxGetItemData(control, rowSelected, 0); + req.stagingRoom.id = (Int)(intptr_t)GadgetListBoxGetItemData(control, rowSelected, 0); if (lastID != req.stagingRoom.id || now > lastFrame + 60) { @@ -1561,7 +1561,7 @@ WindowMsgHandledType WOLLobbyMenuSystem( GameWindow *window, UnsignedInt msg, GadgetListBoxGetSelected(GetGameListBox(), &selected); if (selected >= 0) { - Int selectedID = (Int)GadgetListBoxGetItemData(GetGameListBox(), selected); + Int selectedID = (Int)(intptr_t)GadgetListBoxGetItemData(GetGameListBox(), selected); if (selectedID > 0) { StagingRoomMap *srm = TheGameSpyInfo->getStagingRoomList(); @@ -1672,7 +1672,7 @@ WindowMsgHandledType WOLLobbyMenuSystem( GameWindow *window, UnsignedInt msg, if (rowSelected >= 0) { Int groupID; - groupID = (Int)GadgetComboBoxGetItemData(comboLobbyGroupRooms, rowSelected); + groupID = (Int)(intptr_t)GadgetComboBoxGetItemData(comboLobbyGroupRooms, rowSelected); DEBUG_LOG(("ItemData was %d, current Group Room is %d", groupID, TheGameSpyInfo->getCurrentGroupRoom())); if (groupID && groupID != TheGameSpyInfo->getCurrentGroupRoom()) { @@ -1797,7 +1797,7 @@ WindowMsgHandledType WOLLobbyMenuSystem( GameWindow *window, UnsignedInt msg, break; } - Int selectedID = (Int)GadgetListBoxGetItemData(control, rc->pos); + Int selectedID = (Int)(intptr_t)GadgetListBoxGetItemData(control, rc->pos); if (selectedID > 0) { StagingRoomMap *srm = TheGameSpyInfo->getStagingRoomList(); @@ -1838,7 +1838,7 @@ WindowMsgHandledType WOLLobbyMenuSystem( GameWindow *window, UnsignedInt msg, // break; // // GameWindow *control = (GameWindow *)mData1; -// Int val = (Int)mData2; +// Int val = (Int)(intptr_t)mData2; // Int controlID = control->winGetWindowId(); // if (controlID == sliderChatAdjustID) // { diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp index 977f72e5a87..0bc0a70642a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp @@ -233,7 +233,7 @@ void UpdateStartButton() Int index; Int selected; GadgetComboBoxGetSelectedPos( comboBoxLadder, &selected ); - index = (Int)GadgetComboBoxGetItemData( comboBoxLadder, selected ); + index = (Int)(intptr_t)GadgetComboBoxGetItemData( comboBoxLadder, selected ); const LadderInfo *li = TheLadderList->findLadderByIndex( index ); if (li) { @@ -482,7 +482,7 @@ static const LadderInfo * getLadderInfo() Int index; Int selected; GadgetComboBoxGetSelectedPos( comboBoxLadder, &selected ); - index = (Int)GadgetComboBoxGetItemData( comboBoxLadder, selected ); + index = (Int)(intptr_t)GadgetComboBoxGetItemData( comboBoxLadder, selected ); const LadderInfo *li = TheLadderList->findLadderByIndex( index ); return li; } @@ -561,7 +561,7 @@ static void populateQuickMatchMapSelectListbox( QuickMatchPreferences& pref ) Int index; Int selected; GadgetComboBoxGetSelectedPos( comboBoxLadder, &selected ); - index = (Int)GadgetComboBoxGetItemData( comboBoxLadder, selected ); + index = (Int)(intptr_t)GadgetComboBoxGetItemData( comboBoxLadder, selected ); const LadderInfo *li = TheLadderList->findLadderByIndex( index ); //listboxMapSelect->winEnable( li == nullptr || li->randomMaps == FALSE ); @@ -620,7 +620,7 @@ static void saveQuickMatchOptions() Int index; Int selected; GadgetComboBoxGetSelectedPos( comboBoxLadder, &selected ); - index = (Int)GadgetComboBoxGetItemData( comboBoxLadder, selected ); + index = (Int)(intptr_t)GadgetComboBoxGetItemData( comboBoxLadder, selected ); const LadderInfo *li = TheLadderList->findLadderByIndex( index ); Int numPlayers = 0; @@ -676,7 +676,7 @@ static void saveQuickMatchOptions() Int item; GadgetComboBoxGetSelectedPos(comboBoxSide, &selected); - item = (Int)GadgetComboBoxGetItemData(comboBoxSide, selected); + item = (Int)(intptr_t)GadgetComboBoxGetItemData(comboBoxSide, selected); pref.setSide(max(0, item)); GadgetComboBoxGetSelectedPos(comboBoxColor, &selected); pref.setColor(max(0, selected)); @@ -1557,7 +1557,7 @@ WindowMsgHandledType WOLQuickMatchMenuSystem( GameWindow *window, UnsignedInt ms if (pos >= 0) { QuickMatchPreferences pref; - Int ladderID = (Int)GadgetComboBoxGetItemData(control, pos); + Int ladderID = (Int)(intptr_t)GadgetComboBoxGetItemData(control, pos); if (ladderID == 0) { // no ladder selected - enable buttons @@ -1683,7 +1683,7 @@ WindowMsgHandledType WOLQuickMatchMenuSystem( GameWindow *window, UnsignedInt ms Int ladderIndex, index, selected; GadgetComboBoxGetSelectedPos( comboBoxLadder, &selected ); - ladderIndex = (Int)GadgetComboBoxGetItemData( comboBoxLadder, selected ); + ladderIndex = (Int)(intptr_t)GadgetComboBoxGetItemData( comboBoxLadder, selected ); const LadderInfo *ladderInfo = nullptr; if (ladderIndex < 0) { @@ -1704,7 +1704,7 @@ WindowMsgHandledType WOLQuickMatchMenuSystem( GameWindow *window, UnsignedInt ms index = -1; GadgetComboBoxGetSelectedPos( comboBoxSide, &selected ); if (selected >= 0) - index = (Int)GadgetComboBoxGetItemData( comboBoxSide, selected ); + index = (Int)(intptr_t)GadgetComboBoxGetItemData( comboBoxSide, selected ); req.QM.side = index; if (ladderInfo && ladderInfo->randomFactions) { @@ -1743,7 +1743,7 @@ WindowMsgHandledType WOLQuickMatchMenuSystem( GameWindow *window, UnsignedInt ms { Int numberComboBoxEntries = GadgetComboBoxGetLength(comboBoxSide); Int randomPick = GameClientRandomValue(0, numberComboBoxEntries - 1); - index = (Int)GadgetComboBoxGetItemData( comboBoxSide, randomPick ); + index = (Int)(intptr_t)GadgetComboBoxGetItemData( comboBoxSide, randomPick ); req.QM.side = index; randomTries++; @@ -1753,7 +1753,7 @@ WindowMsgHandledType WOLQuickMatchMenuSystem( GameWindow *window, UnsignedInt ms index = -1; GadgetComboBoxGetSelectedPos( comboBoxColor, &selected ); if (selected >= 0) - index = (Int)GadgetComboBoxGetItemData( comboBoxColor, selected ); + index = (Int)(intptr_t)GadgetComboBoxGetItemData( comboBoxColor, selected ); req.QM.color = index; OptionPreferences natPref; @@ -1850,7 +1850,7 @@ WindowMsgHandledType WOLQuickMatchMenuSystem( GameWindow *window, UnsignedInt ms { GameWindow *control = (GameWindow *)mData1; Int controlID = control->winGetWindowId(); - Int selected = (Int)mData2; + Int selected = (Int)(intptr_t)mData2; if ( controlID == listboxMapSelectID ) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp index f78e882cc0c..825bc0ec7ff 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp @@ -1234,7 +1234,7 @@ Bool outOfWeaponRangePosition( State *thisState, void* userData ) */ static Bool cannotPossiblyAttackObject( State *thisState, void* userData ) { - AbleToAttackType attackType = (AbleToAttackType)(UnsignedInt)userData; + AbleToAttackType attackType = (AbleToAttackType)(UnsignedInt)(uintptr_t)userData; Object *obj = thisState->getMachineOwner(); Object *victim = thisState->getMachineGoalObject(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/SabotageInternetCenterCrateCollide.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/SabotageInternetCenterCrateCollide.cpp index 01a9ec36a99..561cb07f750 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/SabotageInternetCenterCrateCollide.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/SabotageInternetCenterCrateCollide.cpp @@ -118,7 +118,7 @@ Bool SabotageInternetCenterCrateCollide::isValidToExecute( const Object *other ) static void disableHacker( Object *obj, void *userData ) { - UnsignedInt frame = (UnsignedInt)userData; + UnsignedInt frame = (UnsignedInt)(uintptr_t)userData; if( obj ) { obj->setDisabledUntil( DISABLED_HACKED, frame ); @@ -129,7 +129,7 @@ static void disableInternetCenterSpyVision( Object *obj, void *userData ) { if( obj && obj->isKindOf( KINDOF_FS_INTERNET_CENTER ) ) { - UnsignedInt frame = (UnsignedInt)userData; + UnsignedInt frame = (UnsignedInt)(uintptr_t)userData; //Loop through all it's SpyVisionUpdates() and wake them all up so they can be shut down. This is weird because //it's one of the few update modules that is actually properly sleepified. diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp index a5f0a55128c..f1342a9134e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp @@ -5678,7 +5678,7 @@ void hLineAddLooker(Int x1, Int x2, Int y, void *playerIndexVoid) if (y < 0 || y >= ThePartitionManager->m_cellCountY || x1 >= ThePartitionManager->m_cellCountX || x2 < 0) return; - Int playerIndex = (Int)(playerIndexVoid); + Int playerIndex = (Int)(intptr_t)(playerIndexVoid); PartitionCell* cell = &ThePartitionManager->m_cells[y * ThePartitionManager->m_cellCountX + x1]; // yes, this could be invalid. we'll skip the bad ones. for (Int x = x1; x <= x2; ++x, ++cell) @@ -5695,7 +5695,7 @@ void hLineRemoveLooker(Int x1, Int x2, Int y, void *playerIndexVoid) if (y < 0 || y >= ThePartitionManager->m_cellCountY || x1 >= ThePartitionManager->m_cellCountX || x2 < 0) return; - Int playerIndex = (Int)(playerIndexVoid); + Int playerIndex = (Int)(intptr_t)(playerIndexVoid); PartitionCell* cell = &ThePartitionManager->m_cells[y * ThePartitionManager->m_cellCountX + x1]; // yes, this could be invalid. we'll skip the bad ones. for (Int x = x1; x <= x2; ++x, ++cell) @@ -5712,7 +5712,7 @@ void hLineAddShrouder(Int x1, Int x2, Int y, void *playerIndexVoid) if (y < 0 || y >= ThePartitionManager->m_cellCountY || x1 >= ThePartitionManager->m_cellCountX || x2 < 0) return; - Int playerIndex = (Int)(playerIndexVoid); + Int playerIndex = (Int)(intptr_t)(playerIndexVoid); PartitionCell* cell = &ThePartitionManager->m_cells[y * ThePartitionManager->m_cellCountX + x1]; // yes, this could be invalid. we'll skip the bad ones. for (Int x = x1; x <= x2; ++x, ++cell) @@ -5729,7 +5729,7 @@ void hLineRemoveShrouder(Int x1, Int x2, Int y, void *playerIndexVoid) if (y < 0 || y >= ThePartitionManager->m_cellCountY || x1 >= ThePartitionManager->m_cellCountX || x2 < 0) return; - Int playerIndex = (Int)(playerIndexVoid); + Int playerIndex = (Int)(intptr_t)(playerIndexVoid); PartitionCell* cell = &ThePartitionManager->m_cells[y * ThePartitionManager->m_cellCountX + x1]; // yes, this could be invalid. we'll skip the bad ones. for (Int x = x1; x <= x2; ++x, ++cell) diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp index b62a6937e4a..20dfd438640 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp @@ -473,7 +473,7 @@ void UpdateSlotList( GameInfo *myGame, GameWindow *comboPlayer[], max = GadgetComboBoxGetLength(comboColor[i]); for (idx=0; idxgetColor()) { GadgetComboBoxSetSelectedPos(comboColor[i], idx, TRUE); @@ -486,7 +486,7 @@ void UpdateSlotList( GameInfo *myGame, GameWindow *comboPlayer[], max = GadgetComboBoxGetLength(comboTeam[i]); for (idx=0; idxgetTeamNumber()) { GadgetComboBoxSetSelectedPos(comboTeam[i], idx, TRUE); @@ -499,7 +499,7 @@ void UpdateSlotList( GameInfo *myGame, GameWindow *comboPlayer[], max = GadgetComboBoxGetLength(comboPlayerTemplate[i]); for (idx=0; idxgetPlayerTemplate()) { GadgetComboBoxSetSelectedPos(comboPlayerTemplate[i], idx, TRUE); From e7cc9fa4bed31472c56189471d577ea4ee162492 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Thu, 30 Apr 2026 09:26:12 +1000 Subject: [PATCH 006/523] fix(generalsmd-device): non-Win W3DDevice + WW3D2 fork adjustments (cherry picked from commit 86d75c73d0170f9ee9c1f853f2db5b66d5c2ce4c) --- .../GameClient/GUI/Gadget/W3DProgressBar.cpp | 6 ++-- .../Code/GameEngineDevice/CMakeLists.txt | 28 +++++++++++++------ .../W3DDevice/GameClient/W3DAssetManager.cpp | 26 +++++++++-------- .../W3DDevice/GameClient/W3DWebBrowser.cpp | 6 ++++ .../Source/WWVegas/WW3D2/assetmgr.cpp | 4 +-- .../Source/WWVegas/WW3D2/ddsfile.cpp | 9 +++++- 6 files changed, 54 insertions(+), 25 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DProgressBar.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DProgressBar.cpp index 39c989c585d..91fe6831544 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DProgressBar.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DProgressBar.cpp @@ -76,7 +76,7 @@ void W3DGadgetProgressBarDraw( GameWindow *window, WinInstanceData *instData ) { ICoord2D origin, size, start, end; Color backColor, backBorder, barColor, barBorder; - Int progress = (Int)window->winGetUserData(); + Int progress = static_cast(reinterpret_cast(window->winGetUserData())); // get window size and position window->winGetScreenPosition( &origin.x, &origin.y ); @@ -186,7 +186,7 @@ void W3DGadgetProgressBarImageDrawA( GameWindow *window, WinInstanceData *instDa { ICoord2D origin, size; const Image *barCenter, *barRight, *left, *right, *center; - Int progress = (Int)window->winGetUserData(); + Int progress = static_cast(reinterpret_cast(window->winGetUserData())); Int xOffset, yOffset; Int i; // get window size and position @@ -229,7 +229,7 @@ void W3DGadgetProgressBarImageDraw( GameWindow *window, WinInstanceData *instDat ICoord2D origin, size, start, end; const Image *backLeft, *backRight, *backCenter, *barRight, *barCenter;//*backSmallCenter,*barLeft,, *barSmallCenter; - Int progress = (Int)window->winGetUserData(); + Int progress = static_cast(reinterpret_cast(window->winGetUserData())); Int xOffset, yOffset; Int i; diff --git a/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt b/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt index 28efc82a418..8cf18eaa0a3 100644 --- a/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt @@ -219,17 +219,29 @@ target_include_directories(z_gameenginedevice PUBLIC Include ) -if(WIN32) - target_precompile_headers(z_gameenginedevice PRIVATE - [["Utility/CppMacros.h"]] # Must be first, to be removed when abandoning VC6 - [["Common/STLTypedefs.h"]] - [["Common/SubsystemInterface.h"]] - [["WWLib/INI.h"]] - [["WWLib/WWCommon.h"]] - +# TheSuperHackers @build bobtista 29/04/2026 Force the win32 compat shims to +# the front of the include path so our stub wins over the dx8 SDK +# version that ships with d3d8lib (the SDK header references CLSID_*/IID_* +# symbols that don't exist on non-Win and unconditionally fail to parse). +if(NOT WIN32) + target_include_directories(z_gameenginedevice BEFORE PRIVATE + ${CMAKE_SOURCE_DIR}/Core/Libraries/Source/WWVegas/compat/win32_shims ) endif() +# TheSuperHackers @build bobtista 29/04/2026 Make the precompiled header +# unconditional. resolves to the win32 compat shim on macOS/Linux +# (compat/win32_shims is on core_wwvegas's INTERFACE include path), so the same +# PCH list works everywhere. +target_precompile_headers(z_gameenginedevice PRIVATE + [["Utility/CppMacros.h"]] # Must be first, to be removed when abandoning VC6 + + [["Common/STLTypedefs.h"]] + [["Common/SubsystemInterface.h"]] + [["WWLib/INI.h"]] + [["WWLib/WWCommon.h"]] +) + target_link_libraries(z_gameenginedevice PRIVATE corei_gameenginedevice_private zi_always diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DAssetManager.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DAssetManager.cpp index 899dfcb3f74..b5f031de005 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DAssetManager.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DAssetManager.cpp @@ -44,6 +44,8 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include +#include +#include #include "W3DDevice/GameClient/W3DAssetManager.h" #include "WW3D2/proto.h" #include "WW3D2/rendobj.h" @@ -152,7 +154,7 @@ W3DAssetManager::~W3DAssetManager() } #ifdef DUMP_PERF_STATS -__int64 Total_Get_Texture_Time=0; +std::int64_t Total_Get_Texture_Time = 0; #endif TextureClass * W3DAssetManager::Get_Texture @@ -190,7 +192,7 @@ TextureClass *W3DAssetManager::Get_Texture( ) { #ifdef DUMP_PERF_STATS - __int64 startTime64,endTime64; + std::int64_t startTime64, endTime64; GetPrecisionTimer(&startTime64); #endif @@ -699,7 +701,7 @@ TextureClass * W3DAssetManager::Recolor_Texture_One_Time(TextureClass *texture, } #ifdef DUMP_PERF_STATS -__int64 Total_Create_Render_Obj_Time=0; +std::int64_t Total_Create_Render_Obj_Time = 0; #endif //--------------------------------------------------------------------- /** Generals specific code to generate customized render objects for each team color @@ -714,7 +716,7 @@ RenderObjClass * W3DAssetManager::Create_Render_Obj( ) { #ifdef DUMP_PERF_STATS - __int64 startTime64,endTime64; + std::int64_t startTime64, endTime64; GetPrecisionTimer(&startTime64); #endif @@ -771,7 +773,8 @@ RenderObjClass * W3DAssetManager::Create_Render_Obj( const char *mesh_name = strchr (name, '.'); if (mesh_name != nullptr) { - lstrcpyn(filename, name, ((int)mesh_name) - ((int)name) + 1); + const std::ptrdiff_t mesh_name_length = mesh_name - name; + lstrcpyn(filename, name, static_cast(mesh_name_length) + 1); lstrcat(filename, ".w3d"); } else { snprintf( filename, ARRAY_SIZE(filename), "%s.w3d", name); @@ -960,7 +963,7 @@ void W3DAssetManager::Recolor_Vertex_Material(VertexMaterialClass *vmat, const i } #ifdef DUMP_PERF_STATS -__int64 Total_Load_3D_Assets=0; +std::int64_t Total_Load_3D_Assets = 0; static Int Load_3D_Asset_Recursions=0; #endif //--------------------------------------------------------------------- @@ -969,7 +972,7 @@ bool W3DAssetManager::Load_3D_Assets( const char * filename ) #ifdef DUMP_PERF_STATS Load_3D_Asset_Recursions++; - __int64 startTime64,endTime64; + std::int64_t startTime64, endTime64; GetPrecisionTimer(&startTime64); #endif @@ -1020,7 +1023,7 @@ bool W3DAssetManager::Load_3D_Assets( const char * filename ) } #ifdef DUMP_PERF_STATS -__int64 Total_Get_HAnim_Time=0; +std::int64_t Total_Get_HAnim_Time = 0; static Int HAnim_Recursions=0; #endif //--------------------------------------------------------------------- @@ -1029,7 +1032,7 @@ HAnimClass * W3DAssetManager::Get_HAnim(const char * name) #ifdef DUMP_PERF_STATS HAnim_Recursions++; - __int64 startTime64,endTime64; + std::int64_t startTime64, endTime64; GetPrecisionTimer(&startTime64); #endif WWPROFILE( "WW3DAssetManager::Get_HAnim" ); @@ -1371,13 +1374,14 @@ RenderObjClass * W3DAssetManager::Create_Render_Obj(const char * name,float scal char filename [MAX_PATH]; char *mesh_name = ::strchr (name, '.'); if (mesh_name != nullptr) { - ::lstrcpyn (filename, name, ((int)mesh_name) - ((int)name) + 1); + const std::ptrdiff_t mesh_name_length = mesh_name - name; + ::lstrcpyn(filename, name, static_cast(mesh_name_length) + 1); if (isGranny) ::lstrcat (filename, ".gr2"); else ::lstrcat (filename, ".w3d"); } else { - sprintf( filename, "%s.w3d", name); + snprintf(filename, ARRAY_SIZE(filename), "%s.w3d", name); } // If we can't find it, try the parent directory diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DWebBrowser.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DWebBrowser.cpp index 1b7f2915ad1..f780398b0f2 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DWebBrowser.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DWebBrowser.cpp @@ -25,6 +25,10 @@ ////// W3DWebBrowser.cpp /////////////// // July 2002 Bryan Cleveland +// TheSuperHackers @build bobtista 29/04/2026 W3DWebBrowser bridges the Win +// IE embed via IDispatch / CComQIPtr. Skip on non-Win. +#ifdef _WIN32 + #include "W3DDevice/GameClient/W3DWebBrowser.h" #include "WW3D2/texture.h" #include "WW3D2/textureloader.h" @@ -76,3 +80,5 @@ void W3DWebBrowser::closeBrowserWindow(GameWindow *win) { DX8WebBrowser::DestroyBrowser(win->winGetInstanceData()->m_decoratedNameString.str()); } + +#endif // _WIN32 (W3DWebBrowser Win impl) diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp index 9ce8fb48662..d4c9c657c3e 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp @@ -799,7 +799,8 @@ RenderObjClass * WW3DAssetManager::Create_Render_Obj(const char * name) char filename [MAX_PATH]; const char *mesh_name = ::strchr (name, '.'); if (mesh_name != nullptr) { - ::lstrcpyn (filename, name, ((int)mesh_name) - ((int)name) + 1); + const int base_len = static_cast(mesh_name - name + 1); + ::lstrcpyn (filename, name, base_len); ::lstrcat (filename, ".w3d"); } else { snprintf( filename, ARRAY_SIZE(filename), "%s.w3d", name); @@ -1725,4 +1726,3 @@ const char * HTreeIterator::Current_Item_Name() return WW3DAssetManager::Get_Instance()->HTreeManager.Get_Tree(Index)->Get_Name(); } - diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ddsfile.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ddsfile.cpp index 0fa44e57580..bf7c3193fd1 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ddsfile.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ddsfile.cpp @@ -25,7 +25,14 @@ #include "WW3D2/dx8wrapper.h" #include "WW3D2/bitmaphandler.h" #include "WW3D2/colorspace.h" -#include + +#ifndef DDSCAPS2_CUBEMAP +#define DDSCAPS2_CUBEMAP 0x00000200L +#endif + +#ifndef DDSCAPS2_VOLUME +#define DDSCAPS2_VOLUME 0x00200000L +#endif // ---------------------------------------------------------------------------- From 3d290d516b61d7a5cb3dd592bf7be7da9ed6f51f Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Thu, 30 Apr 2026 15:44:13 +1000 Subject: [PATCH 007/523] fix(macos-cmdline): wire GetCommandLineA shim to argv so -headless and friends parse (cherry picked from commit 1c3f6e1efe6042372b7effcc08a763095e6ebd2d) --- .../WWVegas/compat/win32_shims/windows.h | 9 +++- GeneralsMD/Code/Main/SDL3Main.cpp | 48 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/Core/Libraries/Source/WWVegas/compat/win32_shims/windows.h b/Core/Libraries/Source/WWVegas/compat/win32_shims/windows.h index 4ac8c141f40..d69e2e51e3f 100644 --- a/Core/Libraries/Source/WWVegas/compat/win32_shims/windows.h +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/windows.h @@ -277,7 +277,14 @@ typedef IDispatch *LPDISPATCH; #endif inline unsigned int GetDoubleClickTime() { return 500; } -inline const char *GetCommandLineA() { return ""; } +// TheSuperHackers @bugfix bobtista 30/04/2026 The non-Win entry point +// (SDL3Main.cpp) populates g_compatCommandLine with the argv joined by +// spaces so the engine's parseCommandLine helpers (which expect the +// Win32 GetCommandLineA single-string format) see the actual flags. +// Without this every -headless / -replay / -xres flag silently became +// a no-op on macOS, leaving audio + rendering in their default modes. +extern const char *g_compatCommandLine; +inline const char *GetCommandLineA() { return g_compatCommandLine != nullptr ? g_compatCommandLine : ""; } inline DWORD GetModuleFileName(HMODULE, char *, DWORD size) { (void)size; return 0; } inline DWORD GetModuleFileNameA(HMODULE, char *, DWORD size) { (void)size; return 0; } inline void GetLocalTime(SYSTEMTIME *t) { if (t) { *t = SYSTEMTIME{}; } } diff --git a/GeneralsMD/Code/Main/SDL3Main.cpp b/GeneralsMD/Code/Main/SDL3Main.cpp index 07c1bd0e674..a76d6802d66 100644 --- a/GeneralsMD/Code/Main/SDL3Main.cpp +++ b/GeneralsMD/Code/Main/SDL3Main.cpp @@ -16,6 +16,7 @@ #include #include +#include #include "Common/CommandLine.h" #include "Common/GameEngine.h" @@ -36,6 +37,13 @@ namespace int __argc = 0; char **__argv = NULL; +// TheSuperHackers @bugfix bobtista 30/04/2026 Backing storage for the +// compat shim's GetCommandLineA(). Populated in main() before any +// CommandLine::parse* call so the engine sees the real arg vector +// joined into a single Win32-style command-line string. +const char *g_compatCommandLine = ""; +static std::string s_compatCommandLineStorage; + // TheSuperHackers @build bobtista 29/04/2026 Globals normally provided by the // Win-side WinMain.cpp. const char *g_strFile = "data/Generals.str"; @@ -66,6 +74,46 @@ int main(int argc, char **argv) __argc = argc; __argv = argv; + // TheSuperHackers @bugfix bobtista 30/04/2026 Build a Win32-style + // command-line string from argv so GetCommandLineA() in the compat + // shim returns the real arguments. parseCommandLine tokenises by + // whitespace and respects double quotes, so wrap any arg that + // contains a space and escape embedded quotes. + for (int i = 1; i < argc; ++i) + { + if (i > 1) + { + s_compatCommandLineStorage += ' '; + } + const char *a = argv[i]; + bool needsQuote = false; + for (const char *p = a; *p != '\0'; ++p) + { + if (*p == ' ' || *p == '\t') + { + needsQuote = true; + break; + } + } + if (needsQuote) + { + s_compatCommandLineStorage += '"'; + } + for (const char *p = a; *p != '\0'; ++p) + { + if (*p == '"') + { + s_compatCommandLineStorage += '\\'; + } + s_compatCommandLineStorage += *p; + } + if (needsQuote) + { + s_compatCommandLineStorage += '"'; + } + } + g_compatCommandLine = s_compatCommandLineStorage.c_str(); + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) { SDL_Log("SDL_Init failed: %s", SDL_GetError()); From fded0fabbb9bd457aca5aaca75311b71fe1aad7a Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sun, 3 May 2026 21:02:11 +1000 Subject: [PATCH 008/523] Fix non-Windows quit menu transition crash (cherry picked from commit 48d34034b032b6135c5887fee38b9230de77355e) --- .../GameClient/GUI/GameWindowTransitions.cpp | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp b/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp index 09f22640cf4..8c1ec6e989b 100644 --- a/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp @@ -169,12 +169,27 @@ Bool TransitionWindow::init() m_winID = TheNameKeyGenerator->nameToKey(m_winName); m_win = TheWindowManager->winGetWindowFromId(nullptr, m_winID); m_currentFrameDelay = m_frameDelay; -// DEBUG_ASSERTCRASH( m_win, ("TransitionWindow::init Failed to find window %s", m_winName.str())); -// if( !m_win ) -// return FALSE; +#if !defined(_WIN32) + // Some non-Windows data/layout combinations do not create every window + // referenced by transition INI groups. The original Windows path assumes + // those references are valid; keep that behavior there, but avoid leaving + // a null transition object queued for the next frame on SDL/macOS. + if( !m_win ) + { + delete m_transition; + m_transition = nullptr; + return FALSE; + } +#endif delete m_transition; m_transition = getTransitionForStyle( m_style ); +#if !defined(_WIN32) + if( !m_transition ) + { + return FALSE; + } +#endif m_transition->init(m_win); // TheSuperHackers @fix Mauller 15/05/2025 Link TransitionWindow to the GameWindow so the GameWindow can unlink itself when it is destroyed @@ -186,6 +201,12 @@ Bool TransitionWindow::init() void TransitionWindow::update( Int frame ) { +#if !defined(_WIN32) + // TransitionGroup::init() return values are ignored by the legacy caller, + // so a failed TransitionWindow::init() must be harmless during update. + if( !m_transition ) + return; +#endif if(frame < m_currentFrameDelay || frame > (m_currentFrameDelay + m_transition->getFrameLength())) return; @@ -509,6 +530,14 @@ void GameWindowTransitionsHandler::setGroup(AsciiString groupName, Bool immediat void GameWindowTransitionsHandler::reverse( AsciiString groupName ) { TransitionGroup *g = findGroup(groupName); +#if !defined(_WIN32) + // Missing transition groups can happen with incomplete retail data on + // non-Windows builds; on Windows preserve the original assert/crash path. + if( !g ) + { + return; + } +#endif if( m_currentGroup == g ) { m_currentGroup->reverse(); @@ -606,4 +635,3 @@ void GameWindowTransitionsHandler::parseWindow( INI* ini, void *instance, void * ini->initFromINI(transWin, myFieldParse); ((TransitionGroup*)instance)->addWindow(transWin); } - From c481f2515d71eeaca87f36261fc750a000cc4c1b Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sun, 3 May 2026 22:25:59 +1000 Subject: [PATCH 009/523] fix(nonwindows): keep utf16 text reads platform scoped (cherry picked from commit d6829acdf06c9ebc4a1138f633441c53e71a78de) --- .../GameEngine/Source/GameClient/GameText.cpp | 15 +++++++- .../Source/GameClient/LanguageFilter.cpp | 38 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/Core/GameEngine/Source/GameClient/GameText.cpp b/Core/GameEngine/Source/GameClient/GameText.cpp index ca4afba1d0e..b5a98a5823e 100644 --- a/Core/GameEngine/Source/GameClient/GameText.cpp +++ b/Core/GameEngine/Source/GameClient/GameText.cpp @@ -940,11 +940,20 @@ Bool GameTextManager::parseCSF( const Char *filename ) goto quit; } - file->read ( &len, sizeof ( Int ) ); + file->read ( &len, sizeof ( Int ) ); if ( len ) { +#ifdef _WIN32 file->read ( m_tbuffer, len*sizeof(WideChar) ); +#else + for (Int i = 0; i < len; ++i) + { + UnsignedShort ch = 0; + file->read(&ch, sizeof(ch)); + m_tbuffer[i] = static_cast(ch); + } +#endif } if ( num == 0 ) @@ -959,7 +968,11 @@ Bool GameTextManager::parseCSF( const Char *filename ) while ( *ptr ) { +#ifdef _WIN32 *ptr = ~*ptr; +#else + *ptr = static_cast(static_cast(~static_cast(*ptr))); +#endif ptr++; } } diff --git a/Core/GameEngine/Source/GameClient/LanguageFilter.cpp b/Core/GameEngine/Source/GameClient/LanguageFilter.cpp index 93c2513baae..3b381c3d2d4 100644 --- a/Core/GameEngine/Source/GameClient/LanguageFilter.cpp +++ b/Core/GameEngine/Source/GameClient/LanguageFilter.cpp @@ -152,6 +152,7 @@ void LanguageFilter::unHaxor(UnicodeString &word) { // returning true means that there are more words in the file. Bool LanguageFilter::readWord(File *file1, WideChar *buf) { +#ifdef _WIN32 Int index = 0; Bool retval = TRUE; Int val = 0; @@ -182,6 +183,43 @@ Bool LanguageFilter::readWord(File *file1, WideChar *buf) { buf[index] = c; } return retval; +#else + constexpr Int MAX_BAD_WORD_CHARS = 127; + Int index = 0; + Bool retval = TRUE; + Int val = 0; + + UnsignedShort raw = 0; + WideChar c; + + val = file1->read(&raw, sizeof(raw)); + if ((val == -1) || (val == 0)) { + buf[index] = 0; + return FALSE; + } + c = static_cast(raw); + buf[index] = c; + + while (buf[index] != L' ') { + ++index; + val = file1->read(&raw, sizeof(raw)); + if ((val == -1) || (val == 0)) { + c = WEOF; + } else { + c = static_cast(raw); + } + + if ((c == WEOF) || (c == L' ') || (index >= MAX_BAD_WORD_CHARS)) { + buf[index] = 0; + if (c == WEOF) { + retval = FALSE; + } + break; + } + buf[index] = c; + } + return retval; +#endif } LanguageFilter * createLanguageFilter() From 5c81c41bf4668f4f9ded6e3af9f57b05f2773057 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sun, 3 May 2026 23:36:54 +1000 Subject: [PATCH 010/523] build(gamespy): define _MACOSX for the gsinterface target on macOS (cherry picked from commit ec94df514f572ef420a8b06362c171c1492b1ae1) --- cmake/gamespy.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmake/gamespy.cmake b/cmake/gamespy.cmake index 4c498628781..b52d80bb0f7 100644 --- a/cmake/gamespy.cmake +++ b/cmake/gamespy.cmake @@ -8,3 +8,7 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(gamespy) + +if(APPLE AND TARGET gsinterface) + target_compile_definitions(gsinterface INTERFACE _MACOSX) +endif() From 0e5e2c02ec7db95cb72bb9bb89687b5212ea2709 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Mon, 4 May 2026 23:18:27 +1000 Subject: [PATCH 011/523] fix(nonwindows): restore save-loaded object lifecycle (cherry picked from commit 835ebe67ea82a43e347236d64e3ad55715819dbe) --- Core/GameEngine/Include/GameLogic/Damage.h | 14 ++++-- .../Object/Behavior/SlowDeathBehavior.cpp | 28 ++++++++++++ .../GameLogic/Object/PartitionManager.cpp | 44 +++++++++++++++++++ .../Object/Update/DeletionUpdate.cpp | 1 + .../GameLogic/Object/Update/LaserUpdate.cpp | 2 - .../Object/Update/LifetimeUpdate.cpp | 23 ++++++++-- 6 files changed, 104 insertions(+), 8 deletions(-) diff --git a/Core/GameEngine/Include/GameLogic/Damage.h b/Core/GameEngine/Include/GameLogic/Damage.h index 0334c32f27f..59b40f4a0f5 100644 --- a/Core/GameEngine/Include/GameLogic/Damage.h +++ b/Core/GameEngine/Include/GameLogic/Damage.h @@ -236,19 +236,27 @@ typedef UnsignedInt DeathTypeFlags; const DeathTypeFlags DEATH_TYPE_FLAGS_ALL = 0xffffffff; const DeathTypeFlags DEATH_TYPE_FLAGS_NONE = 0x00000000; +inline UnsignedInt getDeathTypeFlagBit(DeathType dt) +{ + // The original dx8 build encoded DEATH_NORMAL through a 32-bit shift count + // wraparound: 1 << (0 - 1) became bit 31 on x86. Make that behavior defined + // so Clang/non-x86 builds match the reference game logic. + return dt == DEATH_NORMAL ? 31 : static_cast(dt - 1); +} + inline Bool getDeathTypeFlag(DeathTypeFlags flags, DeathType dt) { - return (flags & (1UL << (dt - 1))) != 0; + return (flags & (1UL << getDeathTypeFlagBit(dt))) != 0; } inline DeathTypeFlags setDeathTypeFlag(DeathTypeFlags flags, DeathType dt) { - return (flags | (1UL << (dt - 1))); + return (flags | (1UL << getDeathTypeFlagBit(dt))); } inline DeathTypeFlags clearDeathTypeFlag(DeathTypeFlags flags, DeathType dt) { - return (flags & ~(1UL << (dt - 1))); + return (flags & ~(1UL << getDeathTypeFlagBit(dt))); } //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/SlowDeathBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/SlowDeathBehavior.cpp index f2942322f9a..86fa03dd94c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/SlowDeathBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/SlowDeathBehavior.cpp @@ -576,4 +576,32 @@ void SlowDeathBehavior::loadPostProcess() // extend base class UpdateModule::loadPostProcess(); + Object *obj = getObject(); + if (!isSlowDeathActivated()) + return; + + UnsignedInt now = TheGameLogic->getFrame(); + if (now == 0) + now = 1; + + UnsignedInt wakeFrame = m_destructionFrame; + const SlowDeathBehaviorModuleData* d = getSlowDeathBehaviorModuleData(); + + if ((m_flags & (1<m_sinkRate > 0.0f && m_sinkFrame < wakeFrame) + wakeFrame = m_sinkFrame; + + if ((m_flags & (1< +#include +#include +#include + #ifdef RTS_DEBUG //#include "GameClient/InGameUI.h" // for debugHints #endif @@ -1305,6 +1310,7 @@ void PartitionCell::addLooker(Int playerIndex) void PartitionCell::removeLooker(Int playerIndex) { CellShroudStatus oldShroud = getShroudStatusForPlayer( playerIndex ); + const Int oldCurrentShroud = m_shroudLevel[playerIndex].m_currentShroud; // the increasing Algorithm: a -1 goes up to min(1,activeLevel), otherwise it just gets incremented if( m_shroudLevel[playerIndex].m_currentShroud == -1 ) m_shroudLevel[playerIndex].m_currentShroud = min( m_shroudLevel[playerIndex].m_activeShroudLevel, (Short)1 ); @@ -4134,6 +4140,12 @@ void PartitionManager::undoShroudReveal(Real centerX, Real centerY, Real radius, //----------------------------------------------------------------------------- void PartitionManager::queueUndoShroudReveal(Real centerX, Real centerY, Real radius, PlayerMaskType playerMask) { + if ((TheGameLogic != nullptr && TheGameLogic->isLoadingSave()) + || (TheGameState != nullptr && TheGameState->isInLoadGame())) + { + return; + } + UnsignedInt now = TheGameLogic->getFrame(); SightingInfo *newInfo = newInstance(SightingInfo); @@ -4741,6 +4753,18 @@ void PartitionManager::xfer( Xfer *xfer ) // in a queued unlook, so we actually have stuff in here at the start. I am fairly certain that setTeam should wait // until loadPostProcess, but I ain't gonna change it now. // DEBUG_ASSERTCRASH(m_pendingUndoShroudReveals.empty(), ("At load, we appear to not be in a reset state.") ); + // + // The serialized partition state and serialized pending queue are + // authoritative. Any entries already present here were queued by + // object/team restore side effects before the partition manager had + // loaded its saved state. Keeping those load artifacts causes a + // delayed mass unlook a few seconds after loading a save. + while (!m_pendingUndoShroudReveals.empty()) + { + SightingInfo *loadArtifact = m_pendingUndoShroudReveals.front(); + deleteInstance(loadArtifact); + m_pendingUndoShroudReveals.pop(); + } // I have to split this up though, since on Load I need to make new instances. for( Int infoIndex = 0; infoIndex < queueSize; infoIndex++ ) @@ -4749,6 +4773,26 @@ void PartitionManager::xfer( Xfer *xfer ) xfer->xferSnapshot(newInfo); m_pendingUndoShroudReveals.push(newInfo); } + + // setTeam/on-load maintenance may queue new delayed unlooks before + // the saved queue is read. The processing code assumes the queue is + // ordered by deadline, so restore that invariant after combining the + // pre-load and saved entries. + std::vector pending; + while (!m_pendingUndoShroudReveals.empty()) + { + pending.push_back(m_pendingUndoShroudReveals.front()); + m_pendingUndoShroudReveals.pop(); + } + std::stable_sort(pending.begin(), pending.end(), + [](const SightingInfo *a, const SightingInfo *b) + { + return a->m_data < b->m_data; + }); + for (SightingInfo *info : pending) + { + m_pendingUndoShroudReveals.push(info); + } } else { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DeletionUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DeletionUpdate.cpp index 9815cfbc4a5..2fdd884c969 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DeletionUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DeletionUpdate.cpp @@ -30,6 +30,7 @@ // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" #include "Common/RandomValue.h" +#include "Common/ThingTemplate.h" #include "Common/Xfer.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Object.h" diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LaserUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LaserUpdate.cpp index 4a46ae7f527..0d6712ff855 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LaserUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LaserUpdate.cpp @@ -44,7 +44,6 @@ #include "GameLogic/Module/LaserUpdate.h" #include "WWMath/vector3.h" - //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- LaserUpdateModuleData::LaserUpdateModuleData() @@ -99,7 +98,6 @@ LaserUpdate::LaserUpdate( Thing *thing, const ModuleData* moduleData ) : ClientU //------------------------------------------------------------------------------------------------- LaserUpdate::~LaserUpdate() { - if( m_particleSystemID ) TheParticleSystemManager->destroyParticleSystemByID( m_particleSystemID ); if( m_targetParticleSystemID ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LifetimeUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LifetimeUpdate.cpp index 3aca8d125ed..84af8daf886 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LifetimeUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LifetimeUpdate.cpp @@ -35,7 +35,7 @@ #include "GameLogic/GameLogic.h" #include "GameLogic/Module/LifetimeUpdate.h" #include "GameLogic/Object.h" - +#include "Common/ThingTemplate.h" //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- @@ -84,8 +84,16 @@ UnsignedInt LifetimeUpdate::calcSleepDelay(UnsignedInt minFrames, UnsignedInt ma //------------------------------------------------------------------------------------------------- UpdateSleepTime LifetimeUpdate::update() { + Object *obj = getObject(); + + if (obj->isEffectivelyDead()) + { + TheGameLogic->destroyObject(obj); + return UPDATE_SLEEP_FOREVER; + } + // Kill (NOT destroy) if time is up - getObject()->kill(); + obj->kill(); return UPDATE_SLEEP_FOREVER; } @@ -126,8 +134,17 @@ void LifetimeUpdate::xfer( Xfer *xfer ) // ------------------------------------------------------------------------------------------------ void LifetimeUpdate::loadPostProcess() { - // extend base class UpdateModule::loadPostProcess(); + Object *obj = getObject(); + UnsignedInt now = TheGameLogic->getFrame(); + if (now == 0) + now = 1; + + UnsignedInt wakeFrame = m_dieFrame; + if (obj->isEffectivelyDead() || wakeFrame < now) + wakeFrame = now; + + friend_setNextCallFrame(wakeFrame); } From 9f018fe8da3dd7be8351e6bc18ece8ecaea1cc84 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 5 May 2026 19:46:42 +1000 Subject: [PATCH 012/523] fix(lod): only apply the really-low-MHz LOD downgrade when cpu frequency is known (cherry picked from commit 073945fa1eaf38fb114c5eb75f646cc2758bb3f9) --- GeneralsMD/Code/GameEngine/Include/Common/GameLOD.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GameLOD.h b/GeneralsMD/Code/GameEngine/Include/Common/GameLOD.h index 7e28de412cf..f603ba3921b 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GameLOD.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GameLOD.h @@ -187,7 +187,7 @@ class GameLODManager BenchProfile *newBenchProfile(); Bool didMemPass(); void setReallyLowMHz(Int mhz) { m_reallyLowMHz = mhz; } - Bool isReallyLowMHz() const { return m_cpuFreq < m_reallyLowMHz; } + Bool isReallyLowMHz() const { return m_cpuFreq > 0 && m_cpuFreq < m_reallyLowMHz; } StaticGameLODInfo m_staticGameLODInfo[STATIC_GAME_LOD_COUNT]; DynamicGameLODInfo m_dynamicGameLODInfo[DYNAMIC_GAME_LOD_COUNT]; From 2dc8ce52d103b9e41f0f4f6261672ef812bbcb9b Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 5 May 2026 19:46:51 +1000 Subject: [PATCH 013/523] Keep shellmap available for quickstart (cherry picked from commit dd9c96ff9fb440f705f62db761bea4feafbcbd70) --- GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp | 1 - GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp index 772830f0f67..f37ed99f4aa 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp @@ -819,7 +819,6 @@ Int parseWinCursors(char *args[], int num) Int parseQuickStart( char *args[], int num ) { parseNoLogo( args, num ); - parseNoShellMap( args, num ); parseNoWindowAnimation( args, num ); return 1; } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp index 34d62c96816..3bb8d09f78f 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp @@ -748,7 +748,11 @@ void GameEngine::init() MapCache::const_iterator it = TheMapCache->find(lowerName); if (it == TheMapCache->end()) { - TheWritableGlobalData->m_shellMapOn = FALSE; + const Bool shellMapFileExists = TheFileSystem && TheFileSystem->doesFileExist(TheGlobalData->m_shellMapName.str()); + if (!shellMapFileExists) + { + TheWritableGlobalData->m_shellMapOn = FALSE; + } } } From 8d0a5a4e5b019b82906ca52773cee03add744c90 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Fri, 8 May 2026 16:34:00 +1000 Subject: [PATCH 014/523] build(generals): move the core_debug link into the WIN32-only block (cherry picked from commit f5ac1ad217af126852cf1321320f6bbf6bc22a8c) --- GeneralsMD/Code/Main/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GeneralsMD/Code/Main/CMakeLists.txt b/GeneralsMD/Code/Main/CMakeLists.txt index 880f4ea402a..6252f31bfb3 100644 --- a/GeneralsMD/Code/Main/CMakeLists.txt +++ b/GeneralsMD/Code/Main/CMakeLists.txt @@ -12,7 +12,6 @@ else() endif() target_link_libraries(z_generals PRIVATE - core_debug core_profile_legacy z_gameengine z_gameenginedevice @@ -23,6 +22,7 @@ if(WIN32) target_link_libraries(z_generals PRIVATE binkstub comctl32 + core_debug dinput8 dxguid imm32 From 95b7af7bf138bfb6fc9db158a7f9cd5385d49249 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Thu, 28 May 2026 19:51:24 +1000 Subject: [PATCH 015/523] bugfix(macos-compat): start DIK_NUMPAD0 at 1 to avoid KEY_NONE collision --- Core/Libraries/Source/WWVegas/compat/win32_shims/dinput.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/Libraries/Source/WWVegas/compat/win32_shims/dinput.h b/Core/Libraries/Source/WWVegas/compat/win32_shims/dinput.h index 2f9e8dc0abb..1db4c11316b 100644 --- a/Core/Libraries/Source/WWVegas/compat/win32_shims/dinput.h +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/dinput.h @@ -1,9 +1,10 @@ #pragma once +// TheSuperHackers @bugfix bobtista 28/05/2026 Start numbering at 1 so DIK_NUMPAD0 does not collide with KEY_NONE (0) in KeyDefs.h. enum DInputKeys { // keypad keys ---------------------------------------------------------------- - DIK_NUMPAD0, + DIK_NUMPAD0 = 1, DIK_NUMPAD1, DIK_NUMPAD2, DIK_NUMPAD3, From ba277b224df407fc45d4d78921dcf7141681fe58 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Thu, 28 May 2026 19:51:25 +1000 Subject: [PATCH 016/523] style(macos-compat): add braces to single-line if bodies in SlowDeathBehavior::loadPostProcess --- .../GameLogic/Object/Behavior/SlowDeathBehavior.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/SlowDeathBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/SlowDeathBehavior.cpp index 86fa03dd94c..851d9200b98 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/SlowDeathBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/SlowDeathBehavior.cpp @@ -578,11 +578,15 @@ void SlowDeathBehavior::loadPostProcess() Object *obj = getObject(); if (!isSlowDeathActivated()) + { return; + } UnsignedInt now = TheGameLogic->getFrame(); if (now == 0) + { now = 1; + } UnsignedInt wakeFrame = m_destructionFrame; const SlowDeathBehaviorModuleData* d = getSlowDeathBehaviorModuleData(); @@ -594,13 +598,19 @@ void SlowDeathBehavior::loadPostProcess() else { if (d->m_sinkRate > 0.0f && m_sinkFrame < wakeFrame) + { wakeFrame = m_sinkFrame; + } if ((m_flags & (1< Date: Thu, 28 May 2026 19:53:26 +1000 Subject: [PATCH 017/523] style(macos-compat): add braces to single-line if bodies in LaserUpdate and LifetimeUpdate --- .../GameEngine/Source/GameLogic/Object/Update/LaserUpdate.cpp | 4 ++++ .../Source/GameLogic/Object/Update/LifetimeUpdate.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LaserUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LaserUpdate.cpp index 0d6712ff855..ccb5882036e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LaserUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LaserUpdate.cpp @@ -99,9 +99,13 @@ LaserUpdate::LaserUpdate( Thing *thing, const ModuleData* moduleData ) : ClientU LaserUpdate::~LaserUpdate() { if( m_particleSystemID ) + { TheParticleSystemManager->destroyParticleSystemByID( m_particleSystemID ); + } if( m_targetParticleSystemID ) + { TheParticleSystemManager->destroyParticleSystemByID( m_targetParticleSystemID ); + } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LifetimeUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LifetimeUpdate.cpp index 84af8daf886..fa0b3cdbd21 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LifetimeUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LifetimeUpdate.cpp @@ -140,11 +140,15 @@ void LifetimeUpdate::loadPostProcess() Object *obj = getObject(); UnsignedInt now = TheGameLogic->getFrame(); if (now == 0) + { now = 1; + } UnsignedInt wakeFrame = m_dieFrame; if (obj->isEffectivelyDead() || wakeFrame < now) + { wakeFrame = now; + } friend_setNextCallFrame(wakeFrame); } From fa6b59e88b21da4b1475a3ce35f7ffc1fae982d8 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Fri, 5 Jun 2026 15:59:02 +1000 Subject: [PATCH 018/523] bugfix(input): resolve DIK_OEM_102/DIK_RSHIFT keycode collision in dinput shim --- Core/Libraries/Source/WWVegas/compat/win32_shims/dinput.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Core/Libraries/Source/WWVegas/compat/win32_shims/dinput.h b/Core/Libraries/Source/WWVegas/compat/win32_shims/dinput.h index 1db4c11316b..4ce475dbaec 100644 --- a/Core/Libraries/Source/WWVegas/compat/win32_shims/dinput.h +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/dinput.h @@ -109,8 +109,16 @@ enum DInputKeys DIK_DELETE, DIK_NUMPADENTER, DIK_NUMPADSLASH, + + // TheSuperHackers @bugfix bobtista 05/06/2026 KeyDefs.h #ifndef-fallback defines + // DIK_OEM_102 as 0x56, which collides with this enum's auto-numbered DIK_RSHIFT + // (also 0x56). Provide a unique enum value and alias the macro below so KeyDefs.h + // skips its fallback and KEY_102 no longer equals KEY_RSHIFT. + DIK_OEM_102_VALUE, }; +#define DIK_OEM_102 DIK_OEM_102_VALUE + typedef struct DIRECTINPUT8 *LPDIRECTINPUT8; typedef struct DIRECTINPUTDEVICE8 *LPDIRECTINPUTDEVICE8; typedef struct DIDEVICEOBJECTDATA DIDEVICEOBJECTDATA; \ No newline at end of file From cc38a9d6ab5fb91f4357dc5db28f00faa5008593 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Fri, 26 Jun 2026 09:46:39 -0400 Subject: [PATCH 019/523] build(compat): restore Winsock error constants in shim --- .../WWVegas/compat/win32_shims/winsock.h | 192 +++++++++++++++++- 1 file changed, 184 insertions(+), 8 deletions(-) diff --git a/Core/Libraries/Source/WWVegas/compat/win32_shims/winsock.h b/Core/Libraries/Source/WWVegas/compat/win32_shims/winsock.h index 1814f86c1e9..a8f895e6cc6 100644 --- a/Core/Libraries/Source/WWVegas/compat/win32_shims/winsock.h +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/winsock.h @@ -69,12 +69,28 @@ typedef struct WSAData #define WSAEWOULDBLOCK EAGAIN #endif -#ifndef WSAECONNRESET -#define WSAECONNRESET ECONNRESET +#ifndef WSAEINTR +#define WSAEINTR EINTR #endif -#ifndef WSAENOTCONN -#define WSAENOTCONN ENOTCONN +#ifndef WSAEBADF +#define WSAEBADF EBADF +#endif + +#ifndef WSAEACCES +#define WSAEACCES EACCES +#endif + +#ifndef WSAEFAULT +#define WSAEFAULT EFAULT +#endif + +#ifndef WSAEINVAL +#define WSAEINVAL EINVAL +#endif + +#ifndef WSAEMFILE +#define WSAEMFILE EMFILE #endif #ifndef WSAEINPROGRESS @@ -85,18 +101,178 @@ typedef struct WSAData #define WSAEALREADY EALREADY #endif -#ifndef WSAEINVAL -#define WSAEINVAL EINVAL +#ifndef WSAENOTSOCK +#define WSAENOTSOCK ENOTSOCK +#endif + +#ifndef WSAEDESTADDRREQ +#define WSAEDESTADDRREQ EDESTADDRREQ +#endif + +#ifndef WSAEMSGSIZE +#define WSAEMSGSIZE EMSGSIZE +#endif + +#ifndef WSAEPROTOTYPE +#define WSAEPROTOTYPE EPROTOTYPE +#endif + +#ifndef WSAENOPROTOOPT +#define WSAENOPROTOOPT ENOPROTOOPT +#endif + +#ifndef WSAEPROTONOSUPPORT +#define WSAEPROTONOSUPPORT EPROTONOSUPPORT +#endif + +#ifndef WSAESOCKTNOSUPPORT +#define WSAESOCKTNOSUPPORT ESOCKTNOSUPPORT +#endif + +#ifndef WSAEOPNOTSUPP +#define WSAEOPNOTSUPP EOPNOTSUPP +#endif + +#ifndef WSAEPFNOSUPPORT +#define WSAEPFNOSUPPORT EPFNOSUPPORT +#endif + +#ifndef WSAEAFNOSUPPORT +#define WSAEAFNOSUPPORT EAFNOSUPPORT +#endif + +#ifndef WSAEADDRINUSE +#define WSAEADDRINUSE EADDRINUSE +#endif + +#ifndef WSAEADDRNOTAVAIL +#define WSAEADDRNOTAVAIL EADDRNOTAVAIL +#endif + +#ifndef WSAENETDOWN +#define WSAENETDOWN ENETDOWN +#endif + +#ifndef WSAENETUNREACH +#define WSAENETUNREACH ENETUNREACH +#endif + +#ifndef WSAENETRESET +#define WSAENETRESET ENETRESET +#endif + +#ifndef WSAECONNABORTED +#define WSAECONNABORTED ECONNABORTED +#endif + +#ifndef WSAECONNRESET +#define WSAECONNRESET ECONNRESET +#endif + +#ifndef WSAENOBUFS +#define WSAENOBUFS ENOBUFS #endif #ifndef WSAEISCONN #define WSAEISCONN EISCONN #endif +#ifndef WSAENOTCONN +#define WSAENOTCONN ENOTCONN +#endif + +#ifndef WSAESHUTDOWN +#define WSAESHUTDOWN ESHUTDOWN +#endif + +#ifndef WSAETOOMANYREFS +#define WSAETOOMANYREFS ETOOMANYREFS +#endif + #ifndef WSAETIMEDOUT #define WSAETIMEDOUT ETIMEDOUT #endif -#ifndef WSAEINTR -#define WSAEINTR EINTR +#ifndef WSAECONNREFUSED +#define WSAECONNREFUSED ECONNREFUSED +#endif + +#ifndef WSAELOOP +#define WSAELOOP ELOOP +#endif + +#ifndef WSAENAMETOOLONG +#define WSAENAMETOOLONG ENAMETOOLONG +#endif + +#ifndef WSAEHOSTDOWN +#define WSAEHOSTDOWN EHOSTDOWN +#endif + +#ifndef WSAEHOSTUNREACH +#define WSAEHOSTUNREACH EHOSTUNREACH +#endif + +#ifndef WSAENOTEMPTY +#define WSAENOTEMPTY ENOTEMPTY +#endif + +#ifndef WSAEPROCLIM +#ifdef EPROCLIM +#define WSAEPROCLIM EPROCLIM +#else +#define WSAEPROCLIM 10067 +#endif +#endif + +#ifndef WSAEUSERS +#define WSAEUSERS EUSERS +#endif + +#ifndef WSAEDQUOT +#define WSAEDQUOT EDQUOT +#endif + +#ifndef WSAESTALE +#define WSAESTALE ESTALE +#endif + +#ifndef WSAEREMOTE +#define WSAEREMOTE EREMOTE +#endif + +#ifndef WSABASEERR +#define WSABASEERR 10000 +#endif + +#ifndef WSAEDISCON +#define WSAEDISCON 10101 +#endif + +#ifndef WSASYSNOTREADY +#define WSASYSNOTREADY 10091 +#endif + +#ifndef WSAVERNOTSUPPORTED +#define WSAVERNOTSUPPORTED 10092 +#endif + +#ifndef WSANOTINITIALISED +#define WSANOTINITIALISED 10093 +#endif + +#ifndef WSAHOST_NOT_FOUND +#define WSAHOST_NOT_FOUND 11001 +#endif + +#ifndef WSATRY_AGAIN +#define WSATRY_AGAIN 11002 +#endif + +#ifndef WSANO_RECOVERY +#define WSANO_RECOVERY 11003 +#endif + +#ifndef WSANO_DATA +#define WSANO_DATA 11004 #endif From 71354d0c1af22cf05a7b2415098a79df4e6ddbab Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 30 Jun 2026 14:26:54 -0400 Subject: [PATCH 020/523] fix(filesystem): convert narrow paths without throwing on non-mappable system codepage --- .../StdDevice/Common/StdLocalFileSystem.cpp | 89 +++++++++++++++---- 1 file changed, 73 insertions(+), 16 deletions(-) diff --git a/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp b/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp index 89da7828983..e5af9200ecc 100644 --- a/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp +++ b/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp @@ -34,11 +34,68 @@ #include -StdLocalFileSystem::StdLocalFileSystem() : LocalFileSystem() +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif + +// TheSuperHackers @fix bobtista 30/06/2026 Convert between narrow strings and +// std::filesystem::path without throwing. MSVC's narrow path constructor and +// path::string() convert against the system ANSI codepage with the +// *_ERR_INVALID_CHARS flag set, which throws std::system_error +// (ERROR_NO_UNICODE_TRANSLATION) on machines whose codepage cannot map a byte +// (e.g. the "Use Unicode UTF-8" beta or a DBCS system locale). The unhandled +// throw terminates the game at startup. Convert here without that flag so +// unmappable bytes are substituted instead of crashing. +namespace { -} +#ifdef _WIN32 + std::filesystem::path narrowToPath(const std::string &str) + { + if (str.empty()) + { + return std::filesystem::path(); + } + int wlen = MultiByteToWideChar(CP_ACP, 0, str.c_str(), (int)str.size(), nullptr, 0); + std::wstring wide(wlen, L'\0'); + if (wlen > 0) + { + MultiByteToWideChar(CP_ACP, 0, str.c_str(), (int)str.size(), &wide[0], wlen); + } + return std::filesystem::path(std::move(wide)); + } -StdLocalFileSystem::~StdLocalFileSystem() { + std::string pathToString(const std::filesystem::path &path) + { + const std::wstring &wide = path.native(); + if (wide.empty()) + { + return std::string(); + } + int len = WideCharToMultiByte(CP_ACP, 0, wide.c_str(), (int)wide.size(), nullptr, 0, nullptr, nullptr); + std::string narrow(len, '\0'); + if (len > 0) + { + WideCharToMultiByte(CP_ACP, 0, wide.c_str(), (int)wide.size(), &narrow[0], len, nullptr, nullptr); + } + return narrow; + } +#else + inline std::filesystem::path narrowToPath(const std::string &str) + { + return std::filesystem::path(str); + } + + inline std::string pathToString(const std::filesystem::path &path) + { + return path.string(); + } +#endif } //DECLARE_PERF_TIMER(StdLocalFileSystem_openFile) @@ -56,7 +113,7 @@ static std::filesystem::path fixFilenameFromWindowsPath(const Char *filename, In #endif // Convert the filename to a std::filesystem::path and pass that - std::filesystem::path path(std::move(fixedFilename)); + std::filesystem::path path = narrowToPath(fixedFilename); #ifndef _WIN32 // check if the file exists to see if fixup is required @@ -152,7 +209,7 @@ File * StdLocalFileSystem::openFile(const Char *filename, Int access, size_t buf std::error_code ec; if (!std::filesystem::exists(dir, ec) || ec) { if(!std::filesystem::create_directories(dir, ec) || ec) { - DEBUG_LOG(("StdLocalFileSystem::openFile - Error creating directory %s", dir.string().c_str())); + DEBUG_LOG(("StdLocalFileSystem::openFile - Error creating directory %s", pathToString(dir).c_str())); return nullptr; } } @@ -160,7 +217,7 @@ File * StdLocalFileSystem::openFile(const Char *filename, Int access, size_t buf StdLocalFile *file = newInstance( StdLocalFile ); - if (file->open(path.string().c_str(), access, bufferSize) == FALSE) { + if (file->open(pathToString(path).c_str(), access, bufferSize) == FALSE) { deleteInstance(file); file = nullptr; } else { @@ -219,7 +276,7 @@ void StdLocalFileSystem::getFileListInDirectory(const AsciiString& currentDirect AsciiString asciisearch; asciisearch = originalDirectory; asciisearch.concat(currentDirectory); - auto searchExt = std::filesystem::path(searchName.str()).extension(); + auto searchExt = narrowToPath(searchName.str()).extension(); if (asciisearch.isEmpty()) { asciisearch = "."; } @@ -234,7 +291,7 @@ void StdLocalFileSystem::getFileListInDirectory(const AsciiString& currentDirect Bool done = FALSE; std::error_code ec; - auto iter = std::filesystem::directory_iterator(fixedDirectory.c_str(), ec); + auto iter = std::filesystem::directory_iterator(narrowToPath(fixedDirectory), ec); // The default iterator constructor creates an end iterator done = iter == std::filesystem::directory_iterator(); @@ -244,12 +301,12 @@ void StdLocalFileSystem::getFileListInDirectory(const AsciiString& currentDirect } while (!done) { - std::string filenameStr = iter->path().filename().string(); + std::string filenameStr = pathToString(iter->path().filename()); if (!iter->is_directory() && iter->path().extension() == searchExt && (strcmp(filenameStr.c_str(), ".") != 0 && strcmp(filenameStr.c_str(), "..") != 0)) { // if we haven't already, add this filename to the list. // a stl set should only allow one copy of each filename - AsciiString newFilename = iter->path().string().c_str(); + AsciiString newFilename = pathToString(iter->path()).c_str(); if (filenameList.find(newFilename) == filenameList.end()) { filenameList.insert(newFilename); } @@ -260,7 +317,7 @@ void StdLocalFileSystem::getFileListInDirectory(const AsciiString& currentDirect } if (searchSubdirectories) { - auto iter = std::filesystem::directory_iterator(fixedDirectory, ec); + auto iter = std::filesystem::directory_iterator(narrowToPath(fixedDirectory), ec); if (ec) { DEBUG_LOG(("StdLocalFileSystem::getFileListInDirectory - Error opening subdirectory %s", fixedDirectory.c_str())); @@ -271,7 +328,7 @@ void StdLocalFileSystem::getFileListInDirectory(const AsciiString& currentDirect done = iter == std::filesystem::directory_iterator(); while (!done) { - std::string filenameStr = iter->path().filename().string(); + std::string filenameStr = pathToString(iter->path().filename()); if(iter->is_directory() && (strcmp(filenameStr.c_str(), ".") != 0 && strcmp(filenameStr.c_str(), "..") != 0)) { AsciiString tempsearchstr(filenameStr.c_str()); @@ -330,7 +387,7 @@ Bool StdLocalFileSystem::createDirectory(AsciiString directory) if ((!fixedDirectory.empty()) && (fixedDirectory.length() < _MAX_DIR)) { // Convert to host path - std::filesystem::path path(std::move(fixedDirectory)); + std::filesystem::path path = narrowToPath(fixedDirectory); std::error_code ec; result = std::filesystem::create_directory(path, ec); @@ -346,8 +403,8 @@ AsciiString StdLocalFileSystem::normalizePath(const AsciiString& filePath) const std::string nonNormalized(filePath.str()); #ifndef _WIN32 // Replace backslashes with forward slashes on non-Windows platforms - std::replace(unNormalized.begin(), unNormalized.end(), '\\', '/'); + std::replace(nonNormalized.begin(), nonNormalized.end(), '\\', '/'); #endif - std::filesystem::path pathNonNormalized(nonNormalized); - return AsciiString(pathNonNormalized.lexically_normal().string().c_str()); + std::filesystem::path pathNonNormalized = narrowToPath(nonNormalized); + return AsciiString(pathToString(pathNonNormalized.lexically_normal()).c_str()); } From 12aeb8b89cf95cc3ffe1cd291318ce7a15ce9b78 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Fri, 3 Jul 2026 11:52:34 -0400 Subject: [PATCH 021/523] fix(logic): keep LifetimeUpdate::update retail-identical by destroying dead save-loaded objects in loadPostProcess --- .../Object/Update/LifetimeUpdate.cpp | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LifetimeUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LifetimeUpdate.cpp index fa0b3cdbd21..66df4b36ae4 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LifetimeUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LifetimeUpdate.cpp @@ -84,16 +84,8 @@ UnsignedInt LifetimeUpdate::calcSleepDelay(UnsignedInt minFrames, UnsignedInt ma //------------------------------------------------------------------------------------------------- UpdateSleepTime LifetimeUpdate::update() { - Object *obj = getObject(); - - if (obj->isEffectivelyDead()) - { - TheGameLogic->destroyObject(obj); - return UPDATE_SLEEP_FOREVER; - } - // Kill (NOT destroy) if time is up - obj->kill(); + getObject()->kill(); return UPDATE_SLEEP_FOREVER; } @@ -138,6 +130,16 @@ void LifetimeUpdate::loadPostProcess() UpdateModule::loadPostProcess(); Object *obj = getObject(); + + // TheSuperHackers @bugfix bobtista 03/07/2026 Destroy dead expired objects here instead of + // waking them for update(), so the live-simulation update path stays retail-identical + // (kill on an already dead object is a no-op) and this cleanup only applies on save load. + if (obj->isEffectivelyDead()) + { + TheGameLogic->destroyObject(obj); + return; + } + UnsignedInt now = TheGameLogic->getFrame(); if (now == 0) { @@ -145,7 +147,7 @@ void LifetimeUpdate::loadPostProcess() } UnsignedInt wakeFrame = m_dieFrame; - if (obj->isEffectivelyDead() || wakeFrame < now) + if (wakeFrame < now) { wakeFrame = now; } From 81ee8660d1a14f7bfef822b3a72652008feda8bb Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Wed, 8 Jul 2026 23:42:59 +0400 Subject: [PATCH 022/523] fix(platform): detect running client instances on POSIX with a file lock and honor it in the SDL3 entry point --- .../Source/GameClient/ClientInstance.cpp | 81 +++++++++++++++++++ GeneralsMD/Code/Main/SDL3Main.cpp | 15 +++- 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/Core/GameEngine/Source/GameClient/ClientInstance.cpp b/Core/GameEngine/Source/GameClient/ClientInstance.cpp index 7b06108866a..4180c623fb0 100644 --- a/Core/GameEngine/Source/GameClient/ClientInstance.cpp +++ b/Core/GameEngine/Source/GameClient/ClientInstance.cpp @@ -18,6 +18,14 @@ #include "PreRTS.h" #include "GameClient/ClientInstance.h" +#ifndef _WIN32 +#include +#include +#include +#include +#include +#endif + #define GENERALS_GUID "685EAFF2-3216-4265-B047-251C5F4B82F3" namespace rts @@ -25,6 +33,49 @@ namespace rts HANDLE ClientInstance::s_mutexHandle = nullptr; UnsignedInt ClientInstance::s_instanceIndex = 0; +#ifndef _WIN32 +// TheSuperHackers @fix bobtista 08/07/2026 The Win32 CreateMutex shim is a no-op +// on POSIX platforms, which left instance detection permanently uninitialized: +// isInitialized() stayed false and multiple clients all claimed instance 0. Use +// an advisory flock on a per-user temp file instead; the kernel releases it when +// the process exits, matching the auto-release of an abandoned Win32 mutex. +enum InstanceLockResult +{ + INSTANCE_LOCK_ACQUIRED, + INSTANCE_LOCK_BUSY, + INSTANCE_LOCK_ERROR +}; + +static int s_instanceLockFd = -1; + +static InstanceLockResult acquireInstanceLock(const char* name) +{ + const char* tmpDir = std::getenv("TMPDIR"); + std::string path = (tmpDir != nullptr && tmpDir[0] != '\0') ? tmpDir : "/tmp"; + if (path[path.size() - 1] != '/') + { + path.push_back('/'); + } + path.append(name); + path.append(".lock"); + + const int fd = ::open(path.c_str(), O_CREAT | O_RDWR, 0644); + if (fd < 0) + { + return INSTANCE_LOCK_ERROR; + } + if (::flock(fd, LOCK_EX | LOCK_NB) != 0) + { + const InstanceLockResult result = + (errno == EWOULDBLOCK) ? INSTANCE_LOCK_BUSY : INSTANCE_LOCK_ERROR; + ::close(fd); + return result; + } + s_instanceLockFd = fd; + return INSTANCE_LOCK_ACQUIRED; +} +#endif + #if defined(RTS_MULTI_INSTANCE) Bool ClientInstance::s_isMultiInstance = true; #else @@ -52,6 +103,7 @@ bool ClientInstance::initialize() guidStr.push_back('-'); guidStr.append(idStr); } +#ifdef _WIN32 s_mutexHandle = CreateMutex(nullptr, FALSE, guidStr.c_str()); if (GetLastError() == ERROR_ALREADY_EXISTS) { @@ -64,9 +116,23 @@ bool ClientInstance::initialize() ++s_instanceIndex; continue; } +#else + const InstanceLockResult result = acquireInstanceLock(guidStr.c_str()); + if (result == INSTANCE_LOCK_BUSY) + { + // Try again with a new instance. + ++s_instanceIndex; + continue; + } + if (result == INSTANCE_LOCK_ERROR) + { + break; + } +#endif } else { +#ifdef _WIN32 s_mutexHandle = CreateMutex(nullptr, FALSE, getFirstInstanceName()); if (GetLastError() == ERROR_ALREADY_EXISTS) { @@ -77,6 +143,17 @@ bool ClientInstance::initialize() } return false; } +#else + const InstanceLockResult result = acquireInstanceLock(getFirstInstanceName()); + if (result == INSTANCE_LOCK_BUSY) + { + return false; + } + if (result == INSTANCE_LOCK_ERROR) + { + break; + } +#endif } break; } @@ -86,7 +163,11 @@ bool ClientInstance::initialize() bool ClientInstance::isInitialized() { +#ifdef _WIN32 return s_mutexHandle != nullptr; +#else + return s_instanceLockFd >= 0; +#endif } bool ClientInstance::isMultiInstance() diff --git a/GeneralsMD/Code/Main/SDL3Main.cpp b/GeneralsMD/Code/Main/SDL3Main.cpp index a76d6802d66..06294b70f64 100644 --- a/GeneralsMD/Code/Main/SDL3Main.cpp +++ b/GeneralsMD/Code/Main/SDL3Main.cpp @@ -19,9 +19,11 @@ #include #include "Common/CommandLine.h" +#include "Common/Debug.h" #include "Common/GameEngine.h" #include "Common/GameMemory.h" #include "Common/version.h" +#include "GameClient/ClientInstance.h" #include "SDL3GameEngine.h" // Version constants generated by cmake at configure time. #include "BuildVersion.h" @@ -172,7 +174,18 @@ int main(int argc, char **argv) CommandLine::parseCommandLineForStartup(); - Int result = GameMain(); + // TheSuperHackers @fix bobtista 08/07/2026 Mirror WinMain: refuse to start a + // second client instance. Multi-instance builds and command-line flags claim + // a free instance slot inside initialize() instead of failing. + Int result = 0; + if (rts::ClientInstance::initialize()) + { + result = GameMain(); + } + else + { + DEBUG_LOG(("Generals is already running...Bail!")); + } SDL_DestroyWindow(TheSDL3Window); TheSDL3Window = NULL; From f3eef129f7a0800f75672f0ba240e91c74d3bfb5 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Wed, 8 Jul 2026 23:42:59 +0400 Subject: [PATCH 023/523] tweak(filesystem): only log real errors when listing absent optional directories --- .../Source/StdDevice/Common/StdLocalFileSystem.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp b/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp index e5af9200ecc..ab51fbcf2ed 100644 --- a/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp +++ b/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp @@ -296,7 +296,12 @@ void StdLocalFileSystem::getFileListInDirectory(const AsciiString& currentDirect done = iter == std::filesystem::directory_iterator(); if (ec) { - DEBUG_LOG(("StdLocalFileSystem::getFileListInDirectory - Error opening directory %s", fixedDirectory.c_str())); + // TheSuperHackers @tweak bobtista 08/07/2026 The engine probes many optional + // override directories that rarely exist (Data/INI subfolders), so an absent + // directory is routine; only log real I/O errors. + if (ec != std::errc::no_such_file_or_directory && ec != std::errc::not_a_directory) { + DEBUG_LOG(("StdLocalFileSystem::getFileListInDirectory - Error opening directory %s (%s)", fixedDirectory.c_str(), ec.message().c_str())); + } return; } From 4faa70fda6e59d45eb22b5b64cecc2facf4b1d24 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Mon, 20 Jul 2026 20:15:16 +0400 Subject: [PATCH 024/523] bugfix(compat): initialize WSADATA in the non-Windows WSAStartup shim --- .../Source/WWVegas/compat/win32_shims/winsock.h | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Core/Libraries/Source/WWVegas/compat/win32_shims/winsock.h b/Core/Libraries/Source/WWVegas/compat/win32_shims/winsock.h index a8f895e6cc6..54f3f961f29 100644 --- a/Core/Libraries/Source/WWVegas/compat/win32_shims/winsock.h +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/winsock.h @@ -58,7 +58,20 @@ typedef struct WSAData #endif #ifndef WSAStartup -#define WSAStartup(wVer, lpData) (0) +static inline int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData) +{ + if (lpWSAData != 0) + { + lpWSAData->wVersion = wVersionRequested; + lpWSAData->wHighVersion = wVersionRequested; + lpWSAData->szDescription[0] = '\0'; + lpWSAData->szSystemStatus[0] = '\0'; + lpWSAData->iMaxSockets = 0; + lpWSAData->iMaxUdpDg = 0; + lpWSAData->lpVendorInfo = 0; + } + return 0; +} #endif #ifndef WSACleanup From c2c1487f073b779b3cdc2e6b44f02ec9aec54c9d Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Thu, 23 Jul 2026 21:14:26 +0300 Subject: [PATCH 025/523] bugfix(compat): Return NULL from _strdup for NULL input --- Core/Libraries/Source/WWVegas/compat/win32_shims/windows.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Core/Libraries/Source/WWVegas/compat/win32_shims/windows.h b/Core/Libraries/Source/WWVegas/compat/win32_shims/windows.h index d69e2e51e3f..7f2c70d4ae4 100644 --- a/Core/Libraries/Source/WWVegas/compat/win32_shims/windows.h +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/windows.h @@ -956,6 +956,12 @@ typedef struct GUID { static inline char *_strdup(const char *src) { + // TheSuperHackers @bugfix bobtista 23/07/2026 Match Windows _strdup semantics: return NULL for NULL input. + // BSD/glibc strdup(NULL) dereferences NULL and crashes. + if (src == nullptr) + { + return nullptr; + } return ::strdup(src); } From b30a5720bbf1645bd771083130efd0f6152f066a Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Wed, 29 Jul 2026 15:18:21 +0100 Subject: [PATCH 026/523] build(debug): Replace MSVC __int64 in SimpleProfiler with long long --- Core/GameEngine/Include/Common/Debug.h | 12 ++++++++---- Core/GameEngine/Source/Common/System/Debug.cpp | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Core/GameEngine/Include/Common/Debug.h b/Core/GameEngine/Include/Common/Debug.h index 7288a753cda..cf69717b153 100644 --- a/Core/GameEngine/Include/Common/Debug.h +++ b/Core/GameEngine/Include/Common/Debug.h @@ -223,10 +223,14 @@ DEBUG_EXTERN_C void ReleaseCrashLocalized(const AsciiString& p, const AsciiStrin class SimpleProfiler { private: - __int64 m_freq; - __int64 m_startThisSession; - __int64 m_totalThisSession; - __int64 m_totalAllSessions; + // TheSuperHackers @build bobtista 29/07/2026 __int64 is an MSVC extension, so every + // non-Windows build with debug profiling enabled failed to compile here. long long is + // the same 64-bit signed type and matches the LARGE_INTEGER::QuadPart these members + // are cast to when queried. + long long m_freq; + long long m_startThisSession; + long long m_totalThisSession; + long long m_totalAllSessions; int m_numSessions; public: diff --git a/Core/GameEngine/Source/Common/System/Debug.cpp b/Core/GameEngine/Source/Common/System/Debug.cpp index 10207ece344..7c9a871421b 100644 --- a/Core/GameEngine/Source/Common/System/Debug.cpp +++ b/Core/GameEngine/Source/Common/System/Debug.cpp @@ -647,7 +647,7 @@ void SimpleProfiler::stop() { if (m_startThisSession != 0) { - __int64 stop; + long long stop; QueryPerformanceCounter((LARGE_INTEGER*)&stop); m_totalThisSession = stop - m_startThisSession; m_totalAllSessions += stop - m_startThisSession; From b42980a1f97f83cc74b75b48d91cdf998d0118be Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 28 Apr 2026 16:23:31 +1000 Subject: [PATCH 027/523] build: add flat-directory deploy scripts for GeneralsMD macOS (cherry picked from commit 1444b8aabd51fac0ec41e10c58e710037a50eadd) --- scripts/build/macos/build-macos-generalsmd.sh | 16 ++++ .../build/macos/deploy-macos-generalsmd.sh | 81 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100755 scripts/build/macos/build-macos-generalsmd.sh create mode 100755 scripts/build/macos/deploy-macos-generalsmd.sh diff --git a/scripts/build/macos/build-macos-generalsmd.sh b/scripts/build/macos/build-macos-generalsmd.sh new file mode 100755 index 00000000000..0989a4fec52 --- /dev/null +++ b/scripts/build/macos/build-macos-generalsmd.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +# TheSuperHackers @build bobtista 28/04/2026 GeneralsMD-only macOS build helper. +# Configures and builds the bgfx/SDL3 preset, then deploys the binary plus its +# dylib dependencies to the runtime directory chosen by deploy-macos-generalsmd.sh. + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" +preset="${GGC_MACOS_PRESET:-macos-generalsmd-sdl3-bgfx}" +config="${GGC_MACOS_CONFIG:-Release}" +target="${GGC_MACOS_TARGET:-z_generals}" + +cmake --preset "${preset}" "${repo_root}" +cmake --build "${repo_root}/build/${preset}" --config "${config}" --target "${target}" + +"${repo_root}/scripts/build/macos/deploy-macos-generalsmd.sh" "${preset}" "${config}" diff --git a/scripts/build/macos/deploy-macos-generalsmd.sh b/scripts/build/macos/deploy-macos-generalsmd.sh new file mode 100755 index 00000000000..676bb9dcc70 --- /dev/null +++ b/scripts/build/macos/deploy-macos-generalsmd.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +set -euo pipefail + +# TheSuperHackers @build bobtista 28/04/2026 Flat-directory deploy for the +# GeneralsMD macOS build. Copies the built binary into a runtime directory next +# to the user's .big assets, bundles its dylib dependencies via dylibbundler, +# and writes a run.sh wrapper. +# +# Default runtime dir: ~/TheSuperHackers/GeneralsZH/ +# Override with GGC_MACOS_RUNTIME_DIR=/path/to/install/dir + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" +preset="${1:-${GGC_MACOS_PRESET:-macos-generalsmd-sdl3-bgfx}}" +config="${2:-${GGC_MACOS_CONFIG:-Release}}" +binary_dir="${repo_root}/build/${preset}" +runtime_dir="${GGC_MACOS_RUNTIME_DIR:-${HOME}/TheSuperHackers/GeneralsZH}" + +readonly binary_name="generalszh" +candidate_binaries=( + "${binary_dir}/GeneralsMD/Code/Main/${config}/${binary_name}" + "${binary_dir}/GeneralsMD/Code/Main/${binary_name}" + "${binary_dir}/${config}/${binary_name}" + "${binary_dir}/${binary_name}" +) +binary_path="" +for candidate in "${candidate_binaries[@]}"; do + if [[ -x "${candidate}" ]]; then + binary_path="${candidate}" + break + fi +done +if [[ -z "${binary_path}" ]]; then + echo "ERROR: GeneralsMD binary not found. Checked:" >&2 + printf ' %s\n' "${candidate_binaries[@]}" >&2 + exit 1 +fi + +mkdir -p "${runtime_dir}" + +echo "Deploying ${binary_path}" +echo " to ${runtime_dir}" + +cp -f "${binary_path}" "${runtime_dir}/${binary_name}" +chmod +x "${runtime_dir}/${binary_name}" + +if command -v dylibbundler >/dev/null 2>&1; then + echo " Bundling dylib dependencies via dylibbundler..." + dylibbundler \ + -od \ + -b \ + -x "${runtime_dir}/${binary_name}" \ + -d "${runtime_dir}/" \ + -p "@executable_path/" \ + -i /usr/lib \ + -i /System/Library +else + echo "WARNING: dylibbundler not found; dylib dependencies were not staged." >&2 + echo " Install with 'brew install dylibbundler' to enable bundling." >&2 +fi + +echo " Writing run.sh wrapper..." +cat > "${runtime_dir}/run.sh" <<'WRAPPER' +#!/usr/bin/env bash +# TheSuperHackers @build bobtista 28/04/2026 GeneralsMD macOS launch wrapper. +set -euo pipefail +script_dir="$(cd "$(dirname "$0")" && pwd)" +cd "${script_dir}" +export DYLD_LIBRARY_PATH="${script_dir}:${DYLD_LIBRARY_PATH:-}" +exec "${script_dir}/generalszh" "$@" +WRAPPER +chmod +x "${runtime_dir}/run.sh" + +echo "" +echo "Deploy complete" +echo " Executable: ${runtime_dir}/${binary_name}" +echo " Wrapper: ${runtime_dir}/run.sh" +echo "" +echo "Run with:" +echo " ${runtime_dir}/run.sh" +echo "" +echo "Place your retail Zero Hour .big files in ${runtime_dir}/ before launching." From 1050aca8dc92e18db26c879d1b02b60e7a6d2755 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Thu, 30 Apr 2026 09:26:29 +1000 Subject: [PATCH 028/523] build(unix): expose dx8 headers on non-Win, disable crashdumps, add fetch-game-data (cherry picked from commit 16162941eee5ccb8b6b4f76586f935254c1defd9) --- CMakeLists.txt | 6 +++ cmake/config-memory.cmake | 4 ++ cmake/dx8.cmake | 15 ++++++ .../build/macos/deploy-macos-generalsmd.sh | 2 + scripts/build/macos/fetch-game-data.sh | 54 +++++++++++++++++++ 5 files changed, 81 insertions(+) create mode 100755 scripts/build/macos/fetch-game-data.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index d3a223a2c41..9000b4c9fd4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,12 @@ endif() if((WIN32 OR "${CMAKE_SYSTEM}" MATCHES "Windows") AND ${CMAKE_SIZEOF_VOID_P} EQUAL 4) include(cmake/miles.cmake) include(cmake/bink.cmake) +endif() + +# The standalone bgfx renderer still uses legacy D3D8/D3DX8 headers for +# shared math/types even on non-Windows builds, so make the min-dx8-sdk +# header target available there as well. +if(GGC_BGFX_STANDALONE OR ((WIN32 OR "${CMAKE_SYSTEM}" MATCHES "Windows") AND ${CMAKE_SIZEOF_VOID_P} EQUAL 4)) include(cmake/dx8.cmake) endif() diff --git a/cmake/config-memory.cmake b/cmake/config-memory.cmake index 5daa6f110a4..af4d3422b50 100644 --- a/cmake/config-memory.cmake +++ b/cmake/config-memory.cmake @@ -23,6 +23,10 @@ option(RTS_MEMORYPOOL_DEBUG_INTENSE_DMA_BOOKKEEPING "Prints statistics for memor # Memory dump options option(RTS_CRASHDUMP_ENABLE "Enables writing crash dumps on unhandled exceptions or release crash failures." ON) +if(NOT WIN32) + set(RTS_CRASHDUMP_ENABLE OFF CACHE BOOL "Enables writing crash dumps on unhandled exceptions or release crash failures." FORCE) +endif() + # Game Memory features add_feature_info(GameMemoryEnable RTS_GAMEMEMORY_ENABLE "Build with the original game memory implementation") diff --git a/cmake/dx8.cmake b/cmake/dx8.cmake index dd08f56119a..fda3e8d798f 100644 --- a/cmake/dx8.cmake +++ b/cmake/dx8.cmake @@ -5,3 +5,18 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(dx8) + +# TheSuperHackers @build bobtista 22/04/2026 +# In standalone bgfx mode the game supplies d3d8/d3dx8 functions from +# in-tree stubs (Core/Libraries/Source/WWVegas/WW3D2/StubD3D8Device.cpp +# + D3DXStandaloneStubs.cpp). Pulling the real d3d8.lib / d3dx8.lib +# would cause duplicate-symbol link errors, so strip them off the +# shared d3d8lib INTERFACE target. Headers stay (include dir is still +# on d3d8lib), and import libs that can't be stubbed (dinput8, dxguid) +# stay as well. Ref-popup builds are unaffected. +if(GGC_BGFX_STANDALONE) + set_property(TARGET d3d8lib PROPERTY INTERFACE_LINK_LIBRARIES "") + if(WIN32 OR "${CMAKE_SYSTEM}" MATCHES "Windows") + target_link_libraries(d3d8lib INTERFACE dinput8 dxguid) + endif() +endif() diff --git a/scripts/build/macos/deploy-macos-generalsmd.sh b/scripts/build/macos/deploy-macos-generalsmd.sh index 676bb9dcc70..b92c8466031 100755 --- a/scripts/build/macos/deploy-macos-generalsmd.sh +++ b/scripts/build/macos/deploy-macos-generalsmd.sh @@ -17,6 +17,8 @@ runtime_dir="${GGC_MACOS_RUNTIME_DIR:-${HOME}/TheSuperHackers/GeneralsZH}" readonly binary_name="generalszh" candidate_binaries=( + "${binary_dir}/GeneralsMD/${config}/${binary_name}" + "${binary_dir}/GeneralsMD/${binary_name}" "${binary_dir}/GeneralsMD/Code/Main/${config}/${binary_name}" "${binary_dir}/GeneralsMD/Code/Main/${binary_name}" "${binary_dir}/${config}/${binary_name}" diff --git a/scripts/build/macos/fetch-game-data.sh b/scripts/build/macos/fetch-game-data.sh new file mode 100755 index 00000000000..ac0049808bb --- /dev/null +++ b/scripts/build/macos/fetch-game-data.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +set -euo pipefail + +# TheSuperHackers @build bobtista 29/04/2026 Pull the source-controlled +# Zero Hour game data files (INI rules, .wnd UI defs, language strings) from +# TheSuperHackers/GeneralsGamePatch into the macOS runtime directory. +# +# Retail .big files (Maps, Models, Textures, Audio, Music, Speech) are NOT +# in that repo — copy them from your retail Zero Hour install separately. +# +# Default runtime dir: ~/TheSuperHackers/GeneralsZH/ +# Override with GGC_MACOS_RUNTIME_DIR=/path/to/install/dir + +repo_url="https://github.com/TheSuperHackers/GeneralsGamePatch.git" +cache_dir="${GGC_GAMEDATA_CACHE:-${HOME}/.cache/TheSuperHackers/GeneralsGamePatch}" +sparse_path="Patch104pZH/GameFilesOriginalZH" +runtime_dir="${GGC_MACOS_RUNTIME_DIR:-${HOME}/TheSuperHackers/GeneralsZH}" + +mkdir -p "${runtime_dir}" + +if [[ ! -d "${cache_dir}/.git" ]]; then + echo "Cloning ${repo_url} (sparse: ${sparse_path}) into ${cache_dir}..." + mkdir -p "$(dirname "${cache_dir}")" + git clone --depth=1 --filter=blob:none --sparse "${repo_url}" "${cache_dir}" + git -C "${cache_dir}" sparse-checkout set "${sparse_path}" +else + echo "Updating cache at ${cache_dir}..." + git -C "${cache_dir}" sparse-checkout set "${sparse_path}" + git -C "${cache_dir}" pull --depth=1 --ff-only +fi + +src_dir="${cache_dir}/${sparse_path}" +if [[ ! -d "${src_dir}" ]]; then + echo "ERROR: ${src_dir} missing after clone." >&2 + exit 1 +fi + +echo "Staging Data/, Window/, Art/ into ${runtime_dir}/" +for sub in Data Window Art; do + if [[ -d "${src_dir}/${sub}" ]]; then + rsync -a "${src_dir}/${sub}/" "${runtime_dir}/${sub}/" + fi +done + +echo "" +echo "Source-controlled game data staged." +echo "" +echo "You still need the retail .big files from your Zero Hour install:" +echo " INIZH.big WindowZH.big EnglishZH.big (or German/French/etc.)" +echo " MapsZH.big W3DZH.big TexturesZH.big" +echo " MusicZH.big SpeechZH.big AudioZH.big" +echo "" +echo "Drop them next to the binary at:" +echo " ${runtime_dir}/" From 980ebe58e30b8652c9942c940a917164afa7ed05 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Thu, 30 Apr 2026 17:21:48 +1000 Subject: [PATCH 029/523] build(macos): retry-launch wrapper for the AGX shader-compile startup race (cherry picked from commit 2a13055012008f5462f52ea933e46aa31101e898) --- .../build/macos/deploy-macos-generalsmd.sh | 48 +++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/scripts/build/macos/deploy-macos-generalsmd.sh b/scripts/build/macos/deploy-macos-generalsmd.sh index b92c8466031..e159a33dc38 100755 --- a/scripts/build/macos/deploy-macos-generalsmd.sh +++ b/scripts/build/macos/deploy-macos-generalsmd.sh @@ -63,12 +63,54 @@ fi echo " Writing run.sh wrapper..." cat > "${runtime_dir}/run.sh" <<'WRAPPER' #!/usr/bin/env bash -# TheSuperHackers @build bobtista 28/04/2026 GeneralsMD macOS launch wrapper. -set -euo pipefail +# TheSuperHackers @build bobtista 30/04/2026 GeneralsMD macOS launch wrapper. +# +# Apple's AGX shader compiler intermittently faults inside +# AGCDeserializedReply when bgfx asks it to compile per-framebuffer +# helper shaders for our 14-view setup. The fault is on a background +# dispatch queue, lands in the first 1-2s of run-time, and can't be +# caught from inside the process. The engine itself is fine once it +# wins the race - 174s+ continuous runs captured. Until Apple fixes +# the driver bug, the practical workaround is to retry on a fast-fail +# exit code. Set GGC_MACOS_NO_RETRY=1 to disable. +set -uo pipefail script_dir="$(cd "$(dirname "$0")" && pwd)" cd "${script_dir}" export DYLD_LIBRARY_PATH="${script_dir}:${DYLD_LIBRARY_PATH:-}" -exec "${script_dir}/generalszh" "$@" + +if [[ "${GGC_MACOS_NO_RETRY:-0}" == "1" ]]; then + exec "${script_dir}/generalszh" "$@" +fi + +readonly max_attempts="${GGC_MACOS_LAUNCH_ATTEMPTS:-25}" +readonly fast_fail_seconds="${GGC_MACOS_FAST_FAIL_SECONDS:-15}" +readonly retry_sleep="${GGC_MACOS_RETRY_SLEEP:-0.3}" + +for ((attempt=1; attempt<=max_attempts; ++attempt)); do + start_epoch=$(date +%s) + "${script_dir}/generalszh" "$@" + rc=$? + end_epoch=$(date +%s) + elapsed=$((end_epoch - start_epoch)) + + # Clean exits and slow failures get propagated as-is. + if [[ ${rc} -eq 0 ]] || [[ ${elapsed} -ge ${fast_fail_seconds} ]]; then + exit ${rc} + fi + + # Only retry on the fast-fail signal exits we attribute to the AGX race. + if [[ ${rc} -ne 134 && ${rc} -ne 138 && ${rc} -ne 139 ]]; then + exit ${rc} + fi + + if [[ ${attempt} -lt ${max_attempts} ]]; then + echo "[run.sh] AGX startup race lost (exit ${rc} in ${elapsed}s); retrying ${attempt}/${max_attempts}..." >&2 + sleep "${retry_sleep}" + fi +done + +echo "[run.sh] Gave up after ${max_attempts} attempts. Set GGC_MACOS_NO_RETRY=1 or raise GGC_MACOS_LAUNCH_ATTEMPTS." >&2 +exit ${rc} WRAPPER chmod +x "${runtime_dir}/run.sh" From dca573b778358bd8a4f6ccc8e171b48e9e3264bf Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Thu, 30 Apr 2026 19:10:24 +1000 Subject: [PATCH 030/523] build(macos): forward INT/TERM to running generalszh so retry wrapper does not leak processes (cherry picked from commit b229f64aa4cfee680ab8fb504916f2916f715e8e) --- scripts/build/macos/deploy-macos-generalsmd.sh | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/scripts/build/macos/deploy-macos-generalsmd.sh b/scripts/build/macos/deploy-macos-generalsmd.sh index e159a33dc38..589ba642b84 100755 --- a/scripts/build/macos/deploy-macos-generalsmd.sh +++ b/scripts/build/macos/deploy-macos-generalsmd.sh @@ -86,10 +86,26 @@ readonly max_attempts="${GGC_MACOS_LAUNCH_ATTEMPTS:-25}" readonly fast_fail_seconds="${GGC_MACOS_FAST_FAIL_SECONDS:-15}" readonly retry_sleep="${GGC_MACOS_RETRY_SLEEP:-0.3}" +# TheSuperHackers @bugfix bobtista 30/04/2026 Forward Ctrl+C / SIGTERM +# to whichever generalszh attempt is currently running, so quitting the +# wrapper does not leak background game processes (each one holds a GPU +# context and keeps a window open). +child_pid="" +forward_signal() { + if [[ -n "${child_pid}" ]] && kill -0 "${child_pid}" 2>/dev/null; then + kill -"$1" "${child_pid}" 2>/dev/null || true + fi +} +trap 'forward_signal TERM; exit 130' INT +trap 'forward_signal TERM; exit 143' TERM + for ((attempt=1; attempt<=max_attempts; ++attempt)); do start_epoch=$(date +%s) - "${script_dir}/generalszh" "$@" + "${script_dir}/generalszh" "$@" & + child_pid=$! + wait "${child_pid}" rc=$? + child_pid="" end_epoch=$(date +%s) elapsed=$((end_epoch - start_epoch)) From 6aef76e2d0f16a42aff18f7ab274340c53148314 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Wed, 6 May 2026 18:10:23 +1000 Subject: [PATCH 031/523] Add game files guide and update README for cross-platform (cherry picked from commit 01656f9aa4aea5695e8761742e9cec5b5bf6dde5) --- README.md | 34 +++++++-- docs/BUILD/GETTING_THE_GAME_FILES.md | 109 +++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 docs/BUILD/GETTING_THE_GAME_FILES.md diff --git a/README.md b/README.md index 71ef5135ad0..8f2f8c39fab 100644 --- a/README.md +++ b/README.md @@ -47,12 +47,15 @@ Here's an overview of our current focus and future plans ## Running the Game -To run *Generals* or *Zero Hour* using this project, you need to have the original *Command & Conquer: Generals and Zero Hour* game -installed. The easiest way to get it is through *Command & Conquer The Ultimate Collection* -on [Steam](https://store.steampowered.com/bundle/39394). Once the game is ready, download the latest version of the -project from [GitHub Releases](https://github.com/TheSuperHackers/GeneralsGameCode/releases), extract the necessary -files, and follow the instructions in the [Wiki](https://github.com/TheSuperHackers/GeneralsGameCode/wiki). +You need the original *Command & Conquer: Generals* and *Zero Hour* game data to play. The easiest way is through +*Command & Conquer The Ultimate Collection* on [Steam](https://store.steampowered.com/bundle/39394). +**Windows:** Download the latest build from [GitHub Releases](https://github.com/TheSuperHackers/GeneralsGameCode/releases), +extract into your game directory, and follow the [Wiki](https://github.com/TheSuperHackers/GeneralsGameCode/wiki) instructions. + +**macOS / Linux:** Steam does not offer a macOS or Linux download. See +[Getting the Game Files](docs/BUILD/GETTING_THE_GAME_FILES.md) for three ways to obtain the retail data +(copy from Windows, SteamCMD, or CrossOver). ## Joining the Community @@ -61,7 +64,7 @@ report bugs, and contribute to the project! ## Building the Game Yourself -We provide support for building the project on Windows and Linux. For detailed build instructions, check the +We provide support for building the project on Windows, Linux, and macOS. For detailed build instructions, check the [Wiki](https://github.com/TheSuperHackers/GeneralsGameCode/wiki/build_guides), which includes guides for VS6, VS2022, Docker, CLion, and links to forks supporting additional versions. @@ -79,6 +82,25 @@ cmake --build build/win32 --config Release ./scripts/docker-install.sh --detect # Install to your game ``` +**macOS (Apple Silicon / Intel)** + +Requires Xcode command line tools and Homebrew. Uses bgfx (Metal), SDL3, and OpenAL. + +```bash +xcode-select --install +brew install cmake ninja dylibbundler ffmpeg sdl3 openal-soft +scripts/build/macos/build-macos-generalsmd.sh +``` + +This builds and deploys to `~/TheSuperHackers/GeneralsZH/`. Before launching, you need +the retail game data — see [Getting the Game Files](docs/BUILD/GETTING_THE_GAME_FILES.md). + +```bash +scripts/build/macos/fetch-game-data.sh # open-source INI/UI/Art data +# copy retail .big files into ~/TheSuperHackers/GeneralsZH/ +~/TheSuperHackers/GeneralsZH/run.sh # launch +``` + ### Dependency management The repository uses a vcpkg manifest (`vcpkg.json`) paired with a lockfile (`vcpkg-lock.json`). When you add or upgrade diff --git a/docs/BUILD/GETTING_THE_GAME_FILES.md b/docs/BUILD/GETTING_THE_GAME_FILES.md new file mode 100644 index 00000000000..62eb3f5e6fd --- /dev/null +++ b/docs/BUILD/GETTING_THE_GAME_FILES.md @@ -0,0 +1,109 @@ +# Getting the Game Files + +GeneralsGameCode is the open-source game engine. To play, you also need the retail game data files (maps, models, textures, audio) from Command & Conquer: Generals and Zero Hour. + +Steam does not offer a macOS or Linux download, so you need to obtain the Windows game files and copy them to your runtime directory. + +## Option 1 — Copy From a Windows Installation (Easiest) + +If you have Generals Zero Hour installed on a Windows PC (Steam, Origin, or disc): + +1. Locate your installation directories: + - **Steam:** `C:\Program Files (x86)\Steam\steamapps\common\Command and Conquer Generals Zero Hour\` + - **Origin/EA App:** `C:\Program Files (x86)\Origin Games\Command and Conquer Generals Zero Hour\` + +2. Copy all `.big` files to your runtime directory (`~/TheSuperHackers/GeneralsZH/` on macOS). + +3. Transfer via USB drive, network share, or cloud storage. + +## Option 2 — SteamCMD (No Windows PC Needed) + +SteamCMD is Valve's headless Steam client. It runs on macOS and Linux and can download Windows game files if you own the game on Steam. + +**Install SteamCMD:** +```bash +# macOS +brew install steamcmd + +# Linux (Debian/Ubuntu) +sudo apt install steamcmd +``` + +**Download the game files:** +```bash +steamcmd \ + +@sSteamCmdForcePlatformType windows \ + +login YOUR_STEAM_USERNAME \ + +force_install_dir ./generals_files \ + +app_update 2732940 validate \ + +quit + +steamcmd \ + +@sSteamCmdForcePlatformType windows \ + +login YOUR_STEAM_USERNAME \ + +force_install_dir ./zh_files \ + +app_update 2732960 validate \ + +quit +``` + +Steam will prompt for your password and Steam Guard code. + +App IDs: `2732940` = Generals, `2732960` = Zero Hour. + +**Copy to runtime directory:** +```bash +cp ./generals_files/*.big ~/TheSuperHackers/GeneralsZH/ +cp ./zh_files/*.big ~/TheSuperHackers/GeneralsZH/ +``` + +## Option 3 — CrossOver Trial (No Windows PC, No Command Line) + +CrossOver lets you run Windows software on macOS. CodeWeavers offers a free 14-day trial. + +1. Download CrossOver from https://www.codeweavers.com/crossover +2. Install Steam inside CrossOver +3. Log in and download Generals Zero Hour +4. Copy the `.big` files from the CrossOver bottle to your runtime directory + +The default bottle path is: +``` +~/Library/Application Support/CrossOver/Bottles//drive_c/Program Files (x86)/Steam/steamapps/common/ +``` + +## Required Files + +At minimum you need these `.big` files from both Generals and Zero Hour: + +**Zero Hour:** +- `INIZH.big` — game rules and configuration +- `W3DZH.big` — 3D models +- `TexturesZH.big` — textures +- `MapsZH.big` — maps +- `WindowZH.big` — UI definitions +- `EnglishZH.big` — localized text (use your language variant) + +**Generals (base game):** +- `INI.big`, `W3D.big`, `Textures.big`, `Maps.big`, `Window.big`, `English.big` + +**Optional (audio/music):** +- `MusicZH.big`, `AudioZH.big`, `AudioEnglishZH.big`, `SpeechEnglishZH.big` +- `Music.big`, `Audio.big`, `AudioEnglish.big`, `SpeechEnglish.big`, `Shaders.big` + +Without the audio files the game runs fine — set `GGC_NO_AUDIO=1` to suppress audio warnings. + +## Open-Source Game Data + +Some game data (INI scripts, UI definitions, art overrides) is maintained in the open-source patch repository. Fetch it with: + +```bash +scripts/build/macos/fetch-game-data.sh +``` + +This pulls from [TheSuperHackers/GeneralsGamePatch](https://github.com/TheSuperHackers/GeneralsGamePatch) and stages `Data/`, `Window/`, and `Art/` into your runtime directory. These files override the corresponding entries in the `.big` archives. + +## Next Steps + +Once game files are in place, build and deploy the engine: +- [macOS Build Guide](../../README.md#macos-apple-silicon--intel) +- [Windows Build Guide](../../README.md#windows-visual-studio-2022) +- [Linux Build Guide](../../README.md#linux-via-docker) From f4c50d8f28f68a86c377ab477009823aca2ef71f Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 9 May 2026 14:16:13 +1000 Subject: [PATCH 032/523] fix(macos): preserve argv0 in compat command line (cherry picked from commit 449e3a760c4c11a863c24ce36360751c271cdde8) --- GeneralsMD/Code/Main/SDL3Main.cpp | 116 ++++++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 14 deletions(-) diff --git a/GeneralsMD/Code/Main/SDL3Main.cpp b/GeneralsMD/Code/Main/SDL3Main.cpp index 06294b70f64..6c3d5995e7b 100644 --- a/GeneralsMD/Code/Main/SDL3Main.cpp +++ b/GeneralsMD/Code/Main/SDL3Main.cpp @@ -16,12 +16,29 @@ #include #include +#if defined(__APPLE__) +#include +#endif +#include +#include #include +// TheSuperHackers @info bobtista 30/04/2026 Release builds strip +// WWDEBUG_SAY, so during macOS bring-up we emit a small set of stderr +// breadcrumbs so we can see where init reaches even when the window +// never comes up. Set GGC_TRACE=1 to enable. +#define GGC_TRACE(fmt, ...) do { \ + if (std::getenv("GGC_TRACE") != NULL) { \ + std::fprintf(stderr, "[ggc] " fmt "\n", ##__VA_ARGS__); \ + std::fflush(stderr); \ + } \ +} while (0) + #include "Common/CommandLine.h" #include "Common/Debug.h" #include "Common/GameEngine.h" #include "Common/GameMemory.h" +#include "Common/GlobalData.h" #include "Common/version.h" #include "GameClient/ClientInstance.h" #include "SDL3GameEngine.h" @@ -68,6 +85,18 @@ void OSDisplaySetBusyState(Bool /*busyDisplay*/, Bool /*busySystem*/) {} SDL_Window *TheSDL3Window = NULL; void *ApplicationHWnd = NULL; +// TheSuperHackers @bugfix bobtista 30/04/2026 macOS-only: keep the +// SDL_Metal view alive for the lifetime of the bgfx renderer, and +// publish its CAMetalLayer pointer here so BgfxBackend's +// GetNativeWindowHandle can hand it to bgfx as platformData.nwh +// instead of an NSWindow. Passing the NSWindow lets bgfx try to +// install its own CAMetalLayer on the contentView, which fights with +// the layer SDL3 already created and trips the Apple AGX driver +// during pipeline-state compile. +#if defined(__APPLE__) +SDL_MetalView TheSDL3MetalView = NULL; +void *TheSDL3MetalLayer = NULL; +#endif extern Int GameMain(); @@ -76,14 +105,23 @@ int main(int argc, char **argv) __argc = argc; __argv = argv; + GGC_TRACE("main entered argc=%d", argc); + // TheSuperHackers @bugfix bobtista 30/04/2026 Build a Win32-style // command-line string from argv so GetCommandLineA() in the compat - // shim returns the real arguments. parseCommandLine tokenises by - // whitespace and respects double quotes, so wrap any arg that - // contains a space and escape embedded quotes. - for (int i = 1; i < argc; ++i) + // shim returns the real arguments. The legacy parser expects token 0 + // to be the executable name and starts parsing at token 1, so argv[0] + // must be preserved here. + // + // The engine's parseCommandLine tokenises with nextParam(buf, "\" "), + // i.e. ' ' and '"' are the only separators and there is no backslash + // escape - so we just wrap any arg that contains a space in double + // quotes. Args that contain BOTH a space and a literal '"' are + // unsupported by the engine parser itself, so we don't try to encode + // them either. + for (int i = 0; i < argc; ++i) { - if (i > 1) + if (i > 0) { s_compatCommandLineStorage += ' '; } @@ -91,7 +129,7 @@ int main(int argc, char **argv) bool needsQuote = false; for (const char *p = a; *p != '\0'; ++p) { - if (*p == ' ' || *p == '\t') + if (*p == ' ') { needsQuote = true; break; @@ -101,14 +139,7 @@ int main(int argc, char **argv) { s_compatCommandLineStorage += '"'; } - for (const char *p = a; *p != '\0'; ++p) - { - if (*p == '"') - { - s_compatCommandLineStorage += '\\'; - } - s_compatCommandLineStorage += *p; - } + s_compatCommandLineStorage += a; if (needsQuote) { s_compatCommandLineStorage += '"'; @@ -116,11 +147,13 @@ int main(int argc, char **argv) } g_compatCommandLine = s_compatCommandLineStorage.c_str(); + GGC_TRACE("calling SDL_Init"); if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) { SDL_Log("SDL_Init failed: %s", SDL_GetError()); return 1; } + GGC_TRACE("SDL_Init OK"); Uint32 windowFlags = SDL_WINDOW_RESIZABLE; #if defined(__APPLE__) @@ -148,6 +181,13 @@ int main(int argc, char **argv) break; } } + if (wantWindowed) + { + windowW = (requestedW > 0) ? requestedW : kDefaultWindowWidth; + windowH = (requestedH > 0) ? requestedH : kDefaultWindowHeight; + } + + GGC_TRACE("calling SDL_CreateWindow"); TheSDL3Window = SDL_CreateWindow(kWindowTitle, windowW, windowH, windowFlags); if (TheSDL3Window == NULL) { @@ -161,9 +201,31 @@ int main(int argc, char **argv) SDL_SetWindowFullscreen(TheSDL3Window, true); SDL_SyncWindow(TheSDL3Window); } + GGC_TRACE("SDL_CreateWindow OK window=%p", (void*)TheSDL3Window); ApplicationHWnd = TheSDL3Window; +#if defined(__APPLE__) + // TheSuperHackers @bugfix bobtista 30/04/2026 Use SDL3's official + // Metal-view helper so we own a CAMetalLayer-backed NSView and can + // hand bgfx the CAMetalLayer directly. Without this, bgfx receives + // the NSWindow and races with SDL3 for control of the contentView's + // layer, which manifests as intermittent AGX driver compilation + // crashes in AGCDeserializedReply on macOS Tahoe / Apple Silicon. + GGC_TRACE("calling SDL_Metal_CreateView"); + TheSDL3MetalView = SDL_Metal_CreateView(TheSDL3Window); + if (TheSDL3MetalView != NULL) + { + TheSDL3MetalLayer = SDL_Metal_GetLayer(TheSDL3MetalView); + GGC_TRACE("SDL_Metal_CreateView OK view=%p layer=%p", + (void*)TheSDL3MetalView, TheSDL3MetalLayer); + } + else + { + SDL_Log("SDL_Metal_CreateView failed: %s", SDL_GetError()); + } +#endif + // TheSuperHackers @build bobtista 30/04/2026 Mirror the early-init the // Win path does in WinMain.cpp: build TheVersion, then run command-line // parsing so TheWritableGlobalData exists before GameEngine::init runs. @@ -172,7 +234,22 @@ int main(int argc, char **argv) AsciiString(VERSION_BUILDUSER), AsciiString(VERSION_BUILDLOC), AsciiString(__TIME__), AsciiString(__DATE__)); + GGC_TRACE("calling parseCommandLineForStartup cmdline='%s'", g_compatCommandLine); CommandLine::parseCommandLineForStartup(); + GGC_TRACE("parseCommandLineForStartup OK headless=%d", + (TheGlobalData != NULL && TheGlobalData->m_headless) ? 1 : 0); + + // TheSuperHackers @bugfix bobtista 30/04/2026 -headless asks for + // engine-only execution (no rendering, no audio); on Apple Silicon + // macOS Tahoe even initialising bgfx Metal trips the AGX driver + // bug, so explicitly null out ApplicationHWnd here so the renderer + // chain (W3DDisplay::init -> WW3D::Init -> DX8Wrapper::Init -> + // BgfxBackend::Initialize) bails on null hwnd before any Metal + // pipeline state is constructed. + if (TheGlobalData != NULL && TheGlobalData->m_headless) + { + ApplicationHWnd = NULL; + } // TheSuperHackers @fix bobtista 08/07/2026 Mirror WinMain: refuse to start a // second client instance. Multi-instance builds and command-line flags claim @@ -180,13 +257,24 @@ int main(int argc, char **argv) Int result = 0; if (rts::ClientInstance::initialize()) { + GGC_TRACE("calling GameMain"); result = GameMain(); + GGC_TRACE("GameMain returned result=%d", result); } else { DEBUG_LOG(("Generals is already running...Bail!")); } +#if defined(__APPLE__) + if (TheSDL3MetalView != NULL) + { + SDL_Metal_DestroyView(TheSDL3MetalView); + TheSDL3MetalView = NULL; + TheSDL3MetalLayer = NULL; + } +#endif + SDL_DestroyWindow(TheSDL3Window); TheSDL3Window = NULL; ApplicationHWnd = NULL; From ef56a6f338a82062ea17bf6ecfdbefaab88e798b Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Thu, 28 May 2026 19:53:41 +1000 Subject: [PATCH 033/523] tweak(cmake): soften RTS_CRASHDUMP_ENABLE FORCE override on non-Windows --- cmake/config-memory.cmake | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmake/config-memory.cmake b/cmake/config-memory.cmake index af4d3422b50..f2966dc5ce6 100644 --- a/cmake/config-memory.cmake +++ b/cmake/config-memory.cmake @@ -23,8 +23,9 @@ option(RTS_MEMORYPOOL_DEBUG_INTENSE_DMA_BOOKKEEPING "Prints statistics for memor # Memory dump options option(RTS_CRASHDUMP_ENABLE "Enables writing crash dumps on unhandled exceptions or release crash failures." ON) -if(NOT WIN32) - set(RTS_CRASHDUMP_ENABLE OFF CACHE BOOL "Enables writing crash dumps on unhandled exceptions or release crash failures." FORCE) +# TheSuperHackers @tweak bobtista 28/05/2026 On non-Windows we only soft-default RTS_CRASHDUMP_ENABLE OFF when the user has not already pinned a value in the cache; FORCE was stomping explicit user overrides on every reconfigure. +if(NOT WIN32 AND NOT DEFINED CACHE{RTS_CRASHDUMP_ENABLE}) + set(RTS_CRASHDUMP_ENABLE OFF CACHE BOOL "Enables writing crash dumps on unhandled exceptions or release crash failures.") endif() # Game Memory features From 06d6c1f1fd3772e26409759d52f33a72f9d7cdfe Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Fri, 26 Jun 2026 09:46:56 -0400 Subject: [PATCH 034/523] build(mac): fix local filesystem error logging compile --- .../Source/StdDevice/Common/StdLocalFileSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp b/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp index ab51fbcf2ed..5d802713e3f 100644 --- a/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp +++ b/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp @@ -166,7 +166,7 @@ static std::filesystem::path fixFilenameFromWindowsPath(const Char *filename, In // Required to allow creation of new files if (!(access & File::WRITE)) { - DEBUG_LOG(("StdLocalFileSystem::fixFilenameFromWindowsPath - Error finding file %s", filename.string().c_str())); + DEBUG_LOG(("StdLocalFileSystem::fixFilenameFromWindowsPath - Error finding file %s", filename)); DEBUG_LOG(("StdLocalFileSystem::fixFilenameFromWindowsPath - Got so far %s", pathCurrent.string().c_str())); return std::filesystem::path(); From 498a7833f9ffd32763c057c0664fbf0703b64bb2 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Thu, 2 Jul 2026 00:47:59 -0400 Subject: [PATCH 035/523] tweak(mac): drop archive-fallback misses from local filesystem log --- .../Source/StdDevice/Common/StdLocalFileSystem.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp b/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp index 5d802713e3f..fd9f102bc9f 100644 --- a/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp +++ b/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp @@ -166,9 +166,6 @@ static std::filesystem::path fixFilenameFromWindowsPath(const Char *filename, In // Required to allow creation of new files if (!(access & File::WRITE)) { - DEBUG_LOG(("StdLocalFileSystem::fixFilenameFromWindowsPath - Error finding file %s", filename)); - DEBUG_LOG(("StdLocalFileSystem::fixFilenameFromWindowsPath - Got so far %s", pathCurrent.string().c_str())); - return std::filesystem::path(); } From 7383fb1af5a32cf71620fb2083e129c0ac4390e3 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Wed, 22 Jul 2026 01:27:30 +0400 Subject: [PATCH 036/523] build(cmake): Default RTS_CRASHDUMP_ENABLE off on non-Windows --- cmake/config-memory.cmake | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cmake/config-memory.cmake b/cmake/config-memory.cmake index f2966dc5ce6..96024e1a694 100644 --- a/cmake/config-memory.cmake +++ b/cmake/config-memory.cmake @@ -21,12 +21,13 @@ option(RTS_MEMORYPOOL_DEBUG_CHECK_BLOCK_OWNERSHIP "Enables debug to verify that option(RTS_MEMORYPOOL_DEBUG_INTENSE_DMA_BOOKKEEPING "Prints statistics for memory usage of Memory Pools." OFF) # Memory dump options -option(RTS_CRASHDUMP_ENABLE "Enables writing crash dumps on unhandled exceptions or release crash failures." ON) - -# TheSuperHackers @tweak bobtista 28/05/2026 On non-Windows we only soft-default RTS_CRASHDUMP_ENABLE OFF when the user has not already pinned a value in the cache; FORCE was stomping explicit user overrides on every reconfigure. -if(NOT WIN32 AND NOT DEFINED CACHE{RTS_CRASHDUMP_ENABLE}) - set(RTS_CRASHDUMP_ENABLE OFF CACHE BOOL "Enables writing crash dumps on unhandled exceptions or release crash failures.") +# TheSuperHackers @build bobtista 22/07/2026 Default crash dumps off on non-Windows since MiniDumper is Windows-only; deriving the per-platform default before option() lets it seed the correct default while still honoring explicit -D / preset overrides on reconfigure. +if(WIN32) + set(_RTS_CRASHDUMP_DEFAULT ON) +else() + set(_RTS_CRASHDUMP_DEFAULT OFF) endif() +option(RTS_CRASHDUMP_ENABLE "Enables writing crash dumps on unhandled exceptions or release crash failures." ${_RTS_CRASHDUMP_DEFAULT}) # Game Memory features add_feature_info(GameMemoryEnable RTS_GAMEMEMORY_ENABLE "Build with the original game memory implementation") From 2fc7b926d81ec4c7ebf69e4a1754a2659f549a8c Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 21 Apr 2026 14:02:45 -0400 Subject: [PATCH 037/523] feat(ww3d2): add IRenderBackend abstraction, DX8 reference adapter, and backend selection scaffolding (cherry picked from commit 8d4fe1343839ee0c82d64f917daa99f3b193ece2) --- .../Source/WWVegas/WW3D2/BgfxBackend.cpp | 308 +++++++++++++++++ .../Source/WWVegas/WW3D2/BgfxBackend.h | 127 +++++++ .../Source/WWVegas/WW3D2/CMakeLists.txt | 32 ++ .../Source/WWVegas/WW3D2/DX8Backend.cpp | 321 ++++++++++++++++++ .../Source/WWVegas/WW3D2/DX8Backend.h | 123 +++++++ .../Source/WWVegas/WW3D2/IRenderBackend.h | 217 ++++++++++++ .../Source/WWVegas/WW3D2/RenderBackend.cpp | 65 ++++ .../Source/WWVegas/WW3D2/RenderBackend.h | 42 +++ .../Source/WWVegas/WW3D2/dx8wrapper.cpp | 11 + .../W3DDevice/GameClient/W3DStatusCircle.cpp | 47 +-- .../W3DDevice/GameClient/W3DStatusCircle.cpp | 47 +-- cmake/bgfx.cmake | 39 +++ cmake/render-backend.cmake | 59 ++++ 13 files changed, 1400 insertions(+), 38 deletions(-) create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/RenderBackend.cpp create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/RenderBackend.h create mode 100644 cmake/bgfx.cmake create mode 100644 cmake/render-backend.cmake diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp new file mode 100644 index 00000000000..253fddbcd83 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -0,0 +1,308 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program 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, either version 3 of the License, or +** (at your option) any later version. +** +** This program 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. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +// TheSuperHackers @refactor bobtista 10/04/2026 BgfxBackend stub. +// +// Every virtual method is a no-op (void) or returns a sensible default +// (non-void). The class exists to prove the compile-time backend selection +// mechanism works and to verify that bgfx itself can be fetched, built, +// and linked against WW3D2. Later work fills in real implementations as +// individual rendering subsystems are migrated off DX8Wrapper statics. +// +// We deliberately #include and reference one bgfx symbol so +// that if the FetchContent + link pipeline is broken, we get a compile or +// link error early rather than deep inside the port. + +#include "BgfxBackend.h" + +#include "vector3.h" + +// Including the bgfx header here is intentional: it forces a compile-time +// dependency on the bgfx headers when GGC_RENDER_BACKEND=bgfx. If bgfx +// isn't available the build fails here, which is the right place to +// catch dependency problems. +#include + +#include + +namespace +{ +// Anchor a reference to one bgfx symbol so the linker must resolve bgfx +// symbols even though every virtual method below is a no-op. This turns a +// "bgfx built but never used" scenario into a loud link failure if anything +// is misconfigured. +[[maybe_unused]] const auto kBgfxLinkAnchor = &bgfx::getCaps; + +// Shared static vector returned from Get_Ambient(). Later work will replace +// this with real per-frame ambient tracking. +const Vector3 kZeroVec3(0.0f, 0.0f, 0.0f); +} + +BgfxBackend::BgfxBackend() +{ + std::fprintf(stderr, + "[BgfxBackend] stub backend constructed. " + "Most IRenderBackend methods are no-ops; the game will NOT " + "render correctly through this backend yet. " + "\n"); +} + +BgfxBackend::~BgfxBackend() +{ +} + +// -- Device state queries ---------------------------------------------------- + +bool BgfxBackend::Is_Device_Lost() const +{ + return false; +} + +bool BgfxBackend::Has_Stencil() +{ + return false; +} + +WW3DFormat BgfxBackend::Get_Back_Buffer_Format() +{ + return WW3D_FORMAT_UNKNOWN; +} + +SurfaceClass * BgfxBackend::Get_Back_Buffer(unsigned int /*num*/) +{ + return nullptr; +} + +void BgfxBackend::Set_Gamma(float /*gamma*/, float /*bright*/, float /*contrast*/, + bool /*calibrate*/, bool /*uselimit*/) +{ +} + +// -- Frame lifecycle --------------------------------------------------------- + +void BgfxBackend::Begin_Scene() +{ +} + +void BgfxBackend::End_Scene(bool /*flip_frame*/) +{ +} + +void BgfxBackend::Flip_To_Primary() +{ +} + +void BgfxBackend::Clear(bool /*clear_color*/, bool /*clear_z_stencil*/, + const Vector3 & /*color*/, + float /*dest_alpha*/, float /*z*/, unsigned int /*stencil*/) +{ +} + +void BgfxBackend::Set_Viewport(const RenderBackendViewport & /*viewport*/) +{ +} + +// -- Vertex / index buffers -------------------------------------------------- + +void BgfxBackend::Set_Vertex_Buffer(const VertexBufferClass * /*vb*/, unsigned int /*stream*/) +{ +} + +void BgfxBackend::Set_Vertex_Buffer(const DynamicVBAccessClass & /*vba*/) +{ +} + +void BgfxBackend::Set_Index_Buffer(const IndexBufferClass * /*ib*/, unsigned short /*index_base_offset*/) +{ +} + +void BgfxBackend::Set_Index_Buffer(const DynamicIBAccessClass & /*iba*/, unsigned short /*index_base_offset*/) +{ +} + +void BgfxBackend::Set_Index_Buffer_Index_Offset(unsigned int /*offset*/) +{ +} + +// -- State: shaders, materials, textures ------------------------------------ + +void BgfxBackend::Set_Shader(const ShaderClass & /*shader*/) +{ +} + +void BgfxBackend::Get_Shader(ShaderClass & /*shader*/) +{ +} + +void BgfxBackend::Set_Material(const VertexMaterialClass * /*material*/) +{ +} + +void BgfxBackend::Set_Texture(unsigned int /*stage*/, TextureBaseClass * /*texture*/) +{ +} + +void BgfxBackend::Apply_Render_State_Changes() +{ +} + +void BgfxBackend::Apply_Default_State() +{ +} + +void BgfxBackend::Invalidate_Cached_Render_States() +{ +} + +// -- Transforms -------------------------------------------------------------- + +void BgfxBackend::Set_Transform(TransformKind /*transform*/, const Matrix4x4 & /*m*/) +{ +} + +void BgfxBackend::Set_Transform(TransformKind /*transform*/, const Matrix3D & /*m*/) +{ +} + +void BgfxBackend::Get_Transform(TransformKind /*transform*/, Matrix4x4 & /*m*/) +{ +} + +void BgfxBackend::Set_World_Identity() +{ +} + +void BgfxBackend::Set_View_Identity() +{ +} + +bool BgfxBackend::Is_World_Identity() +{ + return true; +} + +bool BgfxBackend::Is_View_Identity() +{ + return true; +} + +void BgfxBackend::Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & /*matrix*/, + float /*znear*/, float /*zfar*/) +{ +} + +// -- Lighting and fog -------------------------------------------------------- + +void BgfxBackend::Set_Light(unsigned int /*index*/, const LightClass & /*light*/) +{ +} + +void BgfxBackend::Set_Ambient(const Vector3 & /*color*/) +{ +} + +const Vector3 & BgfxBackend::Get_Ambient() const +{ + return kZeroVec3; +} + +void BgfxBackend::Set_Fog(bool /*enable*/, const Vector3 & /*color*/, + float /*start*/, float /*end*/) +{ +} + +bool BgfxBackend::Get_Fog_Enable() const +{ + return false; +} + +void BgfxBackend::Set_Light_Environment(LightEnvironmentClass * /*light_env*/) +{ +} + +LightEnvironmentClass * BgfxBackend::Get_Light_Environment() const +{ + return nullptr; +} + +// -- Draw calls -------------------------------------------------------------- + +void BgfxBackend::Draw_Triangles(unsigned short /*start_index*/, + unsigned short /*polygon_count*/, + unsigned short /*min_vertex_index*/, + unsigned short /*vertex_count*/) +{ +} + +void BgfxBackend::Draw_Triangles(unsigned int /*buffer_type*/, + unsigned short /*start_index*/, + unsigned short /*polygon_count*/, + unsigned short /*min_vertex_index*/, + unsigned short /*vertex_count*/) +{ +} + +void BgfxBackend::Draw_Strip(unsigned short /*start_index*/, + unsigned short /*index_count*/, + unsigned short /*min_vertex_index*/, + unsigned short /*vertex_count*/) +{ +} + +// -- Programmable pipeline --------------------------------------------------- + +void BgfxBackend::Set_Vertex_Shader(unsigned long /*vertex_shader*/) +{ +} + +void BgfxBackend::Set_Pixel_Shader(unsigned long /*pixel_shader*/) +{ +} + +void BgfxBackend::Set_Vertex_Shader_Constant(int /*reg*/, const void * /*data*/, int /*count*/) +{ +} + +void BgfxBackend::Set_Pixel_Shader_Constant(int /*reg*/, const void * /*data*/, int /*count*/) +{ +} + +// -- Render targets ---------------------------------------------------------- + +TextureClass * BgfxBackend::Create_Render_Target(int /*width*/, int /*height*/, WW3DFormat /*format*/) +{ + return nullptr; +} + +void BgfxBackend::Set_Render_Target_With_Z(TextureClass * /*texture*/, ZTextureClass * /*ztexture*/) +{ +} + +bool BgfxBackend::Is_Render_To_Texture() +{ + return false; +} + +void BgfxBackend::Set_Shadow_Map(int /*idx*/, ZTextureClass * /*ztex*/) +{ +} + +ZTextureClass * BgfxBackend::Get_Shadow_Map(int /*idx*/) +{ + return nullptr; +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h new file mode 100644 index 00000000000..d95612eb02b --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h @@ -0,0 +1,127 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program 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, either version 3 of the License, or +** (at your option) any later version. +** +** This program 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. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +// TheSuperHackers @refactor bobtista 10/04/2026 BgfxBackend is the +// IRenderBackend implementation that targets bgfx (DX11 on Windows, +// Vulkan on Linux, Metal on macOS). This provides a stub shell that +// compiles and links against bgfx but implements every virtual method +// as a no-op. Later work fills in real implementations subsystem by +// subsystem as the engine is migrated off DX8Wrapper statics. +// +// This header MUST NOT be included from any VC6 translation unit. The +// VC6 build always uses DX8Backend; the bgfx backend requires MSVC 2022+ +// and C++17. See RENDER_BACKEND.md. + +#pragma once + +#include "IRenderBackend.h" + +class BgfxBackend : public IRenderBackend +{ +public: + BgfxBackend(); + virtual ~BgfxBackend(); + + // -- Device state queries ------------------------------------------------- + + virtual bool Is_Device_Lost() const; + virtual bool Has_Stencil(); + virtual WW3DFormat Get_Back_Buffer_Format(); + virtual SurfaceClass * Get_Back_Buffer(unsigned int num); + virtual void Set_Gamma(float gamma, float bright, float contrast, bool calibrate, bool uselimit); + + // -- Frame lifecycle ------------------------------------------------------ + + virtual void Begin_Scene(); + virtual void End_Scene(bool flip_frame); + virtual void Flip_To_Primary(); + virtual void Clear(bool clear_color, bool clear_z_stencil, + const Vector3 & color, + float dest_alpha, float z, unsigned int stencil); + virtual void Set_Viewport(const RenderBackendViewport & viewport); + + // -- Vertex / index buffers ----------------------------------------------- + + virtual void Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int stream); + virtual void Set_Vertex_Buffer(const DynamicVBAccessClass & vba); + virtual void Set_Index_Buffer(const IndexBufferClass * ib, unsigned short index_base_offset); + virtual void Set_Index_Buffer(const DynamicIBAccessClass & iba, unsigned short index_base_offset); + virtual void Set_Index_Buffer_Index_Offset(unsigned int offset); + + // -- State: shaders, materials, textures --------------------------------- + + virtual void Set_Shader(const ShaderClass & shader); + virtual void Get_Shader(ShaderClass & shader); + virtual void Set_Material(const VertexMaterialClass * material); + virtual void Set_Texture(unsigned int stage, TextureBaseClass * texture); + virtual void Apply_Render_State_Changes(); + virtual void Apply_Default_State(); + virtual void Invalidate_Cached_Render_States(); + + // -- Transforms ----------------------------------------------------------- + + virtual void Set_Transform(TransformKind transform, const Matrix4x4 & m); + virtual void Set_Transform(TransformKind transform, const Matrix3D & m); + virtual void Get_Transform(TransformKind transform, Matrix4x4 & m); + virtual void Set_World_Identity(); + virtual void Set_View_Identity(); + virtual bool Is_World_Identity(); + virtual bool Is_View_Identity(); + virtual void Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & matrix, float znear, float zfar); + + // -- Lighting and fog ----------------------------------------------------- + + virtual void Set_Light(unsigned int index, const LightClass & light); + virtual void Set_Ambient(const Vector3 & color); + virtual const Vector3 & Get_Ambient() const; + virtual void Set_Fog(bool enable, const Vector3 & color, float start, float end); + virtual bool Get_Fog_Enable() const; + virtual void Set_Light_Environment(LightEnvironmentClass * light_env); + virtual LightEnvironmentClass * Get_Light_Environment() const; + + // -- Draw calls ----------------------------------------------------------- + + virtual void Draw_Triangles(unsigned short start_index, + unsigned short polygon_count, + unsigned short min_vertex_index, + unsigned short vertex_count); + virtual void Draw_Triangles(unsigned int buffer_type, + unsigned short start_index, + unsigned short polygon_count, + unsigned short min_vertex_index, + unsigned short vertex_count); + virtual void Draw_Strip(unsigned short start_index, + unsigned short index_count, + unsigned short min_vertex_index, + unsigned short vertex_count); + + // -- Programmable pipeline ------------------------------------------------ + + virtual void Set_Vertex_Shader(unsigned long vertex_shader); + virtual void Set_Pixel_Shader(unsigned long pixel_shader); + virtual void Set_Vertex_Shader_Constant(int reg, const void * data, int count); + virtual void Set_Pixel_Shader_Constant(int reg, const void * data, int count); + + // -- Render targets ------------------------------------------------------- + + virtual TextureClass * Create_Render_Target(int width, int height, WW3DFormat format); + virtual void Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass * ztexture); + virtual bool Is_Render_To_Texture(); + virtual void Set_Shadow_Map(int idx, ZTextureClass * ztex); + virtual ZTextureClass * Get_Shadow_Map(int idx); +}; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt index ecebd564696..1c716e2e365 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt @@ -43,6 +43,8 @@ set(WW3D2_SRC distlod.cpp distlod.h dllist.h + DX8Backend.cpp + DX8Backend.h dx8caps.cpp dx8caps.h dx8fvf.cpp @@ -90,6 +92,7 @@ set(WW3D2_SRC htree.h #htreemgr.cpp #htreemgr.h + IRenderBackend.h intersec.cpp intersec.h intersec.inl @@ -155,6 +158,8 @@ set(WW3D2_SRC proto.h proxy.h rddesc.h + RenderBackend.cpp + RenderBackend.h #render2d.cpp #render2d.h render2dsentence.cpp @@ -237,6 +242,22 @@ add_library(corei_ww3d2 INTERFACE) target_sources(corei_ww3d2 INTERFACE ${WW3D2_SRC}) +# TheSuperHackers @refactor bobtista 10/04/2026 Conditionally include the +# active render backend's source files. See cmake/render-backend.cmake and +if(GGC_RENDER_BACKEND STREQUAL "bgfx") + target_sources(corei_ww3d2 INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/BgfxBackend.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/BgfxBackend.h + ) +endif() + +# Propagate the active backend's compile define to every translation unit +# that pulls in WW3D2, so that RenderBackend.cpp and any future callers can +# #if on GGC_RENDER_BACKEND_DX8 / _BGFX. +if(DEFINED GGC_RENDER_BACKEND_COMPILE_DEFINE) + target_compile_definitions(corei_ww3d2 INTERFACE ${GGC_RENDER_BACKEND_COMPILE_DEFINE}) +endif() + if (MSVC AND NOT IS_VS6_BUILD) target_link_libraries(corei_ww3d2 INTERFACE comsuppw @@ -248,3 +269,14 @@ target_link_libraries(corei_ww3d2 INTERFACE core_wwlib core_wwmath ) + +# Link the backend's native libraries into whoever consumes corei_ww3d2. +if(GGC_RENDER_BACKEND STREQUAL "bgfx") + target_link_libraries(corei_ww3d2 INTERFACE bgfx bx bimg) +elseif(GGC_RENDER_BACKEND STREQUAL "diligent") + target_link_libraries(corei_ww3d2 INTERFACE + Diligent-GraphicsEngineD3D11-static + Diligent-Common + Diligent-GraphicsTools + ) +endif() diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp new file mode 100644 index 00000000000..20c62d6b718 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp @@ -0,0 +1,321 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program 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, either version 3 of the License, or +** (at your option) any later version. +** +** This program 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. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +// TheSuperHackers @refactor bobtista 10/04/2026 DX8Backend forwarding adapter. +// Every method in this file is a one-line trampoline to the existing +// DX8Wrapper static API. Keep it that way — if behavior needs to change it +// should change in DX8Wrapper, not here. + +#include "DX8Backend.h" + +#include "dx8wrapper.h" +#include "vector3.h" +#include "matrix4.h" +#include "matrix3d.h" +#include "light.h" +#include "lightenvironment.h" + +DX8Backend::DX8Backend() +{ +} + +DX8Backend::~DX8Backend() +{ +} + +// -- Device state queries ---------------------------------------------------- + +bool DX8Backend::Is_Device_Lost() const +{ + return DX8Wrapper::Is_Device_Lost(); +} + +bool DX8Backend::Has_Stencil() +{ + return DX8Wrapper::Has_Stencil(); +} + +WW3DFormat DX8Backend::Get_Back_Buffer_Format() +{ + return DX8Wrapper::getBackBufferFormat(); +} + +SurfaceClass * DX8Backend::Get_Back_Buffer(unsigned int num) +{ + return DX8Wrapper::_Get_DX8_Back_Buffer(num); +} + +void DX8Backend::Set_Gamma(float gamma, float bright, float contrast, bool calibrate, bool uselimit) +{ + DX8Wrapper::Set_Gamma(gamma, bright, contrast, calibrate, uselimit); +} + +// -- Frame lifecycle --------------------------------------------------------- + +void DX8Backend::Begin_Scene() +{ + DX8Wrapper::Begin_Scene(); +} + +void DX8Backend::End_Scene(bool flip_frame) +{ + DX8Wrapper::End_Scene(flip_frame); +} + +void DX8Backend::Flip_To_Primary() +{ + DX8Wrapper::Flip_To_Primary(); +} + +void DX8Backend::Clear(bool clear_color, bool clear_z_stencil, + const Vector3 & color, + float dest_alpha, float z, unsigned int stencil) +{ + DX8Wrapper::Clear(clear_color, clear_z_stencil, color, dest_alpha, z, stencil); +} + +void DX8Backend::Set_Viewport(const RenderBackendViewport & viewport) +{ + D3DVIEWPORT8 vp; + vp.X = viewport.x; + vp.Y = viewport.y; + vp.Width = viewport.width; + vp.Height = viewport.height; + vp.MinZ = viewport.min_z; + vp.MaxZ = viewport.max_z; + DX8Wrapper::Set_Viewport(&vp); +} + +// -- Vertex / index buffers -------------------------------------------------- + +void DX8Backend::Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int stream) +{ + DX8Wrapper::Set_Vertex_Buffer(vb, stream); +} + +void DX8Backend::Set_Vertex_Buffer(const DynamicVBAccessClass & vba) +{ + DX8Wrapper::Set_Vertex_Buffer(vba); +} + +void DX8Backend::Set_Index_Buffer(const IndexBufferClass * ib, unsigned short index_base_offset) +{ + DX8Wrapper::Set_Index_Buffer(ib, index_base_offset); +} + +void DX8Backend::Set_Index_Buffer(const DynamicIBAccessClass & iba, unsigned short index_base_offset) +{ + DX8Wrapper::Set_Index_Buffer(iba, index_base_offset); +} + +void DX8Backend::Set_Index_Buffer_Index_Offset(unsigned int offset) +{ + DX8Wrapper::Set_Index_Buffer_Index_Offset(offset); +} + +// -- State: shaders, materials, textures ------------------------------------ + +void DX8Backend::Set_Shader(const ShaderClass & shader) +{ + DX8Wrapper::Set_Shader(shader); +} + +void DX8Backend::Get_Shader(ShaderClass & shader) +{ + DX8Wrapper::Get_Shader(shader); +} + +void DX8Backend::Set_Material(const VertexMaterialClass * material) +{ + DX8Wrapper::Set_Material(material); +} + +void DX8Backend::Set_Texture(unsigned int stage, TextureBaseClass * texture) +{ + DX8Wrapper::Set_Texture(stage, texture); +} + +void DX8Backend::Apply_Render_State_Changes() +{ + DX8Wrapper::Apply_Render_State_Changes(); +} + +void DX8Backend::Apply_Default_State() +{ + DX8Wrapper::Apply_Default_State(); +} + +void DX8Backend::Invalidate_Cached_Render_States() +{ + DX8Wrapper::Invalidate_Cached_Render_States(); +} + +// -- Transforms -------------------------------------------------------------- + +void DX8Backend::Set_Transform(TransformKind transform, const Matrix4x4 & m) +{ + DX8Wrapper::Set_Transform(static_cast(transform), m); +} + +void DX8Backend::Set_Transform(TransformKind transform, const Matrix3D & m) +{ + DX8Wrapper::Set_Transform(static_cast(transform), m); +} + +void DX8Backend::Get_Transform(TransformKind transform, Matrix4x4 & m) +{ + DX8Wrapper::Get_Transform(static_cast(transform), m); +} + +void DX8Backend::Set_World_Identity() +{ + DX8Wrapper::Set_World_Identity(); +} + +void DX8Backend::Set_View_Identity() +{ + DX8Wrapper::Set_View_Identity(); +} + +bool DX8Backend::Is_World_Identity() +{ + return DX8Wrapper::Is_World_Identity(); +} + +bool DX8Backend::Is_View_Identity() +{ + return DX8Wrapper::Is_View_Identity(); +} + +void DX8Backend::Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & matrix, float znear, float zfar) +{ + DX8Wrapper::Set_Projection_Transform_With_Z_Bias(matrix, znear, zfar); +} + +// -- Lighting and fog -------------------------------------------------------- + +void DX8Backend::Set_Light(unsigned int index, const LightClass & light) +{ + DX8Wrapper::Set_Light(index, light); +} + +void DX8Backend::Set_Ambient(const Vector3 & color) +{ + DX8Wrapper::Set_Ambient(color); +} + +const Vector3 & DX8Backend::Get_Ambient() const +{ + return DX8Wrapper::Get_Ambient(); +} + +void DX8Backend::Set_Fog(bool enable, const Vector3 & color, float start, float end) +{ + DX8Wrapper::Set_Fog(enable, color, start, end); +} + +bool DX8Backend::Get_Fog_Enable() const +{ + return DX8Wrapper::Get_Fog_Enable(); +} + +void DX8Backend::Set_Light_Environment(LightEnvironmentClass * light_env) +{ + DX8Wrapper::Set_Light_Environment(light_env); +} + +LightEnvironmentClass * DX8Backend::Get_Light_Environment() const +{ + return DX8Wrapper::Get_Light_Environment(); +} + +// -- Draw calls -------------------------------------------------------------- + +void DX8Backend::Draw_Triangles(unsigned short start_index, + unsigned short polygon_count, + unsigned short min_vertex_index, + unsigned short vertex_count) +{ + DX8Wrapper::Draw_Triangles(start_index, polygon_count, min_vertex_index, vertex_count); +} + +void DX8Backend::Draw_Triangles(unsigned int buffer_type, + unsigned short start_index, + unsigned short polygon_count, + unsigned short min_vertex_index, + unsigned short vertex_count) +{ + DX8Wrapper::Draw_Triangles(buffer_type, start_index, polygon_count, min_vertex_index, vertex_count); +} + +void DX8Backend::Draw_Strip(unsigned short start_index, + unsigned short index_count, + unsigned short min_vertex_index, + unsigned short vertex_count) +{ + DX8Wrapper::Draw_Strip(start_index, index_count, min_vertex_index, vertex_count); +} + +// -- Programmable pipeline --------------------------------------------------- + +void DX8Backend::Set_Vertex_Shader(unsigned long vertex_shader) +{ + DX8Wrapper::Set_Vertex_Shader(static_cast(vertex_shader)); +} + +void DX8Backend::Set_Pixel_Shader(unsigned long pixel_shader) +{ + DX8Wrapper::Set_Pixel_Shader(static_cast(pixel_shader)); +} + +void DX8Backend::Set_Vertex_Shader_Constant(int reg, const void * data, int count) +{ + DX8Wrapper::Set_Vertex_Shader_Constant(reg, data, count); +} + +void DX8Backend::Set_Pixel_Shader_Constant(int reg, const void * data, int count) +{ + DX8Wrapper::Set_Pixel_Shader_Constant(reg, data, count); +} + +// -- Render targets ---------------------------------------------------------- + +TextureClass * DX8Backend::Create_Render_Target(int width, int height, WW3DFormat format) +{ + return DX8Wrapper::Create_Render_Target(width, height, format); +} + +void DX8Backend::Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass * ztexture) +{ + DX8Wrapper::Set_Render_Target_With_Z(texture, ztexture); +} + +bool DX8Backend::Is_Render_To_Texture() +{ + return DX8Wrapper::Is_Render_To_Texture(); +} + +void DX8Backend::Set_Shadow_Map(int idx, ZTextureClass * ztex) +{ + DX8Wrapper::Set_Shadow_Map(idx, ztex); +} + +ZTextureClass * DX8Backend::Get_Shadow_Map(int idx) +{ + return DX8Wrapper::Get_Shadow_Map(idx); +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h new file mode 100644 index 00000000000..2f61e3da2dc --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h @@ -0,0 +1,123 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program 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, either version 3 of the License, or +** (at your option) any later version. +** +** This program 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. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +// TheSuperHackers @refactor bobtista 10/04/2026 DX8Backend is the reference +// implementation of IRenderBackend that forwards every virtual method to the +// existing DX8Wrapper static facade. It adds zero new rendering logic and +// performs zero behavior changes — it is pure adaptation so the rest of the +// engine can start talking to an IRenderBackend pointer while still running +// on the established DX8 path. See RENDER_BACKEND.md. + +#pragma once + +#include "IRenderBackend.h" + +class DX8Backend : public IRenderBackend +{ +public: + DX8Backend(); + virtual ~DX8Backend(); + + // -- Device state queries ------------------------------------------------- + + virtual bool Is_Device_Lost() const; + virtual bool Has_Stencil(); + virtual WW3DFormat Get_Back_Buffer_Format(); + virtual SurfaceClass * Get_Back_Buffer(unsigned int num); + virtual void Set_Gamma(float gamma, float bright, float contrast, bool calibrate, bool uselimit); + + // -- Frame lifecycle ------------------------------------------------------ + + virtual void Begin_Scene(); + virtual void End_Scene(bool flip_frame); + virtual void Flip_To_Primary(); + virtual void Clear(bool clear_color, bool clear_z_stencil, + const Vector3 & color, + float dest_alpha, float z, unsigned int stencil); + virtual void Set_Viewport(const RenderBackendViewport & viewport); + + // -- Vertex / index buffers ----------------------------------------------- + + virtual void Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int stream); + virtual void Set_Vertex_Buffer(const DynamicVBAccessClass & vba); + virtual void Set_Index_Buffer(const IndexBufferClass * ib, unsigned short index_base_offset); + virtual void Set_Index_Buffer(const DynamicIBAccessClass & iba, unsigned short index_base_offset); + virtual void Set_Index_Buffer_Index_Offset(unsigned int offset); + + // -- State: shaders, materials, textures --------------------------------- + + virtual void Set_Shader(const ShaderClass & shader); + virtual void Get_Shader(ShaderClass & shader); + virtual void Set_Material(const VertexMaterialClass * material); + virtual void Set_Texture(unsigned int stage, TextureBaseClass * texture); + virtual void Apply_Render_State_Changes(); + virtual void Apply_Default_State(); + virtual void Invalidate_Cached_Render_States(); + + // -- Transforms ----------------------------------------------------------- + + virtual void Set_Transform(TransformKind transform, const Matrix4x4 & m); + virtual void Set_Transform(TransformKind transform, const Matrix3D & m); + virtual void Get_Transform(TransformKind transform, Matrix4x4 & m); + virtual void Set_World_Identity(); + virtual void Set_View_Identity(); + virtual bool Is_World_Identity(); + virtual bool Is_View_Identity(); + virtual void Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & matrix, float znear, float zfar); + + // -- Lighting and fog ----------------------------------------------------- + + virtual void Set_Light(unsigned int index, const LightClass & light); + virtual void Set_Ambient(const Vector3 & color); + virtual const Vector3 & Get_Ambient() const; + virtual void Set_Fog(bool enable, const Vector3 & color, float start, float end); + virtual bool Get_Fog_Enable() const; + virtual void Set_Light_Environment(LightEnvironmentClass * light_env); + virtual LightEnvironmentClass * Get_Light_Environment() const; + + // -- Draw calls ----------------------------------------------------------- + + virtual void Draw_Triangles(unsigned short start_index, + unsigned short polygon_count, + unsigned short min_vertex_index, + unsigned short vertex_count); + virtual void Draw_Triangles(unsigned int buffer_type, + unsigned short start_index, + unsigned short polygon_count, + unsigned short min_vertex_index, + unsigned short vertex_count); + virtual void Draw_Strip(unsigned short start_index, + unsigned short index_count, + unsigned short min_vertex_index, + unsigned short vertex_count); + + // -- Programmable pipeline ------------------------------------------------ + + virtual void Set_Vertex_Shader(unsigned long vertex_shader); + virtual void Set_Pixel_Shader(unsigned long pixel_shader); + virtual void Set_Vertex_Shader_Constant(int reg, const void * data, int count); + virtual void Set_Pixel_Shader_Constant(int reg, const void * data, int count); + + // -- Render targets ------------------------------------------------------- + + virtual TextureClass * Create_Render_Target(int width, int height, WW3DFormat format); + virtual void Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass * ztexture); + virtual bool Is_Render_To_Texture(); + virtual void Set_Shadow_Map(int idx, ZTextureClass * ztex); + virtual ZTextureClass * Get_Shadow_Map(int idx); +}; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h new file mode 100644 index 00000000000..0451fd43de6 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h @@ -0,0 +1,217 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program 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, either version 3 of the License, or +** (at your option) any later version. +** +** This program 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. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +// TheSuperHackers @refactor bobtista 10/04/2026 Introduce IRenderBackend +// abstract interface so WW3D2 rendering can be re-targeted to modern backends +// (bgfx, etc.) while the existing DX8 path stays functional as the +// reference implementation. See Core/Libraries/Source/WWVegas/WW3D2/RENDER_BACKEND.md. + +#pragma once + +#include "ww3dformat.h" + +// ----------------------------------------------------------------------------- +// Forward declarations +// ----------------------------------------------------------------------------- + +class ShaderClass; +class VertexMaterialClass; +class TextureBaseClass; +class TextureClass; +class ZTextureClass; +class SurfaceClass; +class VertexBufferClass; +class IndexBufferClass; +class DynamicVBAccessClass; +class DynamicIBAccessClass; +class LightClass; +class LightEnvironmentClass; +class Matrix4x4; +class Matrix3D; +class Vector3; + +// ----------------------------------------------------------------------------- +// POD types owned by the interface +// ----------------------------------------------------------------------------- + +enum TransformKind +{ + // Values chosen so they can be mapped directly to D3DTS_* inside the + // DX8Backend without a branch. A modern backend ignores these indices + // and uses whichever matrix storage is convenient for it. + RB_TRANSFORM_VIEW = 2, // D3DTS_VIEW + RB_TRANSFORM_PROJECTION = 3, // D3DTS_PROJECTION + RB_TRANSFORM_WORLD = 256 // D3DTS_WORLD +}; + +struct RenderBackendViewport +{ + unsigned int x; + unsigned int y; + unsigned int width; + unsigned int height; + float min_z; + float max_z; +}; + +// ----------------------------------------------------------------------------- +// IRenderBackend — abstract W3D-facing rendering interface +// ----------------------------------------------------------------------------- +// +// This interface exposes the *high-level* subset of DX8Wrapper's public API: +// the calls that take and return W3D types (ShaderClass, TextureBaseClass, +// Matrix4x4, etc.) and are backend-neutral by construction. The low-level +// D3D8-specific entry points on DX8Wrapper (Set_DX8_Render_State, +// _Create_DX8_Texture, _Get_D3D_Device8, etc.) are NOT exposed here and +// remain reachable only through DX8Wrapper's static methods. Code that +// needs them is DX8-only and must be migrated during later phases. +// +// **Method names intentionally match the existing DX8Wrapper names** so +// that migrating callers is a mechanical `DX8Wrapper::X(...)` → +// `g_renderBackend->X(...)` rewrite with minimal diff noise. +// +// **This header is included from VC6-compiled translation units** (the +// DX8 reference path and the tools). Keep it C++98-compatible: +// - No , no , no or other STL in signatures +// - No `override`, `= default`, `= delete`, `auto`, `constexpr` +// - `nullptr` is OK (the project has a VC6 shim) +// - POD structs for parameter bundles +// - Forward-declare W3D types rather than including their headers +// +// Implementations (DX8Backend.cpp, future BgfxBackend.cpp, etc.) can use +// whatever C++ features the project's main build allows. + +class IRenderBackend +{ +public: + virtual ~IRenderBackend() {} + + // ------------------------------------------------------------------------- + // Device state queries + // ------------------------------------------------------------------------- + + virtual bool Is_Device_Lost() const = 0; + virtual bool Has_Stencil() = 0; + virtual WW3DFormat Get_Back_Buffer_Format() = 0; + virtual SurfaceClass * Get_Back_Buffer(unsigned int num) = 0; + virtual void Set_Gamma(float gamma, float bright, float contrast, bool calibrate, bool uselimit) = 0; + + // ------------------------------------------------------------------------- + // Frame lifecycle + // ------------------------------------------------------------------------- + + virtual void Begin_Scene() = 0; + virtual void End_Scene(bool flip_frame) = 0; + virtual void Flip_To_Primary() = 0; + virtual void Clear(bool clear_color, bool clear_z_stencil, + const Vector3 & color, + float dest_alpha, float z, unsigned int stencil) = 0; + virtual void Set_Viewport(const RenderBackendViewport & viewport) = 0; + + // ------------------------------------------------------------------------- + // Vertex / index buffers + // ------------------------------------------------------------------------- + + virtual void Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int stream) = 0; + virtual void Set_Vertex_Buffer(const DynamicVBAccessClass & vba) = 0; + virtual void Set_Index_Buffer(const IndexBufferClass * ib, unsigned short index_base_offset) = 0; + virtual void Set_Index_Buffer(const DynamicIBAccessClass & iba, unsigned short index_base_offset) = 0; + virtual void Set_Index_Buffer_Index_Offset(unsigned int offset) = 0; + + // ------------------------------------------------------------------------- + // State: shaders, materials, textures + // ------------------------------------------------------------------------- + + virtual void Set_Shader(const ShaderClass & shader) = 0; + virtual void Get_Shader(ShaderClass & shader) = 0; + virtual void Set_Material(const VertexMaterialClass * material) = 0; + virtual void Set_Texture(unsigned int stage, TextureBaseClass * texture) = 0; + + virtual void Apply_Render_State_Changes() = 0; + virtual void Apply_Default_State() = 0; + virtual void Invalidate_Cached_Render_States() = 0; + + // ------------------------------------------------------------------------- + // Transforms + // ------------------------------------------------------------------------- + + virtual void Set_Transform(TransformKind transform, const Matrix4x4 & m) = 0; + virtual void Set_Transform(TransformKind transform, const Matrix3D & m) = 0; + virtual void Get_Transform(TransformKind transform, Matrix4x4 & m) = 0; + virtual void Set_World_Identity() = 0; + virtual void Set_View_Identity() = 0; + virtual bool Is_World_Identity() = 0; + virtual bool Is_View_Identity() = 0; + virtual void Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & matrix, + float znear, float zfar) = 0; + + // ------------------------------------------------------------------------- + // Lighting and fog + // ------------------------------------------------------------------------- + + virtual void Set_Light(unsigned int index, const LightClass & light) = 0; + virtual void Set_Ambient(const Vector3 & color) = 0; + virtual const Vector3 & Get_Ambient() const = 0; + virtual void Set_Fog(bool enable, const Vector3 & color, float start, float end) = 0; + virtual bool Get_Fog_Enable() const = 0; + virtual void Set_Light_Environment(LightEnvironmentClass * light_env) = 0; + virtual LightEnvironmentClass * Get_Light_Environment() const = 0; + + // ------------------------------------------------------------------------- + // Draw calls + // ------------------------------------------------------------------------- + + virtual void Draw_Triangles(unsigned short start_index, + unsigned short polygon_count, + unsigned short min_vertex_index, + unsigned short vertex_count) = 0; + + virtual void Draw_Triangles(unsigned int buffer_type, + unsigned short start_index, + unsigned short polygon_count, + unsigned short min_vertex_index, + unsigned short vertex_count) = 0; + + virtual void Draw_Strip(unsigned short start_index, + unsigned short index_count, + unsigned short min_vertex_index, + unsigned short vertex_count) = 0; + + // ------------------------------------------------------------------------- + // Programmable pipeline (GPU vertex / pixel shaders) + // ------------------------------------------------------------------------- + // + // These correspond to DX8's programmable shader slots. Modern backends + // will re-interpret the handles internally; the interface treats the + // shader id as an opaque unsigned long. + + virtual void Set_Vertex_Shader(unsigned long vertex_shader) = 0; + virtual void Set_Pixel_Shader(unsigned long pixel_shader) = 0; + virtual void Set_Vertex_Shader_Constant(int reg, const void * data, int count) = 0; + virtual void Set_Pixel_Shader_Constant(int reg, const void * data, int count) = 0; + + // ------------------------------------------------------------------------- + // Render targets + // ------------------------------------------------------------------------- + + virtual TextureClass * Create_Render_Target(int width, int height, WW3DFormat format) = 0; + virtual void Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass * ztexture) = 0; + virtual bool Is_Render_To_Texture() = 0; + virtual void Set_Shadow_Map(int idx, ZTextureClass * ztex) = 0; + virtual ZTextureClass * Get_Shadow_Map(int idx) = 0; +}; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/RenderBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/RenderBackend.cpp new file mode 100644 index 00000000000..db66eb358a1 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/RenderBackend.cpp @@ -0,0 +1,65 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program 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, either version 3 of the License, or +** (at your option) any later version. +** +** This program 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. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +// TheSuperHackers @refactor bobtista 10/04/2026 Render backend global +// owner. Holds the single g_renderBackend pointer and constructs/destroys +// the concrete backend instance. +// +// The concrete backend is selected at compile time via the GGC_RENDER_BACKEND +// CMake flag which sets one of: +// GGC_RENDER_BACKEND_DX8 - DirectX 8 (default, VC6-compatible, Windows only) +// GGC_RENDER_BACKEND_BGFX - bgfx (DX11/Vulkan/Metal/GL, cross-platform) +// +// Exactly one of these is defined in any given build. If none are defined +// (a legacy build that hasn't included render-backend.cmake) we default to +// DX8 so the legacy path keeps working unchanged. +// +// See RENDER_BACKEND.md. + +#include "RenderBackend.h" + +#if defined(GGC_RENDER_BACKEND_BGFX) +#include "BgfxBackend.h" +#else +#include "DX8Backend.h" +#endif + +IRenderBackend * g_renderBackend = nullptr; + +void Init_Render_Backend() +{ + if (g_renderBackend != nullptr) + { + return; + } +#if defined(GGC_RENDER_BACKEND_BGFX) + g_renderBackend = new BgfxBackend(); +#else + g_renderBackend = new DX8Backend(); +#endif +} + +void Shutdown_Render_Backend() +{ + if (g_renderBackend == nullptr) + { + return; + } + delete g_renderBackend; + g_renderBackend = nullptr; +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/RenderBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/RenderBackend.h new file mode 100644 index 00000000000..4f2f1022cd3 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/RenderBackend.h @@ -0,0 +1,42 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program 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, either version 3 of the License, or +** (at your option) any later version. +** +** This program 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. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +// TheSuperHackers @refactor bobtista 10/04/2026 Backend-agnostic access point +// for the global IRenderBackend instance. Engine-side code should include +// this header (not IRenderBackend.h or DX8Backend.h directly) to use the +// render backend. See RENDER_BACKEND.md. + +#pragma once + +#include "IRenderBackend.h" + +// The active rendering backend. Set by Init_Render_Backend() during +// WW3D device initialization and cleared by Shutdown_Render_Backend() +// during device teardown. Never null between those two calls. +extern IRenderBackend * g_renderBackend; + +// Create the render backend. Called by DX8Wrapper::Do_Onetime_Device_Dependent_Inits +// after the D3D device has been successfully created. +// +// Always creates a DX8Backend for now. A compile-time +// option to select between DX8Backend and BgfxBackend. +void Init_Render_Backend(); + +// Destroy the render backend. Called by DX8Wrapper::Do_Onetime_Device_Dependent_Shutdowns +// before the D3D device is released. +void Shutdown_Render_Backend(); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp index bc2a6eeeeca..c97158ff443 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp @@ -56,6 +56,7 @@ #include "dx8vertexbuffer.h" #include "dx8indexbuffer.h" #include "dx8renderer.h" +#include "RenderBackend.h" #include "WW3D2/ww3d.h" #include "WW3D2/camera.h" #include "WWLib/wwstring.h" @@ -390,6 +391,11 @@ void DX8Wrapper::Do_Onetime_Device_Dependent_Inits() TextureLoader::Init(); Set_Default_Global_Render_States(); + + // TheSuperHackers @refactor bobtista 10/04/2026 Construct the global + // IRenderBackend instance now that the D3D device is ready. See + // Core/Libraries/Source/WWVegas/WW3D2/RENDER_BACKEND.md. + Init_Render_Backend(); } inline DWORD F2DW(float f) { return *((unsigned*)&f); } @@ -450,6 +456,11 @@ void DX8Wrapper::Invalidate_Cached_Render_States() void DX8Wrapper::Do_Onetime_Device_Dependent_Shutdowns() { + // TheSuperHackers @refactor bobtista 10/04/2026 Tear down the render + // backend before the D3D device is released so any backend-owned + // resources get released first. See RENDER_BACKEND.md. + Shutdown_Render_Backend(); + /* ** Shutdown ww3d systems */ diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DStatusCircle.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DStatusCircle.cpp index 5ac13989f8d..94948fbdb97 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DStatusCircle.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DStatusCircle.cpp @@ -33,6 +33,7 @@ #include #include #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/shader.h" #include "Common/GlobalData.h" #include "Common/MapObject.h" @@ -317,12 +318,19 @@ void W3DStatusCircle::Render(RenderInfoClass & rinfo) if (m_needUpdate) { updateCircleVB(); } + // TheSuperHackers @refactor bobtista 10/04/2026 First IRenderBackend call site + // migrated to IRenderBackend. High-level W3D-facing calls route through + // the global backend pointer; the remaining raw D3DRS_* state calls + // below still go through DX8Wrapper:: directly because IRenderBackend + // does not expose the low-level D3D8 state API yet. + // See Core/Libraries/Source/WWVegas/WW3D2/RENDER_BACKEND.md. + //Apply the shader and material - DX8Wrapper::Set_Material(m_vertexMaterialClass); - DX8Wrapper::Set_Shader(m_shaderClass); - DX8Wrapper::Set_Texture(0, nullptr); - DX8Wrapper::Set_Index_Buffer(m_indexBuffer,0); - DX8Wrapper::Set_Vertex_Buffer(m_vertexBufferCircle); + g_renderBackend->Set_Material(m_vertexMaterialClass); + g_renderBackend->Set_Shader(m_shaderClass); + g_renderBackend->Set_Texture(0, nullptr); + g_renderBackend->Set_Index_Buffer(m_indexBuffer,0); + g_renderBackend->Set_Vertex_Buffer(m_vertexBufferCircle, 0); setIndex = true; Vector3 vec(0.95f, 0.67f, 0); @@ -330,8 +338,8 @@ void W3DStatusCircle::Render(RenderInfoClass & rinfo) tm.Set_Translation(vec); - DX8Wrapper::Set_Transform(D3DTS_WORLD,tm); - DX8Wrapper::Draw_Triangles( 0,NUM_TRI, 0, (m_numTriangles*3)); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,tm); + g_renderBackend->Draw_Triangles( 0,NUM_TRI, 0, (m_numTriangles*3)); } @@ -341,9 +349,9 @@ void W3DStatusCircle::Render(RenderInfoClass & rinfo) } if (!setIndex) { - DX8Wrapper::Set_Material(m_vertexMaterialClass); - DX8Wrapper::Set_Index_Buffer(m_indexBuffer,0); - DX8Wrapper::Set_Texture(0, nullptr); + g_renderBackend->Set_Material(m_vertexMaterialClass); + g_renderBackend->Set_Index_Buffer(m_indexBuffer,0); + g_renderBackend->Set_Texture(0, nullptr); } tm.Make_Identity(); @@ -351,32 +359,33 @@ void W3DStatusCircle::Render(RenderInfoClass & rinfo) Int clr = 255*intensity; Int diffuse = (0xff<<24)|(clr<<16)|(clr<<8)|clr; // b g<<8 r<<16 a<<24. updateScreenVB(diffuse); - DX8Wrapper::Set_Transform(D3DTS_WORLD,tm); - DX8Wrapper::Set_Shader(ShaderClass(SC_ADD)); - DX8Wrapper::Set_Vertex_Buffer(m_vertexBufferScreen); - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,tm); + g_renderBackend->Set_Shader(ShaderClass(SC_ADD)); + g_renderBackend->Set_Vertex_Buffer(m_vertexBufferScreen, 0); + g_renderBackend->Apply_Render_State_Changes(); switch (fade) { default: case ScriptEngine::FADE_ADD: - DX8Wrapper::Draw_Triangles( 0,2, 0, (2*3)); + g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); break; case ScriptEngine::FADE_SUBTRACT: + // Low-level blend op not yet abstracted by IRenderBackend (D3DRS_* skipped). DX8Wrapper::Set_DX8_Render_State(D3DRS_BLENDOP, D3DBLENDOP_REVSUBTRACT ); - DX8Wrapper::Draw_Triangles( 0,2, 0, (2*3)); + g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); DX8Wrapper::Set_DX8_Render_State(D3DRS_BLENDOP, D3DBLENDOP_ADD ); break; case ScriptEngine::FADE_SATURATE: // 4x multiply DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_DESTCOLOR); DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_SRCCOLOR); - DX8Wrapper::Draw_Triangles( 0,2, 0, (2*3)); - DX8Wrapper::Draw_Triangles( 0,2, 0, (2*3)); + g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); + g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); break; case ScriptEngine::FADE_MULTIPLY: // Straight multiply DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_ZERO); DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_SRCCOLOR); - DX8Wrapper::Draw_Triangles( 0,2, 0, (2*3)); + g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); break; } ShaderClass::Invalidate(); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DStatusCircle.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DStatusCircle.cpp index 796279e802f..a0d7fef13e3 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DStatusCircle.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DStatusCircle.cpp @@ -34,6 +34,7 @@ #include #include #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/shader.h" #include "Common/GlobalData.h" #include "Common/MapObject.h" @@ -316,12 +317,19 @@ void W3DStatusCircle::Render(RenderInfoClass & rinfo) if (m_needUpdate) { updateCircleVB(); } + // TheSuperHackers @refactor bobtista 10/04/2026 First IRenderBackend call site + // migrated to IRenderBackend. High-level W3D-facing calls route through + // the global backend pointer; the remaining raw D3DRS_* state calls + // below still go through DX8Wrapper:: directly because IRenderBackend + // does not expose the low-level D3D8 state API yet. + // See Core/Libraries/Source/WWVegas/WW3D2/RENDER_BACKEND.md. + //Apply the shader and material - DX8Wrapper::Set_Material(m_vertexMaterialClass); - DX8Wrapper::Set_Shader(m_shaderClass); - DX8Wrapper::Set_Texture(0, nullptr); - DX8Wrapper::Set_Index_Buffer(m_indexBuffer,0); - DX8Wrapper::Set_Vertex_Buffer(m_vertexBufferCircle); + g_renderBackend->Set_Material(m_vertexMaterialClass); + g_renderBackend->Set_Shader(m_shaderClass); + g_renderBackend->Set_Texture(0, nullptr); + g_renderBackend->Set_Index_Buffer(m_indexBuffer,0); + g_renderBackend->Set_Vertex_Buffer(m_vertexBufferCircle, 0); setIndex = true; Vector3 vec(0.95f, 0.67f, 0); @@ -329,8 +337,8 @@ void W3DStatusCircle::Render(RenderInfoClass & rinfo) tm.Set_Translation(vec); - DX8Wrapper::Set_Transform(D3DTS_WORLD,tm); - DX8Wrapper::Draw_Triangles( 0,NUM_TRI, 0, (m_numTriangles*3)); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,tm); + g_renderBackend->Draw_Triangles( 0,NUM_TRI, 0, (m_numTriangles*3)); } @@ -340,9 +348,9 @@ void W3DStatusCircle::Render(RenderInfoClass & rinfo) } if (!setIndex) { - DX8Wrapper::Set_Material(m_vertexMaterialClass); - DX8Wrapper::Set_Index_Buffer(m_indexBuffer,0); - DX8Wrapper::Set_Texture(0, nullptr); + g_renderBackend->Set_Material(m_vertexMaterialClass); + g_renderBackend->Set_Index_Buffer(m_indexBuffer,0); + g_renderBackend->Set_Texture(0, nullptr); } tm.Make_Identity(); @@ -350,32 +358,33 @@ void W3DStatusCircle::Render(RenderInfoClass & rinfo) Int clr = 255*intensity; Int diffuse = (0xff<<24)|(clr<<16)|(clr<<8)|clr; // b g<<8 r<<16 a<<24. updateScreenVB(diffuse); - DX8Wrapper::Set_Transform(D3DTS_WORLD,tm); - DX8Wrapper::Set_Shader(ShaderClass(SC_ADD)); - DX8Wrapper::Set_Vertex_Buffer(m_vertexBufferScreen); - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,tm); + g_renderBackend->Set_Shader(ShaderClass(SC_ADD)); + g_renderBackend->Set_Vertex_Buffer(m_vertexBufferScreen, 0); + g_renderBackend->Apply_Render_State_Changes(); switch (fade) { default: case ScriptEngine::FADE_ADD: - DX8Wrapper::Draw_Triangles( 0,2, 0, (2*3)); + g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); break; case ScriptEngine::FADE_SUBTRACT: + // Low-level blend op not yet abstracted by IRenderBackend (D3DRS_* skipped). DX8Wrapper::Set_DX8_Render_State(D3DRS_BLENDOP, D3DBLENDOP_REVSUBTRACT ); - DX8Wrapper::Draw_Triangles( 0,2, 0, (2*3)); + g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); DX8Wrapper::Set_DX8_Render_State(D3DRS_BLENDOP, D3DBLENDOP_ADD ); break; case ScriptEngine::FADE_SATURATE: // 4x multiply DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_DESTCOLOR); DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_SRCCOLOR); - DX8Wrapper::Draw_Triangles( 0,2, 0, (2*3)); - DX8Wrapper::Draw_Triangles( 0,2, 0, (2*3)); + g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); + g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); break; case ScriptEngine::FADE_MULTIPLY: // Straight multiply DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_ZERO); DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_SRCCOLOR); - DX8Wrapper::Draw_Triangles( 0,2, 0, (2*3)); + g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); break; } ShaderClass::Invalidate(); diff --git a/cmake/bgfx.cmake b/cmake/bgfx.cmake new file mode 100644 index 00000000000..63fd3b6f3c0 --- /dev/null +++ b/cmake/bgfx.cmake @@ -0,0 +1,39 @@ +# cmake/bgfx.cmake +# +# TheSuperHackers @refactor bobtista 10/04/2026 bgfx dependency for the +# GGC_RENDER_BACKEND=bgfx build. Included from cmake/render-backend.cmake. +# +# Pulls in bgfx via the community bgfx.cmake wrapper, which internally +# fetches bgfx/bx/bimg as git submodules. We pin a specific bgfx.cmake +# SHA for reproducibility. +# +# This file is NOT included when GGC_RENDER_BACKEND is dx8. + +# Disable bgfx features we don't need. These must be set BEFORE +# FetchContent_MakeAvailable so bgfx.cmake picks them up at configure time. +set(BGFX_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) +set(BGFX_BUILD_TESTS OFF CACHE BOOL "" FORCE) +set(BGFX_BUILD_TOOLS ON CACHE BOOL "" FORCE) # shaderc is mandatory +set(BGFX_BUILD_TOOLS_BIN2C ON CACHE BOOL "" FORCE) +set(BGFX_BUILD_TOOLS_SHADER ON CACHE BOOL "" FORCE) +set(BGFX_BUILD_TOOLS_GEOMETRY OFF CACHE BOOL "" FORCE) +set(BGFX_BUILD_TOOLS_TEXTURE OFF CACHE BOOL "" FORCE) +set(BGFX_INSTALL OFF CACHE BOOL "" FORCE) +set(BGFX_CUSTOM_TARGETS OFF CACHE BOOL "" FORCE) + +FetchContent_Declare( + bgfx_cmake + GIT_REPOSITORY https://github.com/bkaradzic/bgfx.cmake.git + GIT_TAG 668550dc7c47c71860a39c5ef4c162e79294c93f + # Nested submodules (bgfx, bx, bimg) are cloned recursively by FetchContent. + GIT_SUBMODULES_RECURSE TRUE +) + +FetchContent_MakeAvailable(bgfx_cmake) + +# IDE organization. +foreach(_t bgfx bx bimg shaderc bimg_decode bimg_encode) + if(TARGET ${_t}) + set_target_properties(${_t} PROPERTIES FOLDER "Dependencies/bgfx") + endif() +endforeach() diff --git a/cmake/render-backend.cmake b/cmake/render-backend.cmake new file mode 100644 index 00000000000..06e87a959e5 --- /dev/null +++ b/cmake/render-backend.cmake @@ -0,0 +1,59 @@ +# cmake/render-backend.cmake +# +# TheSuperHackers @refactor bobtista 10/04/2026 Selects the rendering backend +# for WW3D2 at configure time. See Core/Libraries/Source/WWVegas/WW3D2/RENDER_BACKEND.md +# +# Valid values: +# dx8 - existing DirectX 8 backend. Default. VC6-compatible. Windows only. +# bgfx - bgfx abstraction over DX11/Vulkan/Metal/GL. Cross-platform. MSVC 2022+. +# +# When set to bgfx the dependency module is included from cmake/bgfx.cmake. +# It is not fetched when dx8 is selected. +# +# This file must be included from the top-level CMakeLists.txt after the +# project() call but before the WW3D2 source subdirectories are added. + +set(GGC_RENDER_BACKEND "dx8" CACHE STRING + "Rendering backend for WW3D2: dx8 (default) or bgfx") +set_property(CACHE GGC_RENDER_BACKEND PROPERTY STRINGS dx8 bgfx) + +if(NOT GGC_RENDER_BACKEND STREQUAL "dx8" AND + NOT GGC_RENDER_BACKEND STREQUAL "bgfx") + message(FATAL_ERROR + "Invalid GGC_RENDER_BACKEND: '${GGC_RENDER_BACKEND}'. " + "Must be one of: dx8, bgfx.") +endif() + +message(STATUS "WW3D2 render backend: ${GGC_RENDER_BACKEND}") + +# Non-DX8 backends require a modern toolchain. We do NOT want to silently +# pick a broken build configuration, so fail early with a useful message. +if(NOT GGC_RENDER_BACKEND STREQUAL "dx8") + if(IS_VS6_BUILD) + message(FATAL_ERROR + "GGC_RENDER_BACKEND=${GGC_RENDER_BACKEND} requires a modern C++ toolchain " + "(MSVC 2022 or Clang 11+). VC6 builds must use GGC_RENDER_BACKEND=dx8.") + endif() + if(NOT WIN32) + message(WARNING + "GGC_RENDER_BACKEND=${GGC_RENDER_BACKEND} is being configured on a non-Windows host. " + "Cross-platform build targets will land later; for now expect compile failures " + "outside Windows.") + endif() +endif() + +# Expose the selection as a compile definition so downstream code can do +# #if defined(GGC_RENDER_BACKEND_BGFX) +# without coupling to the raw string variable. +if(GGC_RENDER_BACKEND STREQUAL "dx8") + set(GGC_RENDER_BACKEND_COMPILE_DEFINE "GGC_RENDER_BACKEND_DX8=1") +elseif(GGC_RENDER_BACKEND STREQUAL "bgfx") + set(GGC_RENDER_BACKEND_COMPILE_DEFINE "GGC_RENDER_BACKEND_BGFX=1") +endif() + +# Pull in the backend's dependency module if it has one. dx8 has no module +# here because cmake/dx8.cmake is already included from the top-level +# CMakeLists.txt unconditionally for the min-dx8-sdk. +if(GGC_RENDER_BACKEND STREQUAL "bgfx") + include(cmake/bgfx.cmake) +endif() From cf12396eae633e9d2416361c96b9d42c006f19a3 Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 21 Apr 2026 14:02:46 -0400 Subject: [PATCH 038/523] refactor(ww3d): route W3D subsystems and shadow/scene callers through g_renderBackend, extend IRenderBackend with blend/stencil/cursor/alpha state (cherry picked from commit 549a32ea97a71a9e7c9577416be929df945f29b5) --- CMakePresets.json | 8 + .../W3DDevice/GameClient/FlatHeightMap.cpp | 47 +++--- .../Source/W3DDevice/GameClient/W3DMouse.cpp | 41 +++-- .../W3DDevice/GameClient/W3DTerrainTracks.cpp | 19 ++- .../Source/WWVegas/WW3D2/BgfxBackend.cpp | 60 ++++++++ .../Source/WWVegas/WW3D2/BgfxBackend.h | 15 ++ .../Source/WWVegas/WW3D2/DX8Backend.cpp | 108 ++++++++++++++ .../Source/WWVegas/WW3D2/DX8Backend.h | 15 ++ .../Source/WWVegas/WW3D2/IRenderBackend.h | 96 +++++++++++- .../Source/WWVegas/WW3D2/dx8wrapper.cpp | 1 + .../Include/W3DDevice/GameClient/W3DShroud.h | 6 +- .../GameClient/Shadow/W3DProjectedShadow.cpp | 50 ++++--- .../GameClient/Shadow/W3DVolumetricShadow.cpp | 38 +++-- .../W3DDevice/GameClient/W3DBibBuffer.cpp | 17 ++- .../W3DDevice/GameClient/W3DBridgeBuffer.cpp | 33 +++-- .../W3DDevice/GameClient/W3DDebugIcons.cpp | 21 +-- .../W3DDevice/GameClient/W3DInGameUI.cpp | 17 ++- .../W3DDevice/GameClient/W3DRoadBuffer.cpp | 27 ++-- .../Source/W3DDevice/GameClient/W3DScene.cpp | 131 ++++++++-------- .../Source/W3DDevice/GameClient/W3DShroud.cpp | 86 ++++++----- .../W3DDevice/GameClient/W3DStatusCircle.cpp | 21 ++- .../Include/W3DDevice/GameClient/W3DShroud.h | 6 +- .../GameClient/Shadow/W3DProjectedShadow.cpp | 51 ++++--- .../GameClient/Shadow/W3DVolumetricShadow.cpp | 41 +++-- .../W3DDevice/GameClient/W3DBibBuffer.cpp | 17 ++- .../W3DDevice/GameClient/W3DBridgeBuffer.cpp | 33 +++-- .../W3DDevice/GameClient/W3DDebugIcons.cpp | 21 +-- .../W3DDevice/GameClient/W3DInGameUI.cpp | 17 ++- .../W3DDevice/GameClient/W3DRoadBuffer.cpp | 25 ++-- .../Source/W3DDevice/GameClient/W3DScene.cpp | 140 +++++++++--------- .../Source/W3DDevice/GameClient/W3DShroud.cpp | 86 ++++++----- .../W3DDevice/GameClient/W3DStatusCircle.cpp | 21 ++- 32 files changed, 880 insertions(+), 435 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 282117cbfff..95f8d6bc709 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -122,6 +122,14 @@ "RTS_BUILD_OPTION_DEBUG": "ON" } }, + { + "name": "win32-debug-bgfx", + "inherits": "win32-debug", + "displayName": "Windows 32bit Debug (bgfx stub backend)", + "cacheVariables": { + "GGC_RENDER_BACKEND": "bgfx" + } + }, { "name": "win32-vcpkg", "inherits": "default-vcpkg", diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/FlatHeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/FlatHeightMap.cpp index c2daacdf927..1d793e82a6d 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/FlatHeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/FlatHeightMap.cpp @@ -83,6 +83,7 @@ #include "W3DDevice/GameClient/W3DWater.h" #include "W3DDevice/GameClient/W3DShroud.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/light.h" #include "WW3D2/scene.h" #include "W3DDevice/GameClient/W3DPoly.h" @@ -487,20 +488,22 @@ void FlatHeightMapRenderObjClass::Render(RenderInfoClass & rinfo) } #endif - DX8Wrapper::Set_Light_Environment(rinfo.light_environment); + // TheSuperHackers @refactor bobtista 10/04/2026 Route high-level calls + // through the IRenderBackend abstraction. + g_renderBackend->Set_Light_Environment(rinfo.light_environment); // Force shaders to update. m_stageTwoTexture->restore(); - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Set_Texture(1,nullptr); + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Set_Texture(1,nullptr); ShaderClass::Invalidate(); // tm.Scale(ObjSpaceExtent); - DX8Wrapper::Set_Transform(D3DTS_WORLD,tm); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,tm); - DX8Wrapper::Set_Material(m_vertexMaterialClass); - DX8Wrapper::Set_Shader(m_shaderClass); + g_renderBackend->Set_Material(m_vertexMaterialClass); + g_renderBackend->Set_Shader(m_shaderClass); st=W3DShaderManager::ST_FLAT_TERRAIN_BASE; //set default shader @@ -538,8 +541,8 @@ void FlatHeightMapRenderObjClass::Render(RenderInfoClass & rinfo) W3DShaderManager::setTexture(2,m_stageTwoTexture); //cloud W3DShaderManager::setTexture(3,m_stageThreeTexture);//noise //Disable writes to destination alpha channel (if there is one) - if (DX8Wrapper::getBackBufferFormat() == WW3D_FORMAT_A8R8G8B8) { - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_BLUE|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_RED); + if (g_renderBackend->Get_Back_Buffer_Format() == WW3D_FORMAT_A8R8G8B8) { + g_renderBackend->Set_Color_Write_Enable(true, true, true, false); } Int pass; @@ -550,8 +553,8 @@ void FlatHeightMapRenderObjClass::Render(RenderInfoClass & rinfo) for (pass=0; passSet_Shader(ShaderClass::_PresetOpaque2DShader); + g_renderBackend->Set_Texture(0,nullptr); } else { W3DShaderManager::setShader(st, pass); } @@ -590,13 +593,13 @@ void FlatHeightMapRenderObjClass::Render(RenderInfoClass & rinfo) renderShoreLines(&rinfo.Camera); #ifdef DO_ROADS - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Set_Texture(1,nullptr); + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Set_Texture(1,nullptr); m_stageTwoTexture->restore(); ShaderClass::Invalidate(); if (!ShaderClass::Is_Backface_Culling_Inverted()) { - DX8Wrapper::Set_Material(m_vertexMaterialClass); + g_renderBackend->Set_Material(m_vertexMaterialClass); if (Scene) { RTS3DScene *pMyScene = (RTS3DScene *)Scene; RefRenderObjListIterator pDynamicLightsIterator(pMyScene->getDynamicLights()); @@ -607,8 +610,8 @@ void FlatHeightMapRenderObjClass::Render(RenderInfoClass & rinfo) #endif #ifdef DO_SCORCH - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Set_Texture(1,nullptr); + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Set_Texture(1,nullptr); m_stageTwoTexture->restore(); ShaderClass::Invalidate(); @@ -616,11 +619,11 @@ void FlatHeightMapRenderObjClass::Render(RenderInfoClass & rinfo) drawScorches(); } #endif - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Set_Texture(1,nullptr); + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Set_Texture(1,nullptr); m_stageTwoTexture->restore(); ShaderClass::Invalidate(); - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); m_bridgeBuffer->drawBridges(&rinfo.Camera, m_disableTextures, m_stageTwoTexture); @@ -628,18 +631,18 @@ void FlatHeightMapRenderObjClass::Render(RenderInfoClass & rinfo) TheTerrainTracksRenderObjClassSystem->flush(); ShaderClass::Invalidate(); - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); m_waypointBuffer->drawWaypoints(rinfo); m_bibBuffer->renderBibs(); #endif // We do some custom blending, so tell the shader class to reset everything. - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Set_Texture(1,nullptr); + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Set_Texture(1,nullptr); m_stageTwoTexture->restore(); ShaderClass::Invalidate(); - DX8Wrapper::Set_Material(nullptr); + g_renderBackend->Set_Material(nullptr); } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DMouse.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DMouse.cpp index 814bb83cce5..ea5fcc2c1f4 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DMouse.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DMouse.cpp @@ -30,6 +30,7 @@ #include "Common/GameMemory.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/rendobj.h" #include "WW3D2/hanim.h" #include "WW3D2/camera.h" @@ -108,11 +109,13 @@ W3DMouse::W3DMouse() W3DMouse::~W3DMouse() { - LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); - - if (m_pDev) + // TheSuperHackers @refactor bobtista 10/04/2026 Route the + // hardware cursor hide through the IRenderBackend cursor API instead + // of touching IDirect3DDevice8 directly. Null guard + // is defensive against unusual destruction orderings. + if (g_renderBackend != nullptr) { - m_pDev->ShowCursor(FALSE); //kill DX8 cursor + g_renderBackend->Show_Hardware_Cursor(false); //kill DX8 cursor Win32Mouse::setCursor(ARROW); //enable default windows cursor } @@ -389,12 +392,15 @@ void W3DMouse::setCursor( MouseCursor cursor ) { SetCursor(nullptr); //Kill Windows Cursor - LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); + // TheSuperHackers @refactor bobtista 10/04/2026 Route all + // hardware cursor calls through the IRenderBackend cursor API. The + // previous code grabbed IDirect3DDevice8 directly via DX8Wrapper. + // Bool doImageChange=FALSE; - if (m_pDev != nullptr) + if (g_renderBackend != nullptr) { - m_pDev->ShowCursor(FALSE); //disable DX8 cursor + g_renderBackend->Show_Hardware_Cursor(false); //disable DX8 cursor if (cursor != m_currentD3DCursor) { if (!isThread) { releaseD3DCursorTextures(m_currentD3DCursor); @@ -410,12 +416,13 @@ void W3DMouse::setCursor( MouseCursor cursor ) //it didn't change. This is needed to prevent the cursor from flickering. if (doImageChange) { - HRESULT res; m_currentHotSpot = m_cursorInfo[cursor].hotSpotPosition; m_currentFMS = m_cursorInfo[cursor].fps/1000.0f; m_currentAnimFrame = 0; //reset animation when cursor changes - res = m_pDev->SetCursorProperties(m_currentHotSpot.x,m_currentHotSpot.y,m_currentD3DSurface[(Int)m_currentAnimFrame]->Peek_D3D_Surface()); - m_pDev->ShowCursor(TRUE); //Enable DX8 cursor + g_renderBackend->Set_Hardware_Cursor_Image( + m_currentHotSpot.x, m_currentHotSpot.y, + m_currentD3DSurface[(Int)m_currentAnimFrame]); + g_renderBackend->Show_Hardware_Cursor(true); //Enable DX8 cursor m_currentD3DFrame=(Int)m_currentAnimFrame; m_currentD3DCursor = cursor; m_lastAnimTime=timeGetTime(); @@ -483,11 +490,13 @@ void W3DMouse::draw() if (m_currentRedrawMode == RM_DX8 && m_currentD3DCursor != NONE) { + // TheSuperHackers @refactor bobtista 10/04/2026 Route the + // per-frame cursor positioning + animation through the IRenderBackend + // cursor API. //called from update thread or rendering loop. Tells D3D where //to draw the mouse cursor. - LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); - if (m_pDev) - { m_pDev->ShowCursor(TRUE); //Enable DX8 cursor + if (g_renderBackend != nullptr) + { g_renderBackend->Show_Hardware_Cursor(true); //Enable DX8 cursor if (TheDisplay && !TheDisplay->getWindowed()) { //if we're full-screen, need to manually move cursor image @@ -495,7 +504,7 @@ void W3DMouse::draw() GetCursorPos( &ptCursor ); ScreenToClient( ApplicationHWnd, &ptCursor ); - m_pDev->SetCursorPosition( ptCursor.x, ptCursor.y, D3DCURSOR_IMMEDIATE_UPDATE); + g_renderBackend->Set_Hardware_Cursor_Position(ptCursor.x, ptCursor.y); } //Check if animated cursor and new frame if (m_currentFrames > 1) @@ -508,7 +517,9 @@ void W3DMouse::draw() if ((Int)m_currentAnimFrame != m_currentD3DFrame) { m_currentD3DFrame=(Int)m_currentAnimFrame; - m_pDev->SetCursorProperties(m_currentHotSpot.x,m_currentHotSpot.y,m_currentD3DSurface[m_currentD3DFrame]->Peek_D3D_Surface()); + g_renderBackend->Set_Hardware_Cursor_Image( + m_currentHotSpot.x, m_currentHotSpot.y, + m_currentD3DSurface[m_currentD3DFrame]); } } } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainTracks.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainTracks.cpp index c5c557d2d2c..85673d55c1d 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainTracks.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainTracks.cpp @@ -55,6 +55,7 @@ #include "WW3D2/camera.h" #include "WW3D2/assetmgr.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/scene.h" #include "GameLogic/TerrainLogic.h" #include "GameLogic/Object.h" @@ -888,21 +889,23 @@ Try improving the fit to vertical surfaces like cliffs. if (m_edgesToFlush >= 2) { ShaderClass::Invalidate(); - DX8Wrapper::Set_Material(m_vertexMaterialClass); - DX8Wrapper::Set_Shader(m_shaderClass); - DX8Wrapper::Set_Index_Buffer(m_indexBuffer,0); - DX8Wrapper::Set_Vertex_Buffer(m_vertexBuffer); + // TheSuperHackers @refactor bobtista 10/04/2026 Route high-level calls + // through the IRenderBackend abstraction. + g_renderBackend->Set_Material(m_vertexMaterialClass); + g_renderBackend->Set_Shader(m_shaderClass); + g_renderBackend->Set_Index_Buffer(m_indexBuffer,0); + g_renderBackend->Set_Vertex_Buffer(m_vertexBuffer,0); trackStartIndex=0; mod=m_usedModules; - DX8Wrapper::Set_Transform(D3DTS_WORLD,mod->Transform); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,mod->Transform); while (mod) { if (mod->m_activeEdgeCount >= 2 && mod->Is_Really_Visible()) { - DX8Wrapper::Set_Texture(0,mod->m_stageZeroTexture); - DX8Wrapper::Set_Index_Buffer_Index_Offset(trackStartIndex); - DX8Wrapper::Draw_Triangles( 0,(mod->m_activeEdgeCount-1)*2, 0, mod->m_activeEdgeCount*2); + g_renderBackend->Set_Texture(0,mod->m_stageZeroTexture); + g_renderBackend->Set_Index_Buffer_Index_Offset(trackStartIndex); + g_renderBackend->Draw_Triangles( 0,(mod->m_activeEdgeCount-1)*2, 0, mod->m_activeEdgeCount*2); trackStartIndex += mod->m_activeEdgeCount*2; } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp index 253fddbcd83..5ed88947926 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -169,6 +169,66 @@ void BgfxBackend::Invalidate_Cached_Render_States() { } +void BgfxBackend::Set_Blend_Op(BlendOp /*op*/) +{ +} + +void BgfxBackend::Set_Blend_Factors(BlendFactor /*src*/, BlendFactor /*dest*/) +{ +} + +void BgfxBackend::Set_Color_Write_Enable(bool /*red*/, bool /*green*/, bool /*blue*/, bool /*alpha*/) +{ +} + +void BgfxBackend::Set_Alpha_Blend_Enable(bool /*enable*/) +{ +} + +void BgfxBackend::Show_Hardware_Cursor(bool /*show*/) +{ +} + +void BgfxBackend::Set_Hardware_Cursor_Image(int /*hotspot_x*/, int /*hotspot_y*/, SurfaceClass * /*surface*/) +{ +} + +void BgfxBackend::Set_Hardware_Cursor_Position(int /*x*/, int /*y*/) +{ +} + +void BgfxBackend::Set_Stencil_Enable(bool /*enable*/) +{ +} + +void BgfxBackend::Set_Stencil_Func(CompareFunc /*func*/) +{ +} + +void BgfxBackend::Set_Stencil_Ref(unsigned int /*ref*/) +{ +} + +void BgfxBackend::Set_Stencil_Mask(unsigned int /*mask*/) +{ +} + +void BgfxBackend::Set_Stencil_Write_Mask(unsigned int /*mask*/) +{ +} + +void BgfxBackend::Set_Stencil_Pass_Op(StencilOp /*op*/) +{ +} + +void BgfxBackend::Set_Stencil_Fail_Op(StencilOp /*op*/) +{ +} + +void BgfxBackend::Set_Stencil_ZFail_Op(StencilOp /*op*/) +{ +} + // -- Transforms -------------------------------------------------------------- void BgfxBackend::Set_Transform(TransformKind /*transform*/, const Matrix4x4 & /*m*/) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h index d95612eb02b..74fae465b0c 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h @@ -72,6 +72,21 @@ class BgfxBackend : public IRenderBackend virtual void Apply_Render_State_Changes(); virtual void Apply_Default_State(); virtual void Invalidate_Cached_Render_States(); + virtual void Set_Blend_Op(BlendOp op); + virtual void Set_Blend_Factors(BlendFactor src, BlendFactor dest); + virtual void Set_Color_Write_Enable(bool red, bool green, bool blue, bool alpha); + virtual void Set_Alpha_Blend_Enable(bool enable); + virtual void Show_Hardware_Cursor(bool show); + virtual void Set_Hardware_Cursor_Image(int hotspot_x, int hotspot_y, SurfaceClass * surface); + virtual void Set_Hardware_Cursor_Position(int x, int y); + virtual void Set_Stencil_Enable(bool enable); + virtual void Set_Stencil_Func(CompareFunc func); + virtual void Set_Stencil_Ref(unsigned int ref); + virtual void Set_Stencil_Mask(unsigned int mask); + virtual void Set_Stencil_Write_Mask(unsigned int mask); + virtual void Set_Stencil_Pass_Op(StencilOp op); + virtual void Set_Stencil_Fail_Op(StencilOp op); + virtual void Set_Stencil_ZFail_Op(StencilOp op); // -- Transforms ----------------------------------------------------------- diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp index 20c62d6b718..3b6086d5bc2 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp @@ -165,6 +165,114 @@ void DX8Backend::Invalidate_Cached_Render_States() DX8Wrapper::Invalidate_Cached_Render_States(); } +void DX8Backend::Set_Blend_Op(BlendOp op) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_BLENDOP, static_cast(op)); +} + +void DX8Backend::Set_Blend_Factors(BlendFactor src, BlendFactor dest) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, static_cast(src)); + DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, static_cast(dest)); +} + +void DX8Backend::Set_Color_Write_Enable(bool red, bool green, bool blue, bool alpha) +{ + unsigned mask = 0; + if (red) + { + mask |= D3DCOLORWRITEENABLE_RED; + } + if (green) + { + mask |= D3DCOLORWRITEENABLE_GREEN; + } + if (blue) + { + mask |= D3DCOLORWRITEENABLE_BLUE; + } + if (alpha) + { + mask |= D3DCOLORWRITEENABLE_ALPHA; + } + DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE, mask); +} + +void DX8Backend::Set_Alpha_Blend_Enable(bool enable) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE, enable ? TRUE : FALSE); +} + +void DX8Backend::Show_Hardware_Cursor(bool show) +{ + IDirect3DDevice8 * pDev = DX8Wrapper::_Get_D3D_Device8(); + if (pDev != nullptr) + { + pDev->ShowCursor(show ? TRUE : FALSE); + } +} + +void DX8Backend::Set_Hardware_Cursor_Image(int hotspot_x, int hotspot_y, SurfaceClass * surface) +{ + IDirect3DDevice8 * pDev = DX8Wrapper::_Get_D3D_Device8(); + if (pDev != nullptr && surface != nullptr) + { + pDev->SetCursorProperties( + static_cast(hotspot_x), + static_cast(hotspot_y), + surface->Peek_D3D_Surface()); + } +} + +void DX8Backend::Set_Hardware_Cursor_Position(int x, int y) +{ + IDirect3DDevice8 * pDev = DX8Wrapper::_Get_D3D_Device8(); + if (pDev != nullptr) + { + pDev->SetCursorPosition(x, y, D3DCURSOR_IMMEDIATE_UPDATE); + } +} + +void DX8Backend::Set_Stencil_Enable(bool enable) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILENABLE, enable ? TRUE : FALSE); +} + +void DX8Backend::Set_Stencil_Func(CompareFunc func) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFUNC, static_cast(func)); +} + +void DX8Backend::Set_Stencil_Ref(unsigned int ref) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILREF, ref); +} + +void DX8Backend::Set_Stencil_Mask(unsigned int mask) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILMASK, mask); +} + +void DX8Backend::Set_Stencil_Write_Mask(unsigned int mask) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILWRITEMASK, mask); +} + +void DX8Backend::Set_Stencil_Pass_Op(StencilOp op) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILPASS, static_cast(op)); +} + +void DX8Backend::Set_Stencil_Fail_Op(StencilOp op) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFAIL, static_cast(op)); +} + +void DX8Backend::Set_Stencil_ZFail_Op(StencilOp op) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILZFAIL, static_cast(op)); +} + // -- Transforms -------------------------------------------------------------- void DX8Backend::Set_Transform(TransformKind transform, const Matrix4x4 & m) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h index 2f61e3da2dc..b1f04a1a1b5 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h @@ -68,6 +68,21 @@ class DX8Backend : public IRenderBackend virtual void Apply_Render_State_Changes(); virtual void Apply_Default_State(); virtual void Invalidate_Cached_Render_States(); + virtual void Set_Blend_Op(BlendOp op); + virtual void Set_Blend_Factors(BlendFactor src, BlendFactor dest); + virtual void Set_Color_Write_Enable(bool red, bool green, bool blue, bool alpha); + virtual void Set_Alpha_Blend_Enable(bool enable); + virtual void Show_Hardware_Cursor(bool show); + virtual void Set_Hardware_Cursor_Image(int hotspot_x, int hotspot_y, SurfaceClass * surface); + virtual void Set_Hardware_Cursor_Position(int x, int y); + virtual void Set_Stencil_Enable(bool enable); + virtual void Set_Stencil_Func(CompareFunc func); + virtual void Set_Stencil_Ref(unsigned int ref); + virtual void Set_Stencil_Mask(unsigned int mask); + virtual void Set_Stencil_Write_Mask(unsigned int mask); + virtual void Set_Stencil_Pass_Op(StencilOp op); + virtual void Set_Stencil_Fail_Op(StencilOp op); + virtual void Set_Stencil_ZFail_Op(StencilOp op); // -- Transforms ----------------------------------------------------------- diff --git a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h index 0451fd43de6..054c8c962b8 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h @@ -69,6 +69,67 @@ struct RenderBackendViewport float max_z; }; +// TheSuperHackers @refactor bobtista 10/04/2026 interface extension +// to unblock W3DStatusCircle fade effects and FlatHeightMap shroud trickery +// without exposing raw D3DRENDERSTATETYPE in the interface. +// +// Values chosen to match D3DBLENDOP_* / D3DBLEND_* directly so the DX8Backend +// can cast without a branch. Modern backends translate these to their native +// blend-state representation. + +enum BlendOp +{ + RB_BLEND_OP_ADD = 1, // D3DBLENDOP_ADD + RB_BLEND_OP_SUBTRACT = 2, // D3DBLENDOP_SUBTRACT + RB_BLEND_OP_REV_SUBTRACT = 3, // D3DBLENDOP_REVSUBTRACT + RB_BLEND_OP_MIN = 4, // D3DBLENDOP_MIN + RB_BLEND_OP_MAX = 5 // D3DBLENDOP_MAX +}; + +enum BlendFactor +{ + RB_BLEND_ZERO = 1, // D3DBLEND_ZERO + RB_BLEND_ONE = 2, // D3DBLEND_ONE + RB_BLEND_SRC_COLOR = 3, // D3DBLEND_SRCCOLOR + RB_BLEND_INV_SRC_COLOR = 4, // D3DBLEND_INVSRCCOLOR + RB_BLEND_SRC_ALPHA = 5, // D3DBLEND_SRCALPHA + RB_BLEND_INV_SRC_ALPHA = 6, // D3DBLEND_INVSRCALPHA + RB_BLEND_DEST_ALPHA = 7, // D3DBLEND_DESTALPHA + RB_BLEND_INV_DEST_ALPHA = 8, // D3DBLEND_INVDESTALPHA + RB_BLEND_DEST_COLOR = 9, // D3DBLEND_DESTCOLOR + RB_BLEND_INV_DEST_COLOR = 10, // D3DBLEND_INVDESTCOLOR + RB_BLEND_SRC_ALPHA_SAT = 11 // D3DBLEND_SRCALPHASAT +}; + +// TheSuperHackers @refactor bobtista 10/04/2026 stencil state +// extension. Generic CompareFunc enum is also reusable for depth-test +// comparison in a future pass. +enum CompareFunc +{ + // Values match D3DCMP_* 1..8 directly so DX8Backend can cast. + RB_CMP_NEVER = 1, + RB_CMP_LESS = 2, + RB_CMP_EQUAL = 3, + RB_CMP_LESS_EQUAL = 4, + RB_CMP_GREATER = 5, + RB_CMP_NOT_EQUAL = 6, + RB_CMP_GREATER_EQUAL = 7, + RB_CMP_ALWAYS = 8 +}; + +enum StencilOp +{ + // Values match D3DSTENCILOP_* 1..8 directly so DX8Backend can cast. + RB_STENCIL_OP_KEEP = 1, + RB_STENCIL_OP_ZERO = 2, + RB_STENCIL_OP_REPLACE = 3, + RB_STENCIL_OP_INCR_SAT = 4, + RB_STENCIL_OP_DECR_SAT = 5, + RB_STENCIL_OP_INVERT = 6, + RB_STENCIL_OP_INCR = 7, + RB_STENCIL_OP_DECR = 8 +}; + // ----------------------------------------------------------------------------- // IRenderBackend — abstract W3D-facing rendering interface // ----------------------------------------------------------------------------- @@ -118,9 +179,11 @@ class IRenderBackend virtual void Begin_Scene() = 0; virtual void End_Scene(bool flip_frame) = 0; virtual void Flip_To_Primary() = 0; + // Defaults match DX8Wrapper::Clear so existing call sites that supplied + // only the first 3-4 arguments compile unchanged after migration. virtual void Clear(bool clear_color, bool clear_z_stencil, const Vector3 & color, - float dest_alpha, float z, unsigned int stencil) = 0; + float dest_alpha = 0.0f, float z = 1.0f, unsigned int stencil = 0) = 0; virtual void Set_Viewport(const RenderBackendViewport & viewport) = 0; // ------------------------------------------------------------------------- @@ -146,6 +209,37 @@ class IRenderBackend virtual void Apply_Default_State() = 0; virtual void Invalidate_Cached_Render_States() = 0; + // TheSuperHackers @refactor bobtista 10/04/2026 typed blend + + // color-write setters. These exist so subsystems that + // previously called DX8Wrapper::Set_DX8_Render_State(D3DRS_BLENDOP / ...) + // can migrate without the interface re-exposing the raw D3DRENDERSTATETYPE. + virtual void Set_Blend_Op(BlendOp op) = 0; + virtual void Set_Blend_Factors(BlendFactor src, BlendFactor dest) = 0; + virtual void Set_Color_Write_Enable(bool red, bool green, bool blue, bool alpha) = 0; + // TheSuperHackers @refactor bobtista 10/04/2026 Natural complement + // to the IRenderBackend blend extension. + virtual void Set_Alpha_Blend_Enable(bool enable) = 0; + + // TheSuperHackers @refactor bobtista 10/04/2026 hardware cursor + // extension. Lets W3DMouse drive the device's hardware cursor without + // touching IDirect3DDevice8 directly. + virtual void Show_Hardware_Cursor(bool show) = 0; + virtual void Set_Hardware_Cursor_Image(int hotspot_x, int hotspot_y, SurfaceClass * surface) = 0; + virtual void Set_Hardware_Cursor_Position(int x, int y) = 0; + + // TheSuperHackers @refactor bobtista 10/04/2026 stencil state + // group. Each method maps 1:1 onto an existing D3DRS_STENCIL* state. The + // CompareFunc and StencilOp enums above are reusable for future depth + // and stencil work. + virtual void Set_Stencil_Enable(bool enable) = 0; + virtual void Set_Stencil_Func(CompareFunc func) = 0; + virtual void Set_Stencil_Ref(unsigned int ref) = 0; + virtual void Set_Stencil_Mask(unsigned int mask) = 0; + virtual void Set_Stencil_Write_Mask(unsigned int mask) = 0; + virtual void Set_Stencil_Pass_Op(StencilOp op) = 0; + virtual void Set_Stencil_Fail_Op(StencilOp op) = 0; + virtual void Set_Stencil_ZFail_Op(StencilOp op) = 0; + // ------------------------------------------------------------------------- // Transforms // ------------------------------------------------------------------------- diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp index c97158ff443..cbc88a5853f 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp @@ -658,6 +658,7 @@ bool DX8Wrapper::Reset_Device(bool reload_assets) Invalidate_Cached_Render_States(); Set_Default_Global_Render_States(); SHD_INIT_SHADERS; + IsDeviceLost=false; WWDEBUG_SAY(("Device reset completed")); return true; } diff --git a/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DShroud.h b/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DShroud.h index 82e5ed1f13f..f5732e185cf 100644 --- a/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DShroud.h +++ b/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DShroud.h @@ -28,6 +28,7 @@ #include "WW3D2/dx8wrapper.h" class AABoxClass; +class SurfaceClass; class WorldHeightMap; typedef UnsignedByte W3DShroudLevel; @@ -106,7 +107,10 @@ class W3DShroud Real m_cellWidth; ///CreateIndexBuffer / CreateVertexBuffer calls below stay on + // the IDirect3DDevice8 device pointer because they need a substantial + // allocation abstraction we don't have yet. m_renderTargetHasAlpha=TRUE; - if ((m_dynamicRenderTarget=DX8Wrapper::Create_Render_Target (DEFAULT_RENDER_TARGET_WIDTH, DEFAULT_RENDER_TARGET_HEIGHT, WW3D_FORMAT_A8R8G8B8)) == nullptr) + if ((m_dynamicRenderTarget=g_renderBackend->Create_Render_Target (DEFAULT_RENDER_TARGET_WIDTH, DEFAULT_RENDER_TARGET_HEIGHT, WW3D_FORMAT_A8R8G8B8)) == nullptr) { m_renderTargetHasAlpha=FALSE; //failed to get a render target with alpha. //try again without. - m_dynamicRenderTarget=DX8Wrapper::Create_Render_Target (DEFAULT_RENDER_TARGET_WIDTH, DEFAULT_RENDER_TARGET_HEIGHT); + m_dynamicRenderTarget=g_renderBackend->Create_Render_Target (DEFAULT_RENDER_TARGET_WIDTH, DEFAULT_RENDER_TARGET_HEIGHT, WW3D_FORMAT_UNKNOWN); } LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); @@ -624,14 +629,16 @@ static void RenderVBTile(TextureClass *text, Real ox, Real oy, Real ou, Real ov, ib[4]=0; } - DX8Wrapper::Set_Index_Buffer(ib_access,0); - DX8Wrapper::Set_Vertex_Buffer(vb_access); - DX8Wrapper::Set_Texture(0, text); - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE, TRUE ); + // TheSuperHackers @refactor bobtista 10/04/2026 Route the + // high-level binding/draw calls and the blend state through g_renderBackend. + // + g_renderBackend->Set_Index_Buffer(ib_access,0); + g_renderBackend->Set_Vertex_Buffer(vb_access); + g_renderBackend->Set_Texture(0, text); + g_renderBackend->Set_Blend_Factors(RB_BLEND_SRC_ALPHA, RB_BLEND_INV_SRC_ALPHA); + g_renderBackend->Set_Alpha_Blend_Enable(true); ShaderClass::Invalidate(); //invalidate to force shader to reset since we directly changed states - DX8Wrapper::Draw_Triangles( 0,2, 0, 4); //draw a quad, 2 triangles, 4 verts + g_renderBackend->Draw_Triangles( 0,2, 0, 4); //draw a quad, 2 triangles, 4 verts } //Debug code used to draw some dummy polygons. @@ -647,12 +654,12 @@ void TestBlendRender(RenderInfoClass & rinfo) } VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); - DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); + g_renderBackend->Set_Shader(ShaderClass::_PresetOpaqueShader); Matrix3D tm(1); //identity - DX8Wrapper::Set_Transform(D3DTS_WORLD,tm); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,tm); //grass RenderVBTile(grass,580.0f,480.0f,0.0f,0.0f); RenderVBTile(grass,590.0f,480.0f,0.25f,0.0f); @@ -685,22 +692,27 @@ void W3DProjectedShadowManager::flushDecals(W3DShadowTexture *texture, ShadowTyp LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); if (!m_pDev) return; //no D3D Device to render + // TheSuperHackers @refactor bobtista 10/04/2026 the high-level Set_Material/Set_Texture/Set_Shader/Apply_Render_State_Changes + // calls below are routed through g_renderBackend. The raw m_pDev->X() calls + // later in this function (SetIndices, SetStreamSource, SetVertexShader, + // SetRenderState, DrawIndexedPrimitive) remain on IDirect3DDevice8 because + // they belong to the deeply-coupled inner rendering loop. VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); - DX8Wrapper::Set_Texture(0,texture->getTexture()); + g_renderBackend->Set_Texture(0,texture->getTexture()); // DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); //good for debugging, draws without alpha switch (type) { case SHADOW_DECAL: - DX8Wrapper::Set_Shader(ShaderClass::_PresetMultiplicativeShader); + g_renderBackend->Set_Shader(ShaderClass::_PresetMultiplicativeShader); break; case SHADOW_ALPHA_DECAL: - DX8Wrapper::Set_Shader(ShaderClass::_PresetAlphaShader); + g_renderBackend->Set_Shader(ShaderClass::_PresetAlphaShader); break; case SHADOW_ADDITIVE_DECAL: - DX8Wrapper::Set_Shader(ShaderClass::_PresetAdditiveShader); + g_renderBackend->Set_Shader(ShaderClass::_PresetAdditiveShader); break; } @@ -708,7 +720,7 @@ void W3DProjectedShadowManager::flushDecals(W3DShadowTexture *texture, ShadowTyp // DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC,D3DCMP_GREATEREQUAL); //_PresetAlphaSpriteShader - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices //Alpha Blended Shadows // m_pDev->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); @@ -1380,7 +1392,7 @@ Int W3DProjectedShadowManager::renderShadows(RenderInfoClass & rinfo) //terrain is always visible and affected by all shadows so must render projector->Peek_Material_Pass()->Install_Materials(); - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices if (renderProjectedTerrainShadow(shadow, aaBox)) projectionCount++; projector->Peek_Material_Pass()->UnInstall_Materials(); diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp index 6e26d71f8ac..a7809d37ee4 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp @@ -43,6 +43,7 @@ #include "WW3D2/camera.h" #include "WW3D2/light.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/hlod.h" #include "WW3D2/mesh.h" #include "WW3D2/meshmdl.h" @@ -3285,15 +3286,21 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) nShadowIndicesInBuf = 0xffff; nShadowVertsInBuf = 0xffff; + // TheSuperHackers @refactor bobtista 10/04/2026 the high-level Set_Material/Set_Shader/Set_Texture/Apply_Render_State_Changes + // calls below are routed through g_renderBackend. The raw m_pDev->SetRenderState + // and m_pDev->SetTextureStageState calls in the same function remain on the + // IDirect3DDevice8 device pointer because they belong to the deeply-coupled + // stencil-volume rendering inner loop. + //Set W3D to some known state VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); - DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); - DX8Wrapper::Set_Texture(0,nullptr); //turn off textures - DX8Wrapper::Set_Texture(1,nullptr); //turn off textures - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Set_Shader(ShaderClass::_PresetOpaqueShader); + g_renderBackend->Set_Texture(0,nullptr); //turn off textures + g_renderBackend->Set_Texture(1,nullptr); //turn off textures + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices // turn off z writing m_pDev->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL); @@ -3447,6 +3454,11 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) //m_pDev->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID); + // note: oldColorWriteEnable is a captured DWORD bitmask read + // directly via m_pDev->GetRenderState earlier in this function. The + // restore can't go through Set_Color_Write_Enable(r,g,b,a) without + // re-decoding the bitmask, so it stays on Set_DX8_Render_State. The + // whole save/restore pair migrates together in a future phase. if (oldColorWriteEnable != 0x12345678) DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,oldColorWriteEnable); @@ -3462,24 +3474,26 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) m_pDev->SetRenderState(D3DRS_ALPHABLENDENABLE , FALSE); m_pDev->SetRenderState(D3DRS_LIGHTING, FALSE); - DX8Wrapper::Invalidate_Cached_Render_States(); + g_renderBackend->Invalidate_Cached_Render_States(); } else if (forceStencilFill) { //no shadows to render, but still need to fill stencil buffer //for other effects. + // TheSuperHackers @refactor bobtista 10/04/2026 same pattern as the main shadow-render branch above. + //Set W3D to some known state VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); - DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Set_Shader(ShaderClass::_PresetOpaqueShader); + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices renderStencilShadows(); - DX8Wrapper::Invalidate_Cached_Render_States(); + g_renderBackend->Invalidate_Cached_Render_States(); } } @@ -3654,7 +3668,7 @@ void W3DVolumetricShadowManager::reset() // ============================================================================ W3DVolumetricShadow* W3DVolumetricShadowManager::addShadow(RenderObjClass *robj, Shadow::ShadowTypeInfo *shadowInfo, Drawable *draw) { - if (!DX8Wrapper::Has_Stencil() || !robj || !TheGlobalData->m_useShadowVolumes) + if (!g_renderBackend->Has_Stencil() || !robj || !TheGlobalData->m_useShadowVolumes) return nullptr; //right now we require a stencil buffer W3DShadowGeometry *sg=nullptr; diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBibBuffer.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBibBuffer.cpp index 7a0444f1190..d30567913f6 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBibBuffer.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBibBuffer.cpp @@ -57,6 +57,7 @@ #include "W3DDevice/GameClient/W3DDynamicLight.h" #include "WW3D2/camera.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/dx8renderer.h" #include "WW3D2/mesh.h" #include "WW3D2/meshmdl.h" @@ -423,17 +424,19 @@ void W3DBibBuffer::renderBibs() if (m_curNumBibIndices == 0) { return; } + // TheSuperHackers @refactor bobtista 10/04/2026 Route high-level calls + // through the IRenderBackend abstraction. // Setup the vertex buffer, shader & texture. - DX8Wrapper::Set_Index_Buffer(m_indexBib,0); - DX8Wrapper::Set_Vertex_Buffer(m_vertexBib); - DX8Wrapper::Set_Shader(detailAlphaShader); + g_renderBackend->Set_Index_Buffer(m_indexBib,0); + g_renderBackend->Set_Vertex_Buffer(m_vertexBib,0); + g_renderBackend->Set_Shader(detailAlphaShader); if (m_curNumNormalBibIndices) { - DX8Wrapper::Set_Texture(0,m_bibTexture); - DX8Wrapper::Draw_Triangles( 0, m_curNumNormalBibIndices/3, 0, m_curNumNormalBibVertex); + g_renderBackend->Set_Texture(0,m_bibTexture); + g_renderBackend->Draw_Triangles( 0, m_curNumNormalBibIndices/3, 0, m_curNumNormalBibVertex); } if (m_curNumBibIndices>m_curNumNormalBibIndices) { - DX8Wrapper::Set_Texture(0,m_highlightBibTexture); - DX8Wrapper::Draw_Triangles( m_curNumNormalBibIndices, (m_curNumBibIndices-m_curNumNormalBibIndices)/3, + g_renderBackend->Set_Texture(0,m_highlightBibTexture); + g_renderBackend->Draw_Triangles( m_curNumNormalBibIndices, (m_curNumBibIndices-m_curNumNormalBibIndices)/3, m_curNumNormalBibVertex, m_curNumBibVertices-m_curNumNormalBibVertex); } } diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBridgeBuffer.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBridgeBuffer.cpp index 041d858f61d..d4c1d305878 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBridgeBuffer.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBridgeBuffer.cpp @@ -66,6 +66,7 @@ #include "W3DDevice/GameClient/W3DShroud.h" #include "WW3D2/camera.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/dx8renderer.h" #include "WW3D2/mesh.h" #include "WW3D2/meshmdl.h" @@ -131,9 +132,11 @@ are already set. */ void W3DBridge::renderBridge(Bool wireframe) { if (m_visible && m_numPolygons && m_numVertex) { - if (!wireframe) DX8Wrapper::Set_Texture(0,m_bridgeTexture); + // TheSuperHackers @refactor bobtista 10/04/2026 Route high-level calls + // through the IRenderBackend abstraction. + if (!wireframe) g_renderBackend->Set_Texture(0,m_bridgeTexture); // Draw all the bridges. - DX8Wrapper::Draw_Triangles( m_firstIndex, m_numPolygons, m_firstVertex, m_numVertex); + g_renderBackend->Draw_Triangles( m_firstIndex, m_numPolygons, m_firstVertex, m_numVertex); } } @@ -1149,16 +1152,18 @@ void W3DBridgeBuffer::drawBridges(CameraClass * camera, Bool wireframe, TextureC return; } - DX8Wrapper::Set_Material(m_vertexMaterial); + // TheSuperHackers @refactor bobtista 10/04/2026 Route high-level calls + // through the IRenderBackend abstraction. + g_renderBackend->Set_Material(m_vertexMaterial); // Setup the vertex buffer, shader & texture. - DX8Wrapper::Set_Index_Buffer(m_indexBridge,0); - DX8Wrapper::Set_Vertex_Buffer(m_vertexBridge); - DX8Wrapper::Set_Shader(detailAlphaShader); + g_renderBackend->Set_Index_Buffer(m_indexBridge,0); + g_renderBackend->Set_Vertex_Buffer(m_vertexBridge,0); + g_renderBackend->Set_Shader(detailAlphaShader); #ifdef RTS_DEBUG //DX8Wrapper::Set_Shader(detailShader); // shows alpha clipping. #endif - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); if (!wireframe && cloudTexture) { //Force a cloud texture projection into stage 1 @@ -1180,12 +1185,14 @@ void W3DBridgeBuffer::drawBridges(CameraClass * camera, Bool wireframe, TextureC if (!wireframe && TheTerrainRenderObject->getShroud()) { //Reset to a known shader. - DX8Wrapper::Invalidate_Cached_Render_States(); - DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); - DX8Wrapper::Set_Material(m_vertexMaterial); - DX8Wrapper::Set_Index_Buffer(m_indexBridge,0); - DX8Wrapper::Set_Vertex_Buffer(m_vertexBridge); - DX8Wrapper::Apply_Render_State_Changes(); + // TheSuperHackers @refactor bobtista 10/04/2026 Route high-level calls + // through the IRenderBackend abstraction. + g_renderBackend->Invalidate_Cached_Render_States(); + g_renderBackend->Set_Shader(ShaderClass::_PresetOpaqueShader); + g_renderBackend->Set_Material(m_vertexMaterial); + g_renderBackend->Set_Index_Buffer(m_indexBridge,0); + g_renderBackend->Set_Vertex_Buffer(m_vertexBridge,0); + g_renderBackend->Apply_Render_State_Changes(); //Apply custom shroud projection shader. W3DShaderManager::setTexture(0,TheTerrainRenderObject->getShroud()->getShroudTexture()); W3DShaderManager::setShader(W3DShaderManager::ST_SHROUD_TEXTURE, 0); diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDebugIcons.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDebugIcons.cpp index 356012c265a..7b3fee25223 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDebugIcons.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDebugIcons.cpp @@ -52,6 +52,7 @@ #include "GameLogic/GameLogic.h" #include "Common/MapObject.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" #if defined(RTS_DEBUG) @@ -215,14 +216,16 @@ void W3DDebugIcons::Render(RenderInfoClass & rinfo) // Bool anyVanished = false; if (m_numDebugIcons==0) return; - DX8Wrapper::Apply_Render_State_Changes(); + // TheSuperHackers @refactor bobtista 10/04/2026 Route high-level calls + // through the IRenderBackend abstraction. + g_renderBackend->Apply_Render_State_Changes(); - DX8Wrapper::Set_Material(m_vertexMaterialClass); - DX8Wrapper::Set_Texture(0, nullptr); - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Set_Material(m_vertexMaterialClass); + g_renderBackend->Set_Texture(0, nullptr); + g_renderBackend->Apply_Render_State_Changes(); Matrix3D tm(Transform); - DX8Wrapper::Set_Transform(D3DTS_WORLD,tm); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,tm); Int numRect = m_numDebugIcons; static Real offset = 30; @@ -303,10 +306,10 @@ void W3DDebugIcons::Render(RenderInfoClass & rinfo) } } if (numVertex == 0) break; - DX8Wrapper::Set_Shader(ShaderClass(SC_ALPHA)); - DX8Wrapper::Set_Index_Buffer(ib_access,0); - DX8Wrapper::Set_Vertex_Buffer(vb_access); - DX8Wrapper::Draw_Triangles( 0,curIndex/3, 0, numVertex); //draw a quad, 2 triangles, 4 verts + g_renderBackend->Set_Shader(ShaderClass(SC_ALPHA)); + g_renderBackend->Set_Index_Buffer(ib_access,0); + g_renderBackend->Set_Vertex_Buffer(vb_access); + g_renderBackend->Draw_Triangles( 0,curIndex/3, 0, numVertex); //draw a quad, 2 triangles, 4 verts } if (anyVanished) { diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DInGameUI.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DInGameUI.cpp index c85dd43ab0b..e9122d54e1f 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DInGameUI.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DInGameUI.cpp @@ -58,6 +58,7 @@ #include "W3DDevice/GameClient/HeightMap.h" #include "WW3D2/dx8indexbuffer.h" #include "WW3D2/dx8vertexbuffer.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/vertmaterial.h" class DebugHintObject : public RenderObjClass { @@ -240,18 +241,20 @@ void DebugHintObject::Render(RenderInfoClass & rinfo) SphereClass bounds(Vector3(m_myLoc.x, m_myLoc.y, m_myLoc.z), m_mySize); if (!rinfo.Camera.Cull_Sphere(bounds)) { - DX8Wrapper::Set_Material(m_vertexMaterialClass); - DX8Wrapper::Set_Shader(m_shaderClass); - DX8Wrapper::Set_Texture(0, nullptr); - DX8Wrapper::Set_Index_Buffer(m_indexBuffer,0); - DX8Wrapper::Set_Vertex_Buffer(m_vertexBufferTile); + // TheSuperHackers @refactor bobtista 10/04/2026 Route high-level calls + // through the IRenderBackend abstraction. + g_renderBackend->Set_Material(m_vertexMaterialClass); + g_renderBackend->Set_Shader(m_shaderClass); + g_renderBackend->Set_Texture(0, nullptr); + g_renderBackend->Set_Index_Buffer(m_indexBuffer,0); + g_renderBackend->Set_Vertex_Buffer(m_vertexBufferTile,0); Matrix3D tm(Transform); Vector3 vec(m_myLoc.x, m_myLoc.y, m_myLoc.z); tm.Set_Translation(vec); - DX8Wrapper::Set_Transform(D3DTS_WORLD, tm); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD, tm); - DX8Wrapper::Draw_Triangles( 0, 1, 0, 3); + g_renderBackend->Draw_Triangles( 0, 1, 0, 3); } } #endif // RTS_DEBUG diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DRoadBuffer.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DRoadBuffer.cpp index a4f1125b659..b3256f50e10 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DRoadBuffer.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DRoadBuffer.cpp @@ -63,6 +63,7 @@ #include "W3DDevice/GameClient/W3DShaderManager.h" #include "WW3D2/camera.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/dx8renderer.h" #include "WW3D2/mesh.h" #include "WW3D2/meshmdl.h" @@ -157,8 +158,10 @@ RoadType::~RoadType() void RoadType::applyTexture() { W3DShaderManager::setTexture(0,m_roadTexture); - DX8Wrapper::Set_Index_Buffer(m_indexRoad,0); - DX8Wrapper::Set_Vertex_Buffer(m_vertexRoad); + // TheSuperHackers @refactor bobtista 10/04/2026 Route high-level calls + // through the IRenderBackend abstraction. + g_renderBackend->Set_Index_Buffer(m_indexRoad,0); + g_renderBackend->Set_Vertex_Buffer(m_vertexRoad,0); } @@ -3257,10 +3260,12 @@ void W3DRoadBuffer::drawRoads(CameraClass * camera, TextureClass *cloudTexture, m_curRoadType = i; loadRoadsInVertexAndIndexBuffers(); if (m_roadTypes[i].getNumIndices() == 0) continue; + // TheSuperHackers @refactor bobtista 10/04/2026 Route high-level calls + // through the IRenderBackend abstraction. if (wireframe) { m_roadTypes[i].applyTexture(); - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Set_Shader(detailShader); // shows clipping. + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Set_Shader(detailShader); // shows clipping. } else { m_roadTypes[i].applyTexture(); } @@ -3272,7 +3277,7 @@ void W3DRoadBuffer::drawRoads(CameraClass * camera, TextureClass *cloudTexture, if (!wireframe) W3DShaderManager::setShader(st, pass); //Draw all this road type. - DX8Wrapper::Draw_Triangles( 0, m_roadTypes[i].getNumIndices()/3, 0, m_roadTypes[i].getNumVertices()); + g_renderBackend->Draw_Triangles( 0, m_roadTypes[i].getNumIndices()/3, 0, m_roadTypes[i].getNumVertices()); } if (!wireframe) //shader was applied at least once? @@ -3282,8 +3287,8 @@ void W3DRoadBuffer::drawRoads(CameraClass * camera, TextureClass *cloudTexture, #if 0 // Need to use a separate set of index & vertex buffers for this. jba. - DX8Wrapper::Set_Index_Buffer(nullptr,0); - DX8Wrapper::Set_Vertex_Buffer(nullptr); + g_renderBackend->Set_Index_Buffer(nullptr,0); + g_renderBackend->Set_Vertex_Buffer(nullptr,0); if (pDynamicLightsIterator) { for (i=0; im_curNumRoadIndices == 0) continue; if (wireframe) { - DX8Wrapper::Set_Texture(0,nullptr); + g_renderBackend->Set_Texture(0,nullptr); } else { m_roadTypes[i].applyTexture(); if (cloudTexture) { - DX8Wrapper::Set_Texture(1,cloudTexture); + g_renderBackend->Set_Texture(1,cloudTexture); } } - DX8Wrapper::Set_Shader(detailAlphaShader); + g_renderBackend->Set_Shader(detailAlphaShader); //Draw all the roads. - DX8Wrapper::Draw_Triangles( 0, m_curNumRoadIndices/3, 0, m_curNumRoadVertices); + g_renderBackend->Draw_Triangles( 0, m_curNumRoadIndices/3, 0, m_curNumRoadVertices); } } #endif diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp index 29d60da1d5c..533502db676 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp @@ -58,6 +58,7 @@ #include "WW3D2/dx8renderer.h" #include "WW3D2/sortingrenderer.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/light.h" #include "WW3D2/matpass.h" #include "WW3D2/shader.h" @@ -821,7 +822,7 @@ void RTS3DScene::Flush(RenderInfoClass & rinfo) #ifdef USE_NON_STENCIL_OCCLUSION flushOccludedObjects(rinfo); #else - if (DX8Wrapper::Has_Stencil()) + if (g_renderBackend->Has_Stencil()) flushOccludedObjectsIntoStencil(rinfo); #endif // Draw the trees last so they alpha blend onto everything correctly. @@ -924,10 +925,10 @@ void RTS3DScene::updatePlayerColorPasses() void RTS3DScene::Render(RenderInfoClass & rinfo) { //USE_PERF_TIMER(NonTerrainRender) - DX8Wrapper::Set_Fog(FogEnabled, FogColor, FogStart, FogEnd); + g_renderBackend->Set_Fog(FogEnabled, FogColor, FogStart, FogEnd); //Override the behind building selection if it's not available on current hardware (needs stencil). - TheWritableGlobalData->m_enableBehindBuildingMarkers = TheWritableGlobalData->m_enableBehindBuildingMarkers && DX8Wrapper::Has_Stencil(); + TheWritableGlobalData->m_enableBehindBuildingMarkers = TheWritableGlobalData->m_enableBehindBuildingMarkers && g_renderBackend->Has_Stencil(); if (Get_Extra_Pass_Polygon_Mode() == EXTRA_PASS_DISABLE) { @@ -968,7 +969,7 @@ void RTS3DScene::Render(RenderInfoClass & rinfo) //a projected alpha texture which will later be used to determine where //wireframe should be visible. ///@todo: Clearing to black may not be needed if the scene already did the clear. - DX8Wrapper::Clear(true, false, Vector3(0.0f,0.0f,0.0f),1.0f); // Clear color but not z + g_renderBackend->Clear(true, false, Vector3(0.0f,0.0f,0.0f),1.0f); // Clear color but not z DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_ALPHA); DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 0); @@ -1028,7 +1029,7 @@ void RTS3DScene::Render(RenderInfoClass & rinfo) Customized_Render(rinfo); break; case EXTRA_PASS_CLEAR_LINE: - DX8Wrapper::Clear(true, false, Vector3(0.0f,0.0f,0.0f), 0.0f); // Clear color but not z + g_renderBackend->Clear(true, false, Vector3(0.0f,0.0f,0.0f), 0.0f); // Clear color but not z WW3D::Enable_Texturing(false); WW3D::Enable_Coloring(0xff008000); DX8Wrapper::Set_DX8_Render_State(D3DRS_FILLMODE,D3DFILL_WIREFRAME); @@ -1215,11 +1216,18 @@ void renderStenciledPlayerColor( UnsignedInt color, UnsignedInt stencilRef, Bool v[2].color = color; v[3].color = color; - DX8Wrapper::Set_Shader(PlayerColorShader); + // TheSuperHackers @refactor bobtista 10/04/2026 the high-level Set_Shader/Set_Material/Apply_Render_State_Changes calls + // and all the stencil + alpha-blend state setters in this function (and + // the rest of W3DScene.cpp) are routed through g_renderBackend via the + // new stencil state extension. The remaining low-level Set_DX8_Render_State + // calls (D3DRS_ZBIAS, COLORWRITEENABLE, FILLMODE, ZENABLE/ZFUNC, + // SRCBLEND/DESTBLEND pairs, AMBIENT) and the raw m_pDev->* device pointer + // access points stay on DX8Wrapper::* until a future phase. + g_renderBackend->Set_Shader(PlayerColorShader); VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); - DX8Wrapper::Apply_Render_State_Changes(); //force update all render states + g_renderBackend->Apply_Render_State_Changes(); //force update all render states LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); @@ -1231,20 +1239,20 @@ void renderStenciledPlayerColor( UnsignedInt color, UnsignedInt stencilRef, Bool m_pDev->SetVertexShader(D3DFVF_XYZRHW | D3DFVF_DIFFUSE); // Set stencil states - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILENABLE, TRUE ); + g_renderBackend->Set_Stencil_Enable(true); DX8Wrapper::Set_DX8_Render_State(D3DRS_ZENABLE, TRUE ); DWORD oldColorWriteEnable=0x12345678; if (clear) { //we want to clear the stencil buffer to some known value wherever a player index is stored Int occludedMask=TheW3DShadowManager->getStencilShadowMask(); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILREF, 0x80808080 ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILMASK, occludedMask ); //isolate bits containing occluder|playerIndex - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILWRITEMASK,0xffffffff ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFUNC, D3DCMP_LESS ); //only draw to pixels that match the reference value - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILZFAIL, D3DSTENCILOP_REPLACE ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE ); //pixels which had occluded player colors, get MSB set. - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFAIL, D3DSTENCILOP_ZERO ); //pixels which had no occluded player colors are cleared. + g_renderBackend->Set_Stencil_Ref(0x80808080); + g_renderBackend->Set_Stencil_Mask(occludedMask); //isolate bits containing occluder|playerIndex + g_renderBackend->Set_Stencil_Write_Mask(0xffffffff); + g_renderBackend->Set_Stencil_Func(RB_CMP_LESS); //only draw to pixels that match the reference value + g_renderBackend->Set_Stencil_ZFail_Op(RB_STENCIL_OP_REPLACE); + g_renderBackend->Set_Stencil_Pass_Op(RB_STENCIL_OP_REPLACE); //pixels which had occluded player colors, get MSB set. + g_renderBackend->Set_Stencil_Fail_Op(RB_STENCIL_OP_ZERO); //pixels which had no occluded player colors are cleared. DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC, D3DCMP_NEVER ); //fail all access to the frame buffer to improve memory bandwidth //disable writes to color buffer @@ -1256,35 +1264,32 @@ void renderStenciledPlayerColor( UnsignedInt color, UnsignedInt stencilRef, Bool else { //device does not support disabling writes to color buffer so fake it through alpha blending - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE, TRUE); - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_ZERO ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, D3DBLEND_ONE ); + g_renderBackend->Set_Alpha_Blend_Enable(true); + g_renderBackend->Set_Blend_Factors(RB_BLEND_ZERO, RB_BLEND_ONE); } } else { - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILREF, stencilRef ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILMASK, 0xffffffff ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILWRITEMASK,0xffffffff ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFUNC, D3DCMP_EQUAL ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ); + g_renderBackend->Set_Stencil_Ref(stencilRef); + g_renderBackend->Set_Stencil_Mask(0xffffffff); + g_renderBackend->Set_Stencil_Write_Mask(0xffffffff); + g_renderBackend->Set_Stencil_Func(RB_CMP_EQUAL); + g_renderBackend->Set_Stencil_ZFail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Pass_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Fail_Op(RB_STENCIL_OP_KEEP); //Make occluded pixels transparent - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE, TRUE); - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); + g_renderBackend->Set_Alpha_Blend_Enable(true); + g_renderBackend->Set_Blend_Factors(RB_BLEND_SRC_ALPHA, RB_BLEND_INV_SRC_ALPHA); } if (DX8Wrapper::_Is_Triangle_Draw_Enabled()) m_pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANSLITVERTEX)); // turn off the stencil buffer - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILENABLE, FALSE ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE, FALSE); //restore shader state - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_ONE ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, D3DBLEND_ZERO ); + g_renderBackend->Set_Stencil_Enable(false); + g_renderBackend->Set_Alpha_Blend_Enable(false); //restore shader state + g_renderBackend->Set_Blend_Factors(RB_BLEND_ONE, RB_BLEND_ZERO); DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC, D3DCMP_ALWAYS); if (oldColorWriteEnable != 0x12345678) @@ -1345,16 +1350,16 @@ void RTS3DScene::flushOccludedObjectsIntoStencil(RenderInfoClass & rinfo) lastPlayerObject[index]++; //increment to next object } - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILENABLE, TRUE ); + g_renderBackend->Set_Stencil_Enable(true); DX8Wrapper::Set_DX8_Render_State(D3DRS_ZENABLE, TRUE ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILMASK, 0xffffffff); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILWRITEMASK, 0xffffffff); + g_renderBackend->Set_Stencil_Mask(0xffffffff); + g_renderBackend->Set_Stencil_Write_Mask(0xffffffff); //Always store player index into stencil unless it is occluded by another //player's potentially occluded objects. - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFUNC, D3DCMP_ALWAYS ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE ); + g_renderBackend->Set_Stencil_Func(RB_CMP_ALWAYS); + g_renderBackend->Set_Stencil_ZFail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Fail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Pass_Op(RB_STENCIL_OP_REPLACE); //Find out which player indices are actually used and remap them to //a color index. Render all objects using the same color index at once. @@ -1384,7 +1389,7 @@ void RTS3DScene::flushOccludedObjectsIntoStencil(RenderInfoClass & rinfo) Int thisPlayerColorIndex=playerColorIndex[k]; //Store this object's color index into bits 3-6 of stencil buffer - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILREF, thisPlayerColorIndex<<3); + g_renderBackend->Set_Stencil_Ref(thisPlayerColorIndex<<3); //Render all of this player's objects for which we care when they are occluded. RenderObjClass **renderList=&playerObjects[k][0]; @@ -1400,7 +1405,7 @@ void RTS3DScene::flushOccludedObjectsIntoStencil(RenderInfoClass & rinfo) //Stencil buffer is now filled with color indices of potentially occluded objects. We now draw //non-occluder or occludee objects such as small rocks, shrubs, etc. which we don't care about //but need to render here so that they don't interfere with building occlusion. - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILENABLE, FALSE ); //these objects are not stored in stencil + g_renderBackend->Set_Stencil_Enable(false); //these objects are not stored in stencil RenderObjClass **nonOccluderOrOccludeeList=m_nonOccludersOrOccludees; for (k=0; kSet_Stencil_Enable(true); DX8Wrapper::Set_DX8_Render_State(D3DRS_ZENABLE, TRUE ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILREF, 0xffffffff); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILMASK, 0xffffffff); //isolate lowest player color - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILWRITEMASK, 0x80); //only write to MSB - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFUNC, D3DCMP_ALWAYS ); //check if player colors stored in pixel - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE ); + g_renderBackend->Set_Stencil_Ref(0xffffffff); + g_renderBackend->Set_Stencil_Mask(0xffffffff); //isolate lowest player color + g_renderBackend->Set_Stencil_Write_Mask(0x80); //only write to MSB + g_renderBackend->Set_Stencil_Func(RB_CMP_ALWAYS); //check if player colors stored in pixel + g_renderBackend->Set_Stencil_ZFail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Fail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Pass_Op(RB_STENCIL_OP_REPLACE); //Render all potential occluders on top of already rendered potential occludees. RenderObjClass **occluderList=m_potentialOccluders; @@ -1454,7 +1459,7 @@ void RTS3DScene::flushOccludedObjectsIntoStencil(RenderInfoClass & rinfo) TheW3DShadowManager->setStencilShadowMask(0x80808080); //msb indicates occluded player pixels so ignore it when filling screen with shadow } - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILENABLE, FALSE ); + g_renderBackend->Set_Stencil_Enable(false); } else if (m_numNonOccluderOrOccludee || m_numPotentialOccluders || m_numPotentialOccludees) @@ -1503,18 +1508,18 @@ void RTS3DScene::flushOccludedObjects(RenderInfoClass & rinfo) { const Int localPlayerIndex = rts::getObservedOrLocalPlayerIndex_Safe(); - if (DX8Wrapper::Has_Stencil()) //just in case we have shadows, disable them over occluded pixels. + if (g_renderBackend->Has_Stencil()) //just in case we have shadows, disable them over occluded pixels. { //Set all stencil pixels of potentially occluded objects to 128. - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILENABLE, TRUE ); + g_renderBackend->Set_Stencil_Enable(true); DX8Wrapper::Set_DX8_Render_State(D3DRS_ZENABLE, TRUE ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILREF, 128 ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILMASK, 0xffffffff ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILWRITEMASK,0xffffffff ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFUNC, D3DCMP_ALWAYS ); + g_renderBackend->Set_Stencil_Ref(128); + g_renderBackend->Set_Stencil_Mask(0xffffffff); + g_renderBackend->Set_Stencil_Write_Mask(0xffffffff); + g_renderBackend->Set_Stencil_ZFail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Fail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Pass_Op(RB_STENCIL_OP_REPLACE); + g_renderBackend->Set_Stencil_Func(RB_CMP_ALWAYS); } //First draw all the solid colored models @@ -1542,8 +1547,8 @@ void RTS3DScene::flushOccludedObjects(RenderInfoClass & rinfo) //Now draw the normal models so they cover up the colored models on any pixels that //Normal models will clear stencil value from 128 back to 0 where the object pixels are //not occluded but will leave 128 in stencil where still occluded. - if (DX8Wrapper::Has_Stencil()) - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILREF, 0 ); + if (g_renderBackend->Has_Stencil()) + g_renderBackend->Set_Stencil_Ref(0); for (i=0; iSet_Stencil_Enable(false); TheW3DShadowManager->setStencilShadowMask(0x80808080); //upper MSB always contains flag indicating occluded player color. } diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp index e60510a3c8b..27d3d112b0e 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp @@ -31,6 +31,8 @@ #include "WW3D2/camera.h" #include "WWLib/simplevec.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/surfaceclass.h" #include "Common/MapObject.h" #include "Common/PerfTimer.h" #include "W3DDevice/GameClient/HeightMap.h" @@ -93,8 +95,7 @@ W3DShroud::~W3DShroud() { ReleaseResources(); - if (m_pSrcTexture) - m_pSrcTexture->Release(); + REF_PTR_RELEASE(m_pSrcTexture); delete [] m_finalFogData; delete [] m_currentFogData; @@ -155,26 +156,27 @@ void W3DShroud::init(WorldHeightMap *pMap, Real worldCellSizeX, Real worldCellSi memset(m_finalFogData,0,srcWidth*srcHeight); #endif + // TheSuperHackers @refactor bobtista 10/04/2026 Allocate the + // shroud sysmem surface through SurfaceClass instead of raw IDirect3DSurface8. + // SurfaceClass's (w, h, format) constructor wraps _Create_DX8_Surface so the + // underlying allocation is identical and the lock-then-cache-pointer trick + // the rest of this file relies on continues to work. #if defined(RTS_DEBUG) if (TheGlobalData && TheGlobalData->m_fogOfWarOn) - m_pSrcTexture = DX8Wrapper::_Create_DX8_Surface(srcWidth,srcHeight, WW3D_FORMAT_A4R4G4B4); + m_pSrcTexture = new SurfaceClass(srcWidth, srcHeight, WW3D_FORMAT_A4R4G4B4); else #endif - m_pSrcTexture = DX8Wrapper::_Create_DX8_Surface(srcWidth,srcHeight, WW3D_FORMAT_R5G6B5); + m_pSrcTexture = new SurfaceClass(srcWidth, srcHeight, WW3D_FORMAT_R5G6B5); DEBUG_ASSERTCRASH( m_pSrcTexture != nullptr, ("Failed to Allocate Shroud Src Surface")); - D3DLOCKED_RECT rect; - - //Get a pointer to source surface pixels. - HRESULT res = m_pSrcTexture->LockRect(&rect,nullptr,D3DLOCK_NO_DIRTY_UPDATE); - m_pSrcTexture->UnlockRect(); - - DEBUG_ASSERTCRASH( res == D3D_OK, ("Failed to lock shroud src surface")); - res = 0;// just to avoid compiler warnings - - m_srcTextureData=rect.pBits; - m_srcTexturePitch=rect.Pitch; + //Get a pointer to source surface pixels. We lock-then-unlock and keep the + //pointer alive for the lifetime of the surface, matching the original DX8 + //behavior; the system-memory backing stays valid after the unlock. + int srcPitch = 0; + m_srcTextureData = m_pSrcTexture->Lock(&srcPitch); + m_pSrcTexture->Unlock(); + m_srcTexturePitch = static_cast(srcPitch); //clear entire texture to black memset(m_srcTextureData,0,m_srcTexturePitch*srcHeight); @@ -203,11 +205,7 @@ void W3DShroud::init(WorldHeightMap *pMap, Real worldCellSizeX, Real worldCellSi void W3DShroud::reset() { //Free old shroud data since it may no longer fit new map. - if (m_pSrcTexture) - { - m_pSrcTexture->Release(); - m_pSrcTexture=nullptr; - } + REF_PTR_RELEASE(m_pSrcTexture); delete [] m_finalFogData; m_finalFogData=nullptr; @@ -476,27 +474,31 @@ void W3DShroud::fillBorderShroudData(W3DShroudLevel level, SurfaceClass* pDestSu dstPoint.y=y; dstPoint.x=0; + // TheSuperHackers @refactor bobtista 10/04/2026 Replace + // _Copy_DX8_Rects with SurfaceClass::Copy. The src/dest math is the + // same; SurfaceClass::Copy(dstx, dsty, srcx, srcy, w, h, src) maps + // directly onto the (srcRect, dstPoint) pair the original DX8 call used. for (x=0; xPeek_D3D_Surface(), - &dstPoint); + pDestSurface->Copy( + dstPoint.x, dstPoint.y, + srcRect.left, srcRect.top, + srcRect.right - srcRect.left, + srcRect.bottom - srcRect.top, + m_pSrcTexture); } if (numExtraPixels) { Int oldVal=srcRect.right; dstPoint.x = numFullCopies * oldVal; srcRect.right = numExtraPixels; - DX8Wrapper::_Copy_DX8_Rects( - m_pSrcTexture, - &srcRect, - 1, - pDestSurface->Peek_D3D_Surface(), - &dstPoint); + pDestSurface->Copy( + dstPoint.x, dstPoint.y, + srcRect.left, srcRect.top, + srcRect.right - srcRect.left, + srcRect.bottom - srcRect.top, + m_pSrcTexture); srcRect.right = oldVal; } } @@ -526,7 +528,11 @@ void W3DShroud::render(CameraClass *cam) if (!m_pSrcTexture) return; //nothing to update from. Must be in reset state. - if (DX8Wrapper::_Get_D3D_Device8() && (DX8Wrapper::_Get_D3D_Device8()->TestCooperativeLevel()) != D3D_OK) + // TheSuperHackers @refactor bobtista 10/04/2026 Replaced the + // raw _Get_D3D_Device8()->TestCooperativeLevel() check with the abstracted + // device-lost flag on IRenderBackend. Same intent: skip rendering this + // frame if the device isn't ready. + if (g_renderBackend->Is_Device_Lost()) return; //device not ready to render anything #if defined(RTS_DEBUG) @@ -711,12 +717,14 @@ void W3DShroud::render(CameraClass *cam) { //USE_PERF_TIMER(shroudCopy) - DX8Wrapper::_Copy_DX8_Rects( - m_pSrcTexture, - &srcRect, - 1, - pDestSurface->Peek_D3D_Surface(), - &dstPoint); + // TheSuperHackers @refactor bobtista 10/04/2026 SurfaceClass::Copy + // in place of _Copy_DX8_Rects. + pDestSurface->Copy( + dstPoint.x, dstPoint.y, + srcRect.left, srcRect.top, + srcRect.right - srcRect.left, + srcRect.bottom - srcRect.top, + m_pSrcTexture); } REF_PTR_RELEASE (pDestSurface); diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DStatusCircle.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DStatusCircle.cpp index 94948fbdb97..9228a0889f7 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DStatusCircle.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DStatusCircle.cpp @@ -318,11 +318,9 @@ void W3DStatusCircle::Render(RenderInfoClass & rinfo) if (m_needUpdate) { updateCircleVB(); } - // TheSuperHackers @refactor bobtista 10/04/2026 First IRenderBackend call site - // migrated to IRenderBackend. High-level W3D-facing calls route through - // the global backend pointer; the remaining raw D3DRS_* state calls - // below still go through DX8Wrapper:: directly because IRenderBackend - // does not expose the low-level D3D8 state API yet. + // TheSuperHackers @refactor bobtista 10/04/2026 Introduced the + // IRenderBackend migration for this function; completed it by + // routing the fade blend-op overrides through the new interface API. // See Core/Libraries/Source/WWVegas/WW3D2/RENDER_BACKEND.md. //Apply the shader and material @@ -369,22 +367,21 @@ void W3DStatusCircle::Render(RenderInfoClass & rinfo) g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); break; case ScriptEngine::FADE_SUBTRACT: - // Low-level blend op not yet abstracted by IRenderBackend (D3DRS_* skipped). - DX8Wrapper::Set_DX8_Render_State(D3DRS_BLENDOP, D3DBLENDOP_REVSUBTRACT ); + // TheSuperHackers @refactor bobtista 10/04/2026 Route the remaining + // blend-op override through the IRenderBackend extension. + g_renderBackend->Set_Blend_Op(RB_BLEND_OP_REV_SUBTRACT); g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); - DX8Wrapper::Set_DX8_Render_State(D3DRS_BLENDOP, D3DBLENDOP_ADD ); + g_renderBackend->Set_Blend_Op(RB_BLEND_OP_ADD); break; case ScriptEngine::FADE_SATURATE: // 4x multiply - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_DESTCOLOR); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_SRCCOLOR); + g_renderBackend->Set_Blend_Factors(RB_BLEND_DEST_COLOR, RB_BLEND_SRC_COLOR); g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); break; case ScriptEngine::FADE_MULTIPLY: // Straight multiply - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_ZERO); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_SRCCOLOR); + g_renderBackend->Set_Blend_Factors(RB_BLEND_ZERO, RB_BLEND_SRC_COLOR); g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); break; } diff --git a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DShroud.h b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DShroud.h index 7daaf094f95..4dba0c2db4b 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DShroud.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DShroud.h @@ -28,6 +28,7 @@ #include "WW3D2/dx8wrapper.h" class AABoxClass; +class SurfaceClass; class WorldHeightMap; typedef UnsignedByte W3DShroudLevel; @@ -106,7 +107,10 @@ class W3DShroud Real m_cellWidth; ///CreateIndexBuffer / CreateVertexBuffer calls below stay on + // the IDirect3DDevice8 device pointer because they need a substantial + // allocation abstraction we don't have yet. m_renderTargetHasAlpha=TRUE; - if ((m_dynamicRenderTarget=DX8Wrapper::Create_Render_Target (DEFAULT_RENDER_TARGET_WIDTH, DEFAULT_RENDER_TARGET_HEIGHT, WW3D_FORMAT_A8R8G8B8)) == nullptr) + if ((m_dynamicRenderTarget=g_renderBackend->Create_Render_Target (DEFAULT_RENDER_TARGET_WIDTH, DEFAULT_RENDER_TARGET_HEIGHT, WW3D_FORMAT_A8R8G8B8)) == nullptr) { m_renderTargetHasAlpha=FALSE; //failed to get a render target with alpha. //try again without. - m_dynamicRenderTarget=DX8Wrapper::Create_Render_Target (DEFAULT_RENDER_TARGET_WIDTH, DEFAULT_RENDER_TARGET_HEIGHT); + m_dynamicRenderTarget=g_renderBackend->Create_Render_Target (DEFAULT_RENDER_TARGET_WIDTH, DEFAULT_RENDER_TARGET_HEIGHT, WW3D_FORMAT_UNKNOWN); } LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); @@ -624,14 +630,15 @@ static void RenderVBTile(TextureClass *text, Real ox, Real oy, Real ou, Real ov, ib[4]=0; } - DX8Wrapper::Set_Index_Buffer(ib_access,0); - DX8Wrapper::Set_Vertex_Buffer(vb_access); - DX8Wrapper::Set_Texture(0, text); - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE, TRUE ); + // TheSuperHackers @refactor bobtista 10/04/2026 route the + // high-level binding/draw calls and the blend state through g_renderBackend. + g_renderBackend->Set_Index_Buffer(ib_access,0); + g_renderBackend->Set_Vertex_Buffer(vb_access); + g_renderBackend->Set_Texture(0, text); + g_renderBackend->Set_Blend_Factors(RB_BLEND_SRC_ALPHA, RB_BLEND_INV_SRC_ALPHA); + g_renderBackend->Set_Alpha_Blend_Enable(true); ShaderClass::Invalidate(); //invalidate to force shader to reset since we directly changed states - DX8Wrapper::Draw_Triangles( 0,2, 0, 4); //draw a quad, 2 triangles, 4 verts + g_renderBackend->Draw_Triangles( 0,2, 0, 4); //draw a quad, 2 triangles, 4 verts } //Debug code used to draw some dummy polygons. @@ -647,12 +654,12 @@ void TestBlendRender(RenderInfoClass & rinfo) } VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); - DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); + g_renderBackend->Set_Shader(ShaderClass::_PresetOpaqueShader); Matrix3D tm(1); //identity - DX8Wrapper::Set_Transform(D3DTS_WORLD,tm); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,tm); //grass RenderVBTile(grass,580.0f,480.0f,0.0f,0.0f); RenderVBTile(grass,590.0f,480.0f,0.25f,0.0f); @@ -685,22 +692,28 @@ void W3DProjectedShadowManager::flushDecals(W3DShadowTexture *texture, ShadowTyp LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); if (!m_pDev) return; //no D3D Device to render + // TheSuperHackers @refactor bobtista 10/04/2026 partial migration: + // the high-level Set_Material/Set_Texture/Set_Shader/Apply_Render_State_Changes + // calls below are routed through g_renderBackend. The raw m_pDev->X() calls + // later in this function (SetIndices, SetStreamSource, SetVertexShader, + // SetRenderState, DrawIndexedPrimitive) remain on IDirect3DDevice8 because + // they belong to the deeply-coupled inner rendering loop. VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); - DX8Wrapper::Set_Texture(0,texture->getTexture()); + g_renderBackend->Set_Texture(0,texture->getTexture()); // DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); //good for debugging, draws without alpha switch (type) { case SHADOW_DECAL: - DX8Wrapper::Set_Shader(ShaderClass::_PresetMultiplicativeShader); + g_renderBackend->Set_Shader(ShaderClass::_PresetMultiplicativeShader); break; case SHADOW_ALPHA_DECAL: - DX8Wrapper::Set_Shader(ShaderClass::_PresetAlphaShader); + g_renderBackend->Set_Shader(ShaderClass::_PresetAlphaShader); break; case SHADOW_ADDITIVE_DECAL: - DX8Wrapper::Set_Shader(ShaderClass::_PresetAdditiveShader); + g_renderBackend->Set_Shader(ShaderClass::_PresetAdditiveShader); break; } @@ -708,7 +721,7 @@ void W3DProjectedShadowManager::flushDecals(W3DShadowTexture *texture, ShadowTyp // DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC,D3DCMP_GREATEREQUAL); //_PresetAlphaSpriteShader - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices //Alpha Blended Shadows // m_pDev->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); @@ -1380,7 +1393,7 @@ Int W3DProjectedShadowManager::renderShadows(RenderInfoClass & rinfo) //terrain is always visible and affected by all shadows so must render projector->Peek_Material_Pass()->Install_Materials(); - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices if (renderProjectedTerrainShadow(shadow, aaBox)) projectionCount++; projector->Peek_Material_Pass()->UnInstall_Materials(); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp index 33b42a8c03e..4ddbb7d92bf 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp @@ -43,6 +43,7 @@ #include "WW3D2/camera.h" #include "WW3D2/light.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/hlod.h" #include "WW3D2/mesh.h" #include "WW3D2/meshmdl.h" @@ -3429,15 +3430,23 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) nShadowIndicesInBuf = 0xffff; nShadowVertsInBuf = 0xffff; + // TheSuperHackers @refactor bobtista 10/04/2026 partial migration: + // the high-level Set_Material/Set_Shader/Set_Texture/Apply_Render_State_Changes + // calls below are routed through g_renderBackend. The raw m_pDev->SetRenderState + // and m_pDev->SetTextureStageState calls in the same function remain on the + // IDirect3DDevice8 device pointer because they belong to the deeply-coupled + // stencil-volume rendering inner loop. + // list and the reasoning behind the partial migration. + //Set W3D to some known state VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); - DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); - DX8Wrapper::Set_Texture(0,nullptr); //turn off textures - DX8Wrapper::Set_Texture(1,nullptr); //turn off textures - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Set_Shader(ShaderClass::_PresetOpaqueShader); + g_renderBackend->Set_Texture(0,nullptr); //turn off textures + g_renderBackend->Set_Texture(1,nullptr); //turn off textures + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices // turn off z writing m_pDev->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL); @@ -3591,6 +3600,11 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) //m_pDev->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID); + // Note: oldColorWriteEnable is a captured DWORD bitmask read + // directly via m_pDev->GetRenderState earlier in this function. The + // restore can't go through Set_Color_Write_Enable(r,g,b,a) without + // re-decoding the bitmask, so it stays on Set_DX8_Render_State. The + // whole save/restore pair migrates together in a future phase. if (oldColorWriteEnable != 0x12345678) DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,oldColorWriteEnable); @@ -3606,24 +3620,27 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) m_pDev->SetRenderState(D3DRS_ALPHABLENDENABLE , FALSE); m_pDev->SetRenderState(D3DRS_LIGHTING, FALSE); - DX8Wrapper::Invalidate_Cached_Render_States(); + g_renderBackend->Invalidate_Cached_Render_States(); } else if (forceStencilFill) { //no shadows to render, but still need to fill stencil buffer //for other effects. + // TheSuperHackers @refactor bobtista 10/04/2026 partial migration: + // same pattern as the main shadow-render branch above. + //Set W3D to some known state VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); - DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Set_Shader(ShaderClass::_PresetOpaqueShader); + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices renderStencilShadows(); - DX8Wrapper::Invalidate_Cached_Render_States(); + g_renderBackend->Invalidate_Cached_Render_States(); } } @@ -3798,7 +3815,7 @@ void W3DVolumetricShadowManager::reset() // ============================================================================ W3DVolumetricShadow* W3DVolumetricShadowManager::addShadow(RenderObjClass *robj, Shadow::ShadowTypeInfo *shadowInfo, Drawable *draw) { - if (!DX8Wrapper::Has_Stencil() || !robj || !TheGlobalData->m_useShadowVolumes) + if (!g_renderBackend->Has_Stencil() || !robj || !TheGlobalData->m_useShadowVolumes) return nullptr; //right now we require a stencil buffer W3DShadowGeometry *sg=nullptr; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBibBuffer.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBibBuffer.cpp index 5f47d7362d7..de67baa5e43 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBibBuffer.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBibBuffer.cpp @@ -57,6 +57,7 @@ #include "W3DDevice/GameClient/W3DDynamicLight.h" #include "WW3D2/camera.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/dx8renderer.h" #include "WW3D2/mesh.h" #include "WW3D2/meshmdl.h" @@ -423,17 +424,19 @@ void W3DBibBuffer::renderBibs() if (m_curNumBibIndices == 0) { return; } + // TheSuperHackers @refactor bobtista 10/04/2026 Route high-level calls + // through the IRenderBackend abstraction. // Setup the vertex buffer, shader & texture. - DX8Wrapper::Set_Index_Buffer(m_indexBib,0); - DX8Wrapper::Set_Vertex_Buffer(m_vertexBib); - DX8Wrapper::Set_Shader(detailAlphaShader); + g_renderBackend->Set_Index_Buffer(m_indexBib,0); + g_renderBackend->Set_Vertex_Buffer(m_vertexBib,0); + g_renderBackend->Set_Shader(detailAlphaShader); if (m_curNumNormalBibIndices) { - DX8Wrapper::Set_Texture(0,m_bibTexture); - DX8Wrapper::Draw_Triangles( 0, m_curNumNormalBibIndices/3, 0, m_curNumNormalBibVertex); + g_renderBackend->Set_Texture(0,m_bibTexture); + g_renderBackend->Draw_Triangles( 0, m_curNumNormalBibIndices/3, 0, m_curNumNormalBibVertex); } if (m_curNumBibIndices>m_curNumNormalBibIndices) { - DX8Wrapper::Set_Texture(0,m_highlightBibTexture); - DX8Wrapper::Draw_Triangles( m_curNumNormalBibIndices, (m_curNumBibIndices-m_curNumNormalBibIndices)/3, + g_renderBackend->Set_Texture(0,m_highlightBibTexture); + g_renderBackend->Draw_Triangles( m_curNumNormalBibIndices, (m_curNumBibIndices-m_curNumNormalBibIndices)/3, m_curNumNormalBibVertex, m_curNumBibVertices-m_curNumNormalBibVertex); } } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBridgeBuffer.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBridgeBuffer.cpp index a69b827206a..da319003054 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBridgeBuffer.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBridgeBuffer.cpp @@ -66,6 +66,7 @@ #include "W3DDevice/GameClient/W3DShroud.h" #include "WW3D2/camera.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/dx8renderer.h" #include "WW3D2/mesh.h" #include "WW3D2/meshmdl.h" @@ -131,9 +132,11 @@ are already set. */ void W3DBridge::renderBridge(Bool wireframe) { if (m_visible && m_numPolygons && m_numVertex) { - if (!wireframe) DX8Wrapper::Set_Texture(0,m_bridgeTexture); + // TheSuperHackers @refactor bobtista 10/04/2026 Route high-level calls + // through the IRenderBackend abstraction. + if (!wireframe) g_renderBackend->Set_Texture(0,m_bridgeTexture); // Draw all the bridges. - DX8Wrapper::Draw_Triangles( m_firstIndex, m_numPolygons, m_firstVertex, m_numVertex); + g_renderBackend->Draw_Triangles( m_firstIndex, m_numPolygons, m_firstVertex, m_numVertex); } } @@ -1152,16 +1155,18 @@ void W3DBridgeBuffer::drawBridges(CameraClass * camera, Bool wireframe, TextureC return; } - DX8Wrapper::Set_Material(m_vertexMaterial); + // TheSuperHackers @refactor bobtista 10/04/2026 Route high-level calls + // through the IRenderBackend abstraction. + g_renderBackend->Set_Material(m_vertexMaterial); // Setup the vertex buffer, shader & texture. - DX8Wrapper::Set_Index_Buffer(m_indexBridge,0); - DX8Wrapper::Set_Vertex_Buffer(m_vertexBridge); - DX8Wrapper::Set_Shader(detailAlphaShader); + g_renderBackend->Set_Index_Buffer(m_indexBridge,0); + g_renderBackend->Set_Vertex_Buffer(m_vertexBridge,0); + g_renderBackend->Set_Shader(detailAlphaShader); #ifdef RTS_DEBUG //DX8Wrapper::Set_Shader(detailShader); // shows alpha clipping. #endif - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); if (!wireframe && cloudTexture) { //Force a cloud texture projection into stage 1 @@ -1183,12 +1188,14 @@ void W3DBridgeBuffer::drawBridges(CameraClass * camera, Bool wireframe, TextureC if (!wireframe && TheTerrainRenderObject->getShroud()) { //Reset to a known shader. - DX8Wrapper::Invalidate_Cached_Render_States(); - DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); - DX8Wrapper::Set_Material(m_vertexMaterial); - DX8Wrapper::Set_Index_Buffer(m_indexBridge,0); - DX8Wrapper::Set_Vertex_Buffer(m_vertexBridge); - DX8Wrapper::Apply_Render_State_Changes(); + // TheSuperHackers @refactor bobtista 10/04/2026 Route high-level calls + // through the IRenderBackend abstraction. + g_renderBackend->Invalidate_Cached_Render_States(); + g_renderBackend->Set_Shader(ShaderClass::_PresetOpaqueShader); + g_renderBackend->Set_Material(m_vertexMaterial); + g_renderBackend->Set_Index_Buffer(m_indexBridge,0); + g_renderBackend->Set_Vertex_Buffer(m_vertexBridge,0); + g_renderBackend->Apply_Render_State_Changes(); //Apply custom shroud projection shader. W3DShaderManager::setTexture(0,TheTerrainRenderObject->getShroud()->getShroudTexture()); W3DShaderManager::setShader(W3DShaderManager::ST_SHROUD_TEXTURE, 0); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDebugIcons.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDebugIcons.cpp index f41be9aa6bf..cd156437043 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDebugIcons.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDebugIcons.cpp @@ -52,6 +52,7 @@ #include "GameLogic/GameLogic.h" #include "Common/MapObject.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" #if defined(RTS_DEBUG) @@ -215,14 +216,16 @@ void W3DDebugIcons::Render(RenderInfoClass & rinfo) // Bool anyVanished = false; if (m_numDebugIcons==0) return; - DX8Wrapper::Apply_Render_State_Changes(); + // TheSuperHackers @refactor bobtista 10/04/2026 Route high-level calls + // through the IRenderBackend abstraction. + g_renderBackend->Apply_Render_State_Changes(); - DX8Wrapper::Set_Material(m_vertexMaterialClass); - DX8Wrapper::Set_Texture(0, nullptr); - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Set_Material(m_vertexMaterialClass); + g_renderBackend->Set_Texture(0, nullptr); + g_renderBackend->Apply_Render_State_Changes(); Matrix3D tm(Transform); - DX8Wrapper::Set_Transform(D3DTS_WORLD,tm); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,tm); Int numRect = m_numDebugIcons; static Real offset = 30; @@ -303,10 +306,10 @@ void W3DDebugIcons::Render(RenderInfoClass & rinfo) } } if (numVertex == 0) break; - DX8Wrapper::Set_Shader(ShaderClass(SC_ALPHA)); - DX8Wrapper::Set_Index_Buffer(ib_access,0); - DX8Wrapper::Set_Vertex_Buffer(vb_access); - DX8Wrapper::Draw_Triangles( 0,curIndex/3, 0, numVertex); //draw a quad, 2 triangles, 4 verts + g_renderBackend->Set_Shader(ShaderClass(SC_ALPHA)); + g_renderBackend->Set_Index_Buffer(ib_access,0); + g_renderBackend->Set_Vertex_Buffer(vb_access); + g_renderBackend->Draw_Triangles( 0,curIndex/3, 0, numVertex); //draw a quad, 2 triangles, 4 verts } if (anyVanished) { diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DInGameUI.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DInGameUI.cpp index d1fb63d2af4..7060e248fe6 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DInGameUI.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DInGameUI.cpp @@ -58,6 +58,7 @@ #include "W3DDevice/GameClient/HeightMap.h" #include "WW3D2/dx8indexbuffer.h" #include "WW3D2/dx8vertexbuffer.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/vertmaterial.h" class DebugHintObject : public RenderObjClass { @@ -240,18 +241,20 @@ void DebugHintObject::Render(RenderInfoClass & rinfo) SphereClass bounds(Vector3(m_myLoc.x, m_myLoc.y, m_myLoc.z), m_mySize); if (!rinfo.Camera.Cull_Sphere(bounds)) { - DX8Wrapper::Set_Material(m_vertexMaterialClass); - DX8Wrapper::Set_Shader(m_shaderClass); - DX8Wrapper::Set_Texture(0, nullptr); - DX8Wrapper::Set_Index_Buffer(m_indexBuffer,0); - DX8Wrapper::Set_Vertex_Buffer(m_vertexBufferTile); + // TheSuperHackers @refactor bobtista 10/04/2026 Route high-level calls + // through the IRenderBackend abstraction. + g_renderBackend->Set_Material(m_vertexMaterialClass); + g_renderBackend->Set_Shader(m_shaderClass); + g_renderBackend->Set_Texture(0, nullptr); + g_renderBackend->Set_Index_Buffer(m_indexBuffer,0); + g_renderBackend->Set_Vertex_Buffer(m_vertexBufferTile,0); Matrix3D tm(Transform); Vector3 vec(m_myLoc.x, m_myLoc.y, m_myLoc.z); tm.Set_Translation(vec); - DX8Wrapper::Set_Transform(D3DTS_WORLD, tm); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD, tm); - DX8Wrapper::Draw_Triangles( 0, 1, 0, 3); + g_renderBackend->Draw_Triangles( 0, 1, 0, 3); } } #endif // RTS_DEBUG diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DRoadBuffer.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DRoadBuffer.cpp index 15c3d347d76..46a61ed6654 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DRoadBuffer.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DRoadBuffer.cpp @@ -63,6 +63,7 @@ #include "W3DDevice/GameClient/W3DShaderManager.h" #include "WW3D2/camera.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/dx8renderer.h" #include "WW3D2/mesh.h" #include "WW3D2/meshmdl.h" @@ -159,8 +160,10 @@ RoadType::~RoadType() void RoadType::applyTexture() { W3DShaderManager::setTexture(0,m_roadTexture); - DX8Wrapper::Set_Index_Buffer(m_indexRoad,0); - DX8Wrapper::Set_Vertex_Buffer(m_vertexRoad); + // TheSuperHackers @refactor bobtista 10/04/2026 Route high-level calls + // through the IRenderBackend abstraction. + g_renderBackend->Set_Index_Buffer(m_indexRoad,0); + g_renderBackend->Set_Vertex_Buffer(m_vertexRoad,0); } @@ -3331,9 +3334,11 @@ void W3DRoadBuffer::drawRoads(CameraClass * camera, TextureClass *cloudTexture, m_curRoadType = i; if (loadBuffers) loadRoadsInVertexAndIndexBuffers(); if (m_roadTypes[i].getNumIndices() == 0) continue; + // TheSuperHackers @refactor bobtista 10/04/2026 Route high-level calls + // through the IRenderBackend abstraction. if (wireframe) { m_roadTypes[i].applyTexture(); - DX8Wrapper::Set_Texture(0,nullptr); + g_renderBackend->Set_Texture(0,nullptr); } else { m_roadTypes[i].applyTexture(); } @@ -3345,7 +3350,7 @@ void W3DRoadBuffer::drawRoads(CameraClass * camera, TextureClass *cloudTexture, if (!wireframe) W3DShaderManager::setShader(st, pass); //Draw all this road type. - DX8Wrapper::Draw_Triangles( 0, m_roadTypes[i].getNumIndices()/3, 0, m_roadTypes[i].getNumVertices()); + g_renderBackend->Draw_Triangles( 0, m_roadTypes[i].getNumIndices()/3, 0, m_roadTypes[i].getNumVertices()); #ifdef LOG_STATS polys += m_roadTypes[i].getNumIndices()/3; #endif @@ -3363,8 +3368,8 @@ void W3DRoadBuffer::drawRoads(CameraClass * camera, TextureClass *cloudTexture, #if 0 // Need to use a separate set of index & vertex buffers for this. jba. - DX8Wrapper::Set_Index_Buffer(nullptr,0); - DX8Wrapper::Set_Vertex_Buffer(nullptr); + g_renderBackend->Set_Index_Buffer(nullptr,0); + g_renderBackend->Set_Vertex_Buffer(nullptr,0); if (pDynamicLightsIterator) { for (i=0; im_curNumRoadIndices == 0) continue; if (wireframe) { - DX8Wrapper::Set_Texture(0,nullptr); + g_renderBackend->Set_Texture(0,nullptr); } else { m_roadTypes[i].applyTexture(); if (cloudTexture) { - DX8Wrapper::Set_Texture(1,cloudTexture); + g_renderBackend->Set_Texture(1,cloudTexture); } } - DX8Wrapper::Set_Shader(detailAlphaShader); + g_renderBackend->Set_Shader(detailAlphaShader); //Draw all the roads. - DX8Wrapper::Draw_Triangles( 0, m_curNumRoadIndices/3, 0, m_curNumRoadVertices); + g_renderBackend->Draw_Triangles( 0, m_curNumRoadIndices/3, 0, m_curNumRoadVertices); } } #endif diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp index d22790fd6d7..d21d59e4fa6 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp @@ -58,6 +58,7 @@ #include "WW3D2/dx8renderer.h" #include "WW3D2/sortingrenderer.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/light.h" #include "WW3D2/matpass.h" #include "WW3D2/shader.h" @@ -859,7 +860,7 @@ void RTS3DScene::Flush(RenderInfoClass & rinfo) #ifdef USE_NON_STENCIL_OCCLUSION flushOccludedObjects(rinfo); #else - if (DX8Wrapper::Has_Stencil()) + if (g_renderBackend->Has_Stencil()) flushOccludedObjectsIntoStencil(rinfo); #endif @@ -972,10 +973,10 @@ void RTS3DScene::updatePlayerColorPasses() void RTS3DScene::Render(RenderInfoClass & rinfo) { //USE_PERF_TIMER(NonTerrainRender) - DX8Wrapper::Set_Fog(FogEnabled, FogColor, FogStart, FogEnd); + g_renderBackend->Set_Fog(FogEnabled, FogColor, FogStart, FogEnd); //Override the behind building selection if it's not available on current hardware (needs stencil). - TheWritableGlobalData->m_enableBehindBuildingMarkers = TheWritableGlobalData->m_enableBehindBuildingMarkers && DX8Wrapper::Has_Stencil(); + TheWritableGlobalData->m_enableBehindBuildingMarkers = TheWritableGlobalData->m_enableBehindBuildingMarkers && g_renderBackend->Has_Stencil(); if (Get_Extra_Pass_Polygon_Mode() == EXTRA_PASS_DISABLE) { @@ -1016,7 +1017,7 @@ void RTS3DScene::Render(RenderInfoClass & rinfo) //a projected alpha texture which will later be used to determine where //wireframe should be visible. ///@todo: Clearing to black may not be needed if the scene already did the clear. - DX8Wrapper::Clear(true, false, Vector3(0.0f,0.0f,0.0f),1.0f); // Clear color but not z + g_renderBackend->Clear(true, false, Vector3(0.0f,0.0f,0.0f),1.0f); // Clear color but not z DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_ALPHA); DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 0); @@ -1076,7 +1077,7 @@ void RTS3DScene::Render(RenderInfoClass & rinfo) Customized_Render(rinfo); break; case EXTRA_PASS_CLEAR_LINE: - DX8Wrapper::Clear(true, false, Vector3(0.0f,0.0f,0.0f), 0.0f); // Clear color but not z + g_renderBackend->Clear(true, false, Vector3(0.0f,0.0f,0.0f), 0.0f); // Clear color but not z WW3D::Enable_Texturing(false); WW3D::Enable_Coloring(0xff008000); DX8Wrapper::Set_DX8_Render_State(D3DRS_FILLMODE,D3DFILL_WIREFRAME); @@ -1263,11 +1264,19 @@ void renderStenciledPlayerColor( UnsignedInt color, UnsignedInt stencilRef, Bool v[2].color = color; v[3].color = color; - DX8Wrapper::Set_Shader(PlayerColorShader); + // TheSuperHackers @refactor bobtista 10/04/2026 partial migration: + // the high-level Set_Shader/Set_Material/Apply_Render_State_Changes calls + // and all the stencil + alpha-blend state setters in this function (and + // the rest of W3DScene.cpp) are routed through g_renderBackend via the + // new stencil state extension. The remaining low-level Set_DX8_Render_State + // calls (D3DRS_ZBIAS, COLORWRITEENABLE, FILLMODE, ZENABLE/ZFUNC, + // SRCBLEND/DESTBLEND pairs, AMBIENT) and the raw m_pDev->* device pointer + // access points stay on DX8Wrapper::* for now. + g_renderBackend->Set_Shader(PlayerColorShader); VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); - DX8Wrapper::Apply_Render_State_Changes(); //force update all render states + g_renderBackend->Apply_Render_State_Changes(); //force update all render states LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); @@ -1279,20 +1288,20 @@ void renderStenciledPlayerColor( UnsignedInt color, UnsignedInt stencilRef, Bool m_pDev->SetVertexShader(D3DFVF_XYZRHW | D3DFVF_DIFFUSE); // Set stencil states - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILENABLE, TRUE ); + g_renderBackend->Set_Stencil_Enable(true); DX8Wrapper::Set_DX8_Render_State(D3DRS_ZENABLE, TRUE ); DWORD oldColorWriteEnable=0x12345678; if (clear) { //we want to clear the stencil buffer to some known value wherever a player index is stored Int occludedMask=TheW3DShadowManager->getStencilShadowMask(); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILREF, 0x80808080 ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILMASK, occludedMask ); //isolate bits containing occluder|playerIndex - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILWRITEMASK,0xffffffff ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFUNC, D3DCMP_LESS ); //only draw to pixels that match the reference value - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILZFAIL, D3DSTENCILOP_REPLACE ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE ); //pixels which had occluded player colors, get MSB set. - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFAIL, D3DSTENCILOP_ZERO ); //pixels which had no occluded player colors are cleared. + g_renderBackend->Set_Stencil_Ref(0x80808080); + g_renderBackend->Set_Stencil_Mask(occludedMask); //isolate bits containing occluder|playerIndex + g_renderBackend->Set_Stencil_Write_Mask(0xffffffff); + g_renderBackend->Set_Stencil_Func(RB_CMP_LESS); //only draw to pixels that match the reference value + g_renderBackend->Set_Stencil_ZFail_Op(RB_STENCIL_OP_REPLACE); + g_renderBackend->Set_Stencil_Pass_Op(RB_STENCIL_OP_REPLACE); //pixels which had occluded player colors, get MSB set. + g_renderBackend->Set_Stencil_Fail_Op(RB_STENCIL_OP_ZERO); //pixels which had no occluded player colors are cleared. DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC, D3DCMP_NEVER ); //fail all access to the frame buffer to improve memory bandwidth //disable writes to color buffer @@ -1304,35 +1313,32 @@ void renderStenciledPlayerColor( UnsignedInt color, UnsignedInt stencilRef, Bool else { //device does not support disabling writes to color buffer so fake it through alpha blending - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE, TRUE); - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_ZERO ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, D3DBLEND_ONE ); + g_renderBackend->Set_Alpha_Blend_Enable(true); + g_renderBackend->Set_Blend_Factors(RB_BLEND_ZERO, RB_BLEND_ONE); } } else { - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILREF, stencilRef ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILMASK, 0xffffffff ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILWRITEMASK,0xffffffff ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFUNC, D3DCMP_EQUAL ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ); + g_renderBackend->Set_Stencil_Ref(stencilRef); + g_renderBackend->Set_Stencil_Mask(0xffffffff); + g_renderBackend->Set_Stencil_Write_Mask(0xffffffff); + g_renderBackend->Set_Stencil_Func(RB_CMP_EQUAL); + g_renderBackend->Set_Stencil_ZFail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Pass_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Fail_Op(RB_STENCIL_OP_KEEP); //Make occluded pixels transparent - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE, TRUE); - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); + g_renderBackend->Set_Alpha_Blend_Enable(true); + g_renderBackend->Set_Blend_Factors(RB_BLEND_SRC_ALPHA, RB_BLEND_INV_SRC_ALPHA); } if (DX8Wrapper::_Is_Triangle_Draw_Enabled()) m_pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANSLITVERTEX)); // turn off the stencil buffer - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILENABLE, FALSE ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE, FALSE); //restore shader state - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_ONE ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, D3DBLEND_ZERO ); + g_renderBackend->Set_Stencil_Enable(false); + g_renderBackend->Set_Alpha_Blend_Enable(false); //restore shader state + g_renderBackend->Set_Blend_Factors(RB_BLEND_ONE, RB_BLEND_ZERO); DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC, D3DCMP_ALWAYS); if (oldColorWriteEnable != 0x12345678) @@ -1392,16 +1398,16 @@ void RTS3DScene::flushOccludedObjectsIntoStencil(RenderInfoClass & rinfo) lastPlayerObject[index]++; //increment to next object } - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILENABLE, TRUE ); + g_renderBackend->Set_Stencil_Enable(true); DX8Wrapper::Set_DX8_Render_State(D3DRS_ZENABLE, TRUE ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILMASK, 0xffffffff); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILWRITEMASK, 0xffffffff); + g_renderBackend->Set_Stencil_Mask(0xffffffff); + g_renderBackend->Set_Stencil_Write_Mask(0xffffffff); //Always store player index into stencil unless it is occluded by another //player's potentially occluded objects. - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFUNC, D3DCMP_ALWAYS ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE ); + g_renderBackend->Set_Stencil_Func(RB_CMP_ALWAYS); + g_renderBackend->Set_Stencil_ZFail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Fail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Pass_Op(RB_STENCIL_OP_REPLACE); //Find out which player indices are actually used and remap them to //a color index. Render all objects using the same color index at once. @@ -1431,7 +1437,7 @@ void RTS3DScene::flushOccludedObjectsIntoStencil(RenderInfoClass & rinfo) Int thisPlayerColorIndex=playerColorIndex[k]; //Store this object's color index into bits 3-6 of stencil buffer - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILREF, thisPlayerColorIndex<<3); + g_renderBackend->Set_Stencil_Ref(thisPlayerColorIndex<<3); //Render all of this player's objects for which we care when they are occluded. RenderObjClass **renderList=&playerObjects[k][0]; @@ -1444,13 +1450,13 @@ void RTS3DScene::flushOccludedObjectsIntoStencil(RenderInfoClass & rinfo) TheDX8MeshRenderer.Flush(); //render all the submitted meshes using current stencil function SHD_FLUSH; //Disable writing to color buffer since translucent objects are rendered at end of frame. - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFUNC, D3DCMP_NEVER ); //never allow frame buffer writes. - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFAIL, D3DSTENCILOP_REPLACE ); //always replace existing stencil value + g_renderBackend->Set_Stencil_Func(RB_CMP_NEVER); //never allow frame buffer writes. + g_renderBackend->Set_Stencil_Fail_Op(RB_STENCIL_OP_REPLACE); //always replace existing stencil value renderOneObject(rinfo, (*renderList), localPlayerIndex); TheDX8MeshRenderer.Flush(); //render all the submitted meshes using current stencil function SHD_FLUSH; - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFUNC, D3DCMP_ALWAYS ); + g_renderBackend->Set_Stencil_Fail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Func(RB_CMP_ALWAYS); } else { @@ -1465,7 +1471,7 @@ void RTS3DScene::flushOccludedObjectsIntoStencil(RenderInfoClass & rinfo) //Stencil buffer is now filled with color indices of potentially occluded objects. We now draw //non-occluder or occludee objects such as small rocks, shrubs, etc. which we don't care about //but need to render here so that they don't interfere with building occlusion. - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILENABLE, FALSE ); //these objects are not stored in stencil + g_renderBackend->Set_Stencil_Enable(false); //these objects are not stored in stencil RenderObjClass **nonOccluderOrOccludeeList=m_nonOccludersOrOccludees; for (k=0; kSet_Stencil_Enable(true); DX8Wrapper::Set_DX8_Render_State(D3DRS_ZENABLE, TRUE ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILREF, 0xffffffff); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILMASK, 0xffffffff); //isolate lowest player color - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILWRITEMASK, 0x80); //only write to MSB - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFUNC, D3DCMP_ALWAYS ); //check if player colors stored in pixel - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE ); + g_renderBackend->Set_Stencil_Ref(0xffffffff); + g_renderBackend->Set_Stencil_Mask(0xffffffff); //isolate lowest player color + g_renderBackend->Set_Stencil_Write_Mask(0x80); //only write to MSB + g_renderBackend->Set_Stencil_Func(RB_CMP_ALWAYS); //check if player colors stored in pixel + g_renderBackend->Set_Stencil_ZFail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Fail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Pass_Op(RB_STENCIL_OP_REPLACE); //Render all potential occluders on top of already rendered potential occludees. RenderObjClass **occluderList=m_potentialOccluders; @@ -1519,7 +1525,7 @@ void RTS3DScene::flushOccludedObjectsIntoStencil(RenderInfoClass & rinfo) TheW3DShadowManager->setStencilShadowMask(0x80808080); //msb indicates occluded player pixels so ignore it when filling screen with shadow } - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILENABLE, FALSE ); + g_renderBackend->Set_Stencil_Enable(false); } else if (m_numNonOccluderOrOccludee || m_numPotentialOccluders || m_numPotentialOccludees) @@ -1575,18 +1581,18 @@ void RTS3DScene::flushOccludedObjects(RenderInfoClass & rinfo) { const Int localPlayerIndex = rts::getObservedOrLocalPlayerIndex_Safe(); - if (DX8Wrapper::Has_Stencil()) //just in case we have shadows, disable them over occluded pixels. + if (g_renderBackend->Has_Stencil()) //just in case we have shadows, disable them over occluded pixels. { //Set all stencil pixels of potentially occluded objects to 128. - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILENABLE, TRUE ); + g_renderBackend->Set_Stencil_Enable(true); DX8Wrapper::Set_DX8_Render_State(D3DRS_ZENABLE, TRUE ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILREF, 128 ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILMASK, 0xffffffff ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILWRITEMASK,0xffffffff ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFUNC, D3DCMP_ALWAYS ); + g_renderBackend->Set_Stencil_Ref(128); + g_renderBackend->Set_Stencil_Mask(0xffffffff); + g_renderBackend->Set_Stencil_Write_Mask(0xffffffff); + g_renderBackend->Set_Stencil_ZFail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Fail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Pass_Op(RB_STENCIL_OP_REPLACE); + g_renderBackend->Set_Stencil_Func(RB_CMP_ALWAYS); } //First draw all the solid colored models @@ -1614,8 +1620,8 @@ void RTS3DScene::flushOccludedObjects(RenderInfoClass & rinfo) //Now draw the normal models so they cover up the colored models on any pixels that //Normal models will clear stencil value from 128 back to 0 where the object pixels are //not occluded but will leave 128 in stencil where still occluded. - if (DX8Wrapper::Has_Stencil()) - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILREF, 0 ); + if (g_renderBackend->Has_Stencil()) + g_renderBackend->Set_Stencil_Ref(0); for (i=0; iSet_Stencil_Enable(false); TheW3DShadowManager->setStencilShadowMask(0x80808080); //upper MSB always contains flag indicating occluded player color. } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp index 8af10e2efe2..e6ac47d4c7c 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp @@ -31,6 +31,8 @@ #include "WW3D2/camera.h" #include "WWLib/simplevec.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/surfaceclass.h" #include "Common/MapObject.h" #include "Common/PerfTimer.h" #include "W3DDevice/GameClient/HeightMap.h" @@ -93,8 +95,7 @@ W3DShroud::~W3DShroud() { ReleaseResources(); - if (m_pSrcTexture) - m_pSrcTexture->Release(); + REF_PTR_RELEASE(m_pSrcTexture); delete [] m_finalFogData; delete [] m_currentFogData; @@ -155,26 +156,27 @@ void W3DShroud::init(WorldHeightMap *pMap, Real worldCellSizeX, Real worldCellSi memset(m_finalFogData,0,srcWidth*srcHeight); #endif + // TheSuperHackers @refactor bobtista 10/04/2026 Allocate the + // shroud sysmem surface through SurfaceClass instead of raw IDirect3DSurface8. + // SurfaceClass's (w, h, format) constructor wraps _Create_DX8_Surface so the + // underlying allocation is identical and the lock-then-cache-pointer trick + // the rest of this file relies on continues to work. #if defined(RTS_DEBUG) if (TheGlobalData && TheGlobalData->m_fogOfWarOn) - m_pSrcTexture = DX8Wrapper::_Create_DX8_Surface(srcWidth,srcHeight, WW3D_FORMAT_A4R4G4B4); + m_pSrcTexture = new SurfaceClass(srcWidth, srcHeight, WW3D_FORMAT_A4R4G4B4); else #endif - m_pSrcTexture = DX8Wrapper::_Create_DX8_Surface(srcWidth,srcHeight, WW3D_FORMAT_R5G6B5); + m_pSrcTexture = new SurfaceClass(srcWidth, srcHeight, WW3D_FORMAT_R5G6B5); DEBUG_ASSERTCRASH( m_pSrcTexture != nullptr, ("Failed to Allocate Shroud Src Surface")); - D3DLOCKED_RECT rect; - - //Get a pointer to source surface pixels. - HRESULT res = m_pSrcTexture->LockRect(&rect,nullptr,D3DLOCK_NO_DIRTY_UPDATE); - m_pSrcTexture->UnlockRect(); - - DEBUG_ASSERTCRASH( res == D3D_OK, ("Failed to lock shroud src surface")); - res = 0;// just to avoid compiler warnings - - m_srcTextureData=rect.pBits; - m_srcTexturePitch=rect.Pitch; + //Get a pointer to source surface pixels. We lock-then-unlock and keep the + //pointer alive for the lifetime of the surface, matching the original DX8 + //behavior; the system-memory backing stays valid after the unlock. + int srcPitch = 0; + m_srcTextureData = m_pSrcTexture->Lock(&srcPitch); + m_pSrcTexture->Unlock(); + m_srcTexturePitch = static_cast(srcPitch); //clear entire texture to black memset(m_srcTextureData,0,m_srcTexturePitch*srcHeight); @@ -203,11 +205,7 @@ void W3DShroud::init(WorldHeightMap *pMap, Real worldCellSizeX, Real worldCellSi void W3DShroud::reset() { //Free old shroud data since it may no longer fit new map. - if (m_pSrcTexture) - { - m_pSrcTexture->Release(); - m_pSrcTexture=nullptr; - } + REF_PTR_RELEASE(m_pSrcTexture); delete [] m_finalFogData; m_finalFogData=nullptr; @@ -476,27 +474,31 @@ void W3DShroud::fillBorderShroudData(W3DShroudLevel level, SurfaceClass* pDestSu dstPoint.y=y; dstPoint.x=0; + // TheSuperHackers @refactor bobtista 10/04/2026 Replace + // _Copy_DX8_Rects with SurfaceClass::Copy. The src/dest math is the + // same; SurfaceClass::Copy(dstx, dsty, srcx, srcy, w, h, src) maps + // directly onto the (srcRect, dstPoint) pair the original DX8 call used. for (x=0; xPeek_D3D_Surface(), - &dstPoint); + pDestSurface->Copy( + dstPoint.x, dstPoint.y, + srcRect.left, srcRect.top, + srcRect.right - srcRect.left, + srcRect.bottom - srcRect.top, + m_pSrcTexture); } if (numExtraPixels) { Int oldVal=srcRect.right; dstPoint.x = numFullCopies * oldVal; srcRect.right = numExtraPixels; - DX8Wrapper::_Copy_DX8_Rects( - m_pSrcTexture, - &srcRect, - 1, - pDestSurface->Peek_D3D_Surface(), - &dstPoint); + pDestSurface->Copy( + dstPoint.x, dstPoint.y, + srcRect.left, srcRect.top, + srcRect.right - srcRect.left, + srcRect.bottom - srcRect.top, + m_pSrcTexture); srcRect.right = oldVal; } } @@ -526,7 +528,11 @@ void W3DShroud::render(CameraClass *cam) if (!m_pSrcTexture) return; //nothing to update from. Must be in reset state. - if (DX8Wrapper::_Get_D3D_Device8() && (DX8Wrapper::_Get_D3D_Device8()->TestCooperativeLevel()) != D3D_OK) + // TheSuperHackers @refactor bobtista 10/04/2026 Replaced the + // raw _Get_D3D_Device8()->TestCooperativeLevel() check with the abstracted + // device-lost flag on IRenderBackend. Same intent: skip rendering this + // frame if the device isn't ready. + if (g_renderBackend->Is_Device_Lost()) return; //device not ready to render anything #if defined(RTS_DEBUG) @@ -711,12 +717,14 @@ void W3DShroud::render(CameraClass *cam) { //USE_PERF_TIMER(shroudCopy) - DX8Wrapper::_Copy_DX8_Rects( - m_pSrcTexture, - &srcRect, - 1, - pDestSurface->Peek_D3D_Surface(), - &dstPoint); + // TheSuperHackers @refactor bobtista 10/04/2026 SurfaceClass::Copy + // in place of _Copy_DX8_Rects. + pDestSurface->Copy( + dstPoint.x, dstPoint.y, + srcRect.left, srcRect.top, + srcRect.right - srcRect.left, + srcRect.bottom - srcRect.top, + m_pSrcTexture); } REF_PTR_RELEASE (pDestSurface); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DStatusCircle.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DStatusCircle.cpp index a0d7fef13e3..42cb5f7fedb 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DStatusCircle.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DStatusCircle.cpp @@ -317,11 +317,9 @@ void W3DStatusCircle::Render(RenderInfoClass & rinfo) if (m_needUpdate) { updateCircleVB(); } - // TheSuperHackers @refactor bobtista 10/04/2026 First IRenderBackend call site - // migrated to IRenderBackend. High-level W3D-facing calls route through - // the global backend pointer; the remaining raw D3DRS_* state calls - // below still go through DX8Wrapper:: directly because IRenderBackend - // does not expose the low-level D3D8 state API yet. + // TheSuperHackers @refactor bobtista 10/04/2026 introduced the + // IRenderBackend migration for this function; completed by + // routing the fade blend-op overrides through the new interface API. // See Core/Libraries/Source/WWVegas/WW3D2/RENDER_BACKEND.md. //Apply the shader and material @@ -368,22 +366,21 @@ void W3DStatusCircle::Render(RenderInfoClass & rinfo) g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); break; case ScriptEngine::FADE_SUBTRACT: - // Low-level blend op not yet abstracted by IRenderBackend (D3DRS_* skipped). - DX8Wrapper::Set_DX8_Render_State(D3DRS_BLENDOP, D3DBLENDOP_REVSUBTRACT ); + // TheSuperHackers @refactor bobtista 10/04/2026 Route the remaining + // blend-op override through the IRenderBackend blend extension. + g_renderBackend->Set_Blend_Op(RB_BLEND_OP_REV_SUBTRACT); g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); - DX8Wrapper::Set_DX8_Render_State(D3DRS_BLENDOP, D3DBLENDOP_ADD ); + g_renderBackend->Set_Blend_Op(RB_BLEND_OP_ADD); break; case ScriptEngine::FADE_SATURATE: // 4x multiply - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_DESTCOLOR); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_SRCCOLOR); + g_renderBackend->Set_Blend_Factors(RB_BLEND_DEST_COLOR, RB_BLEND_SRC_COLOR); g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); break; case ScriptEngine::FADE_MULTIPLY: // Straight multiply - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_ZERO); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_SRCCOLOR); + g_renderBackend->Set_Blend_Factors(RB_BLEND_ZERO, RB_BLEND_SRC_COLOR); g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); break; } From 10d5d6636c5b3c7c5928ac11eabac11371f60af6 Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 21 Apr 2026 14:02:46 -0400 Subject: [PATCH 039/523] refactor(ww3d): deprecate DX8Wrapper game-code callers in favor of IRenderBackend (cherry picked from commit d1c364c4e077db8d295908a9febc7c4ad61139d6) --- .../W3DDevice/GameClient/BaseHeightMap.cpp | 44 ++--- .../Source/W3DDevice/GameClient/HeightMap.cpp | 90 +++++----- .../W3DDevice/GameClient/W3DShaderManager.cpp | 157 +++++++++--------- .../Source/W3DDevice/GameClient/W3DSmudge.cpp | 32 ++-- .../Source/W3DDevice/GameClient/W3DSnow.cpp | 20 ++- .../GameClient/W3DTerrainBackground.cpp | 26 +-- .../W3DDevice/GameClient/W3DTreeBuffer.cpp | 44 ++--- .../Source/W3DDevice/GameClient/W3DView.cpp | 4 +- .../W3DDevice/GameClient/Water/W3DWater.cpp | 133 +++++++-------- .../GameClient/Water/W3DWaterTracks.cpp | 20 ++- .../Source/WWVegas/WW3D2/IRenderBackend.h | 6 +- .../Source/WWVegas/WW3D2/dx8wrapper.h | 104 +++++++----- .../W3DDevice/GameClient/W3DCustomEdging.cpp | 56 ++++--- .../W3DDevice/GameClient/W3DDisplay.cpp | 4 +- .../Source/WWVegas/WW3D2/CMakeLists.txt | 1 + Generals/Code/Tools/W3DView/CMakeLists.txt | 2 + .../Code/Tools/WorldBuilder/CMakeLists.txt | 2 +- .../W3DDevice/GameClient/W3DCustomEdging.cpp | 56 ++++--- .../W3DDevice/GameClient/W3DDisplay.cpp | 4 +- .../Source/WWVegas/WW3D2/CMakeLists.txt | 1 + GeneralsMD/Code/Tools/W3DView/CMakeLists.txt | 2 + .../Code/Tools/WorldBuilder/CMakeLists.txt | 2 +- 22 files changed, 427 insertions(+), 383 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp index 6a47cbf4775..fa99aa16a09 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp @@ -83,6 +83,8 @@ #include "W3DDevice/GameClient/W3DWater.h" #include "W3DDevice/GameClient/W3DShroud.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/light.h" #include "WW3D2/scene.h" #include "W3DDevice/GameClient/W3DPoly.h" @@ -176,13 +178,13 @@ void BaseHeightMapRenderObjClass::drawScorches() if (m_curNumScorchIndices == 0) { return; } - DX8Wrapper::Set_Index_Buffer(m_indexScorch,0); - DX8Wrapper::Set_Vertex_Buffer(m_vertexScorch); - DX8Wrapper::Set_Shader(ShaderClass::_PresetAlphaShader); + g_renderBackend->Set_Index_Buffer(m_indexScorch,0); + g_renderBackend->Set_Vertex_Buffer(m_vertexScorch); + g_renderBackend->Set_Shader(ShaderClass::_PresetAlphaShader); - DX8Wrapper::Set_Texture(0,m_scorchTexture); + g_renderBackend->Set_Texture(0,m_scorchTexture); if (Is_Hidden() == 0) { - DX8Wrapper::Draw_Triangles( 0,m_curNumScorchIndices/3, 0, m_curNumScorchVertices); + g_renderBackend->Draw_Triangles( 0,m_curNumScorchIndices/3, 0, m_curNumScorchVertices); } } #endif @@ -2478,12 +2480,12 @@ void BaseHeightMapRenderObjClass::renderShoreLines(CameraClass *pCamera) ShaderClass unlitShader=ShaderClass::_PresetOpaque2DShader; unlitShader.Set_Depth_Compare(ShaderClass::PASS_LEQUAL); - DX8Wrapper::Set_Shader(unlitShader); + g_renderBackend->Set_Shader(unlitShader); VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); - DX8Wrapper::Set_Texture(0,m_destAlphaTexture); - DX8Wrapper::Set_Transform(D3DTS_WORLD,Matrix3D(true)); + g_renderBackend->Set_Texture(0,m_destAlphaTexture); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,Matrix3D(true)); //Enabled writes to destination alpha only DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_ALPHA); DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_TEXCOORDINDEX, 0); @@ -2597,9 +2599,9 @@ void BaseHeightMapRenderObjClass::renderShoreLines(CameraClass *pCamera) if (indexCount > 0 && vertexCount > 0) { - DX8Wrapper::Set_Index_Buffer(ib_access,0); - DX8Wrapper::Set_Vertex_Buffer(vb_access); - DX8Wrapper::Draw_Triangles( 0,indexCount/3, 0, vertexCount); //draw a quad, 2 triangles, 4 verts + g_renderBackend->Set_Index_Buffer(ib_access,0); + g_renderBackend->Set_Vertex_Buffer(vb_access); + g_renderBackend->Draw_Triangles( 0,indexCount/3, 0, vertexCount); //draw a quad, 2 triangles, 4 verts m_numVisibleShoreLineTiles += indexCount/6; } @@ -2662,12 +2664,12 @@ void BaseHeightMapRenderObjClass::renderShoreLinesSorted(CameraClass *pCamera) ShaderClass unlitShader=ShaderClass::_PresetOpaque2DShader; unlitShader.Set_Depth_Compare(ShaderClass::PASS_LEQUAL); - DX8Wrapper::Set_Shader(unlitShader); + g_renderBackend->Set_Shader(unlitShader); VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); - DX8Wrapper::Set_Texture(0,m_destAlphaTexture); - DX8Wrapper::Set_Transform(D3DTS_WORLD,Matrix3D(true)); + g_renderBackend->Set_Texture(0,m_destAlphaTexture); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,Matrix3D(true)); //Enabled writes to destination alpha only DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_ALPHA); DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_TEXCOORDINDEX, 0); @@ -2937,9 +2939,9 @@ void BaseHeightMapRenderObjClass::renderShoreLinesSorted(CameraClass *pCamera) if (indexCount > 0 && vertexCount > 0) { - DX8Wrapper::Set_Index_Buffer(ib_access,0); - DX8Wrapper::Set_Vertex_Buffer(vb_access); - DX8Wrapper::Draw_Triangles( 0,indexCount/3, 0, vertexCount); //draw a quad, 2 triangles, 4 verts + g_renderBackend->Set_Index_Buffer(ib_access,0); + g_renderBackend->Set_Vertex_Buffer(vb_access); + g_renderBackend->Draw_Triangles( 0,indexCount/3, 0, vertexCount); //draw a quad, 2 triangles, 4 verts m_numVisibleShoreLineTiles += indexCount/6; } @@ -2968,8 +2970,8 @@ void BaseHeightMapRenderObjClass::renderTrees(CameraClass * camera) if (m_map==nullptr) return; if (Scene==nullptr) return; if (m_treeBuffer) { - DX8Wrapper::Set_Transform(D3DTS_WORLD,Transform); - DX8Wrapper::Set_Material(m_vertexMaterialClass); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,Transform); + g_renderBackend->Set_Material(m_vertexMaterialClass); RTS3DScene *pMyScene = (RTS3DScene *)Scene; RefRenderObjListIterator pDynamicLightsIterator(pMyScene->getDynamicLights()); m_treeBuffer->drawTrees(camera, &pDynamicLightsIterator); diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp index e118956987d..73a2f97adf2 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp @@ -84,6 +84,8 @@ #include "W3DDevice/GameClient/W3DWater.h" #include "W3DDevice/GameClient/W3DShroud.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/light.h" #include "WW3D2/scene.h" #include "W3DDevice/GameClient/W3DPoly.h" @@ -1919,20 +1921,20 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) } #endif - DX8Wrapper::Set_Light_Environment(rinfo.light_environment); + g_renderBackend->Set_Light_Environment(rinfo.light_environment); // Force shaders to update. m_stageTwoTexture->restore(); - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Set_Texture(1,nullptr); + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Set_Texture(1,nullptr); ShaderClass::Invalidate(); // tm.Scale(ObjSpaceExtent); - DX8Wrapper::Set_Transform(D3DTS_WORLD,tm); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,tm); //Apply the shader and material - DX8Wrapper::Set_Index_Buffer(m_indexBuffer,0); + g_renderBackend->Set_Index_Buffer(m_indexBuffer,0); Bool doMultiPassWireFrame=FALSE; @@ -1956,11 +1958,11 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) else { //wireframe pass //Set to vertex diffuse lighting - DX8Wrapper::Set_Material(m_vertexMaterialClass); + g_renderBackend->Set_Material(m_vertexMaterialClass); //Set shader to non-textured solid color from vertex - DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueSolidShader); + g_renderBackend->Set_Shader(ShaderClass::_PresetOpaqueSolidShader); devicePasses=1; //one pass solid, next in wireframe. - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG2, D3DTA_TFACTOR ); DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR,0xff808080); doMultiPassWireFrame=TRUE; @@ -1971,8 +1973,8 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) } else { - DX8Wrapper::Set_Material(m_vertexMaterialClass); - DX8Wrapper::Set_Shader(m_shaderClass); + g_renderBackend->Set_Material(m_vertexMaterialClass); + g_renderBackend->Set_Shader(m_shaderClass); st=W3DShaderManager::ST_TERRAIN_BASE; //set default shader @@ -2021,8 +2023,8 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) if (!doMultiPassWireFrame) //multi-pass wireframe doesn't use regular shaders. { if (m_disableTextures ) { - DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaque2DShader); - DX8Wrapper::Set_Texture(0,nullptr); + g_renderBackend->Set_Shader(ShaderClass::_PresetOpaque2DShader); + g_renderBackend->Set_Texture(0,nullptr); } else { W3DShaderManager::setShader(st, pass); } @@ -2031,17 +2033,17 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) for (j=0; jSet_Vertex_Buffer(getVertexBufferTile(i, j)); #ifdef PRE_TRANSFORM_VERTEX if (m_xformedVertexBuffer && pass==0) { // Note - m_xformedVertexBuffer should only be used for non T&L hardware. jba. - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); int code = DX8Wrapper::_Get_D3D_Device8()->ProcessVertices(0, 0, numVertex, m_xformedVertexBuffer[j*m_numVBTilesX+i], 0); ::OutputDebugString("did process vertex\n"); } if (m_xformedVertexBuffer) { // Note - m_xformedVertexBuffer should only be used for non T&L hardware. jba. - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); DX8Wrapper::_Get_D3D_Device8()->SetStreamSource( 0, m_xformedVertexBuffer[j*m_numVBTilesX+i], @@ -2050,7 +2052,7 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) } #endif if (Is_Hidden() == 0) { - DX8Wrapper::Draw_Triangles(0, HEIGHTMAP_POLYGON_NUM, 0, HEIGHTMAP_VERTEX_NUM); + g_renderBackend->Draw_Triangles(0, HEIGHTMAP_POLYGON_NUM, 0, HEIGHTMAP_VERTEX_NUM); } } @@ -2074,20 +2076,20 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) Int xCoordMax = m_x+m_map->getDrawOrgX()-1; #ifdef TEST_CUSTOM_EDGING // Draw edging just before last pass. - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Set_Texture(1,nullptr); + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Set_Texture(1,nullptr); m_stageTwoTexture->restore(); m_customEdging->drawEdging(m_map, xCoordMin, xCoordMax, yCoordMin, yCoordMax, m_stageZeroTexture, doCloud?m_stageTwoTexture: nullptr, TheGlobalData->m_useLightMap?m_stageThreeTexture: nullptr); #endif #ifdef DO_ROADS - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Set_Texture(1,nullptr); + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Set_Texture(1,nullptr); m_stageTwoTexture->restore(); ShaderClass::Invalidate(); if (!ShaderClass::Is_Backface_Culling_Inverted()) { - DX8Wrapper::Set_Material(m_vertexMaterialClass); + g_renderBackend->Set_Material(m_vertexMaterialClass); if (Scene) { RTS3DScene *pMyScene = (RTS3DScene *)Scene; RefRenderObjListIterator pDynamicLightsIterator(pMyScene->getDynamicLights()); @@ -2100,8 +2102,8 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) m_propBuffer->drawProps(rinfo); } #ifdef DO_SCORCH - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Set_Texture(1,nullptr); + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Set_Texture(1,nullptr); m_stageTwoTexture->restore(); ShaderClass::Invalidate(); @@ -2109,11 +2111,11 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) drawScorches(); } #endif - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Set_Texture(1,nullptr); + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Set_Texture(1,nullptr); m_stageTwoTexture->restore(); ShaderClass::Invalidate(); - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); m_bridgeBuffer->drawBridges(&rinfo.Camera, m_disableTextures, doCloud?m_stageTwoTexture:nullptr); @@ -2128,7 +2130,7 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) } ShaderClass::Invalidate(); - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); } else m_bridgeBuffer->drawBridges(&rinfo.Camera, m_disableTextures, m_stageTwoTexture); @@ -2139,11 +2141,11 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) m_bibBuffer->renderBibs(); // We do some custom blending, so tell the shader class to reset everything. - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Set_Texture(1,nullptr); + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Set_Texture(1,nullptr); m_stageTwoTexture->restore(); ShaderClass::Invalidate(); - DX8Wrapper::Set_Material(nullptr); + g_renderBackend->Set_Material(nullptr); } @@ -2152,26 +2154,26 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) ///Performs additional terrain rendering pass, blending in the black shroud texture. void HeightMapRenderObjClass::renderTerrainPass(CameraClass *pCamera) { - DX8Wrapper::Set_Transform(D3DTS_WORLD,Matrix3D(true)); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,Matrix3D(true)); //Apply the shader and material - DX8Wrapper::Set_Index_Buffer(m_indexBuffer,0); + g_renderBackend->Set_Index_Buffer(m_indexBuffer,0); for (Int j=0; jSet_Vertex_Buffer(getVertexBufferTile(i, j)); #ifdef PRE_TRANSFORM_VERTEX if (m_xformedVertexBuffer && pass==0) { // Note - m_xformedVertexBuffer should only be used for non T&L hardware. jba. - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); int code = DX8Wrapper::_Get_D3D_Device8()->ProcessVertices(0, 0, numVertex, m_xformedVertexBuffer[j*m_numVBTilesX+i], 0); ::OutputDebugString("did process vertex\n"); } if (m_xformedVertexBuffer) { // Note - m_xformedVertexBuffer should only be used for non T&L hardware. jba. - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); DX8Wrapper::_Get_D3D_Device8()->SetStreamSource( 0, m_xformedVertexBuffer[j*m_numVBTilesX+i], @@ -2180,7 +2182,7 @@ void HeightMapRenderObjClass::renderTerrainPass(CameraClass *pCamera) } #endif if (Is_Hidden() == 0) { - DX8Wrapper::Draw_Triangles(0, HEIGHTMAP_POLYGON_NUM, 0, HEIGHTMAP_VERTEX_NUM); + g_renderBackend->Draw_Triangles(0, HEIGHTMAP_POLYGON_NUM, 0, HEIGHTMAP_VERTEX_NUM); } } } @@ -2338,23 +2340,23 @@ void HeightMapRenderObjClass::renderExtraBlendTiles() maxBlendTiles += 16; //enlarge by 16 to reduce trashing. ShaderClass::Invalidate(); //invalidate to force shader to reset since we directly changed states - DX8Wrapper::Set_Index_Buffer(ib_access,0); - DX8Wrapper::Set_Vertex_Buffer(vb_access); + g_renderBackend->Set_Index_Buffer(ib_access,0); + g_renderBackend->Set_Vertex_Buffer(vb_access); VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); ShaderClass shader=ShaderClass::_PresetOpaqueShader; shader.Set_Depth_Mask(ShaderClass::DEPTH_WRITE_DISABLE); //disable writes to z - DX8Wrapper::Set_Shader(shader); + g_renderBackend->Set_Shader(shader); if (TheGlobalData->m_use3WayTerrainBlends == 2) { shader.Set_Primary_Gradient(ShaderClass::GRADIENT_DISABLE); //disable lighting. shader.Set_Texturing(ShaderClass::TEXTURING_DISABLE); //disable texturing. - DX8Wrapper::Set_Shader(shader); - DX8Wrapper::Set_Texture(0,nullptr); //debug mode which draws terrain tiles in white. + g_renderBackend->Set_Shader(shader); + g_renderBackend->Set_Texture(0,nullptr); //debug mode which draws terrain tiles in white. if (Is_Hidden() == 0) { - DX8Wrapper::Draw_Triangles( 0,indexCount/3, 0, vertexCount); //draw a quad, 2 triangles, 4 verts + g_renderBackend->Draw_Triangles( 0,indexCount/3, 0, vertexCount); //draw a quad, 2 triangles, 4 verts m_numVisibleExtraBlendTiles += indexCount/6; } } @@ -2387,7 +2389,7 @@ void HeightMapRenderObjClass::renderExtraBlendTiles() { W3DShaderManager::setShader(st, pass); if (Is_Hidden() == 0) { - DX8Wrapper::Draw_Triangles( 0,indexCount/3, 0, vertexCount); //draw a quad, 2 triangles, 4 verts + g_renderBackend->Draw_Triangles( 0,indexCount/3, 0, vertexCount); //draw a quad, 2 triangles, 4 verts m_numVisibleExtraBlendTiles += indexCount/6; } } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp index e55fee5bde9..fa65137a70a 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp @@ -54,6 +54,8 @@ //----------------------------------------------------------------------------- #include "WW3D2/dx8wrapper.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/assetmgr.h" #include "Lib/BaseType.h" #include "Common/file.h" @@ -233,15 +235,15 @@ Bool ScreenDefaultFilter::postRender(FilterModes mode, Coord2D &scrollDelta,Bool Int ScreenDefaultFilter::set(FilterModes mode) { VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); //no need to keep a reference since it's a preset. - DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Set_Shader(ShaderClass::_PresetOpaqueShader); + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC,D3DCMP_ALWAYS); DX8Wrapper::Set_DX8_Render_State(D3DRS_ZWRITEENABLE,FALSE); - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices return true; } @@ -249,7 +251,7 @@ Int ScreenDefaultFilter::set(FilterModes mode) void ScreenDefaultFilter::reset() { DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,nullptr); //previously rendered frame inside this texture - DX8Wrapper::Invalidate_Cached_Render_States(); + g_renderBackend->Invalidate_Cached_Render_States(); } /*========= ScreenBWFilter =============================================================*/ @@ -411,15 +413,15 @@ Int ScreenBWFilter::set(FilterModes mode) } VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); //no need to keep a reference since it's a preset. - DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Set_Shader(ShaderClass::_PresetOpaqueShader); + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC,D3DCMP_ALWAYS); DX8Wrapper::Set_DX8_Render_State(D3DRS_ZWRITEENABLE,FALSE); - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices hr=DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(m_dwBWPixelShader); DX8Wrapper::_Get_D3D_Device8()->SetPixelShaderConstant(0, D3DXVECTOR4(0.3f, 0.59f, 0.11f, 1.0f), 1); @@ -468,7 +470,7 @@ void ScreenBWFilter::reset() { DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,nullptr); //previously rendered frame inside this texture DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(0); //turn off pixel shader - DX8Wrapper::Invalidate_Cached_Render_States(); + g_renderBackend->Invalidate_Cached_Render_States(); } Int ScreenBWFilter::shutdown() @@ -582,8 +584,8 @@ Bool ScreenBWFilterDOT3::postRender(FilterModes mode, Coord2D &scrollDelta,Bool ShaderClass::Invalidate(); //reset DOT3 blend from above. ShaderClass shader=ShaderClass::_PresetAlphaShader; shader.Set_Depth_Compare(ShaderClass::PASS_ALWAYS); - DX8Wrapper::Set_Shader(shader); - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Set_Shader(shader); + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices //replace texture alpha with vertex alpha DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); @@ -633,15 +635,15 @@ Int ScreenBWFilterDOT3::set(FilterModes mode) } VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); //no need to keep a reference since it's a preset. - DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Set_Shader(ShaderClass::_PresetOpaqueShader); + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC,D3DCMP_ALWAYS); DX8Wrapper::Set_DX8_Render_State(D3DRS_ZWRITEENABLE,FALSE); - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices return true; } @@ -651,7 +653,7 @@ Int ScreenBWFilterDOT3::set(FilterModes mode) void ScreenBWFilterDOT3::reset() { DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,nullptr); //previously rendered frame inside this texture - DX8Wrapper::Invalidate_Cached_Render_States(); + g_renderBackend->Invalidate_Cached_Render_States(); } Int ScreenBWFilterDOT3::shutdown() @@ -853,12 +855,12 @@ Int ScreenCrossFadeFilter::set(FilterModes mode) if (mode > FM_NULL_MODE) { //rendering a quad with redirected rendering surface VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); //no need to keep a reference since it's a preset. - DX8Wrapper::Set_Shader(ShaderClass::_PresetAlphaShader); - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Set_Texture(1,nullptr); - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Set_Shader(ShaderClass::_PresetAlphaShader); + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Set_Texture(1,nullptr); + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); @@ -890,7 +892,7 @@ void ScreenCrossFadeFilter::reset() DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_DISABLE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,nullptr); //previously rendered frame inside this texture - DX8Wrapper::Invalidate_Cached_Render_States(); + g_renderBackend->Invalidate_Cached_Render_States(); } Int ScreenCrossFadeFilter::shutdown() @@ -991,7 +993,7 @@ Bool ScreenMotionBlurFilter::postRender(FilterModes mode, Coord2D &scrollDelta,B DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,false); //draw polygons like this is very inefficient but for only 2 triangles, it's //not worth bothering with index/vertex buffers. - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); pDev->SetVertexShader(D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1); Coord2D center; @@ -1062,7 +1064,7 @@ Bool ScreenMotionBlurFilter::postRender(FilterModes mode, Coord2D &scrollDelta,B pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANS_LIT_TEX_VERTEX)); DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,true); - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); { Int limit = m_maxCount; if (m_maxCount>30) limit = 30; @@ -1143,16 +1145,16 @@ Int ScreenMotionBlurFilter::set(FilterModes mode) { //rendering a quad with redirected rendering surface motion blurred VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); //no need to keep a reference since it's a preset. - DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Set_Texture(1,nullptr); - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Set_Shader(ShaderClass::_PresetOpaqueShader); + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Set_Texture(1,nullptr); + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC,D3DCMP_ALWAYS); DX8Wrapper::Set_DX8_Render_State(D3DRS_ZWRITEENABLE,FALSE); - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices } return TRUE; } @@ -1160,7 +1162,7 @@ Int ScreenMotionBlurFilter::set(FilterModes mode) void ScreenMotionBlurFilter::reset() { DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,nullptr); //previously rendered frame inside this texture - DX8Wrapper::Invalidate_Cached_Render_States(); + g_renderBackend->Invalidate_Cached_Render_States(); } Int ScreenMotionBlurFilter::shutdown() @@ -1203,22 +1205,22 @@ Int ShroudTextureShader::set(Int stage) { //force WW3D2 system to set it's states so it won't later overwrite our custom settings. VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); //no need to keep a reference since it's a preset. - DX8Wrapper::Set_Texture(stage, W3DShaderManager::getShaderTexture(0)); //shroud always stored in texture 0 + g_renderBackend->Set_Texture(stage, W3DShaderManager::getShaderTexture(0)); //shroud always stored in texture 0 if (stage == 0) { #if defined(RTS_DEBUG) if (TheGlobalData && TheGlobalData->m_fogOfWarOn) - DX8Wrapper::Set_Shader(ShaderClass::_PresetAlphaSpriteShader); + g_renderBackend->Set_Shader(ShaderClass::_PresetAlphaSpriteShader); else - DX8Wrapper::Set_Shader(ShaderClass::_PresetMultiplicativeSpriteShader); + g_renderBackend->Set_Shader(ShaderClass::_PresetMultiplicativeSpriteShader); #else - DX8Wrapper::Set_Shader(ShaderClass::_PresetMultiplicativeSpriteShader); + g_renderBackend->Set_Shader(ShaderClass::_PresetMultiplicativeSpriteShader); #endif } - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); @@ -1266,7 +1268,7 @@ Int ShroudTextureShader::set(Int stage) void ShroudTextureShader::reset() { - DX8Wrapper::Set_Texture(m_stageOfSet,nullptr); + g_renderBackend->Set_Texture(m_stageOfSet,nullptr); DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC,D3DCMP_LESSEQUAL); DX8Wrapper::Set_DX8_Texture_Stage_State(m_stageOfSet, D3DTSS_TEXCOORDINDEX, m_stageOfSet); DX8Wrapper::Set_DX8_Texture_Stage_State(m_stageOfSet, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); @@ -1303,7 +1305,7 @@ Int FlatShroudTextureShader::set(Int stage) { //force WW3D2 system to set it's states so it won't later overwrite our custom settings. if (stage < 2) - DX8Wrapper::Set_Texture(stage, W3DShaderManager::getShaderTexture(stage)); + g_renderBackend->Set_Texture(stage, W3DShaderManager::getShaderTexture(stage)); else //stages larger than 1 are not supported by W3D so set them directly DX8Wrapper::Set_DX8_Texture(stage, W3DShaderManager::getShaderTexture(stage)->Peek_D3D_Texture()); @@ -1311,7 +1313,7 @@ Int FlatShroudTextureShader::set(Int stage) DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_COLORARG2, D3DTA_CURRENT ); DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_COLOROP, D3DTOP_MODULATE ); DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); - //DX8Wrapper::Apply_Render_State_Changes(); + //g_renderBackend->Apply_Render_State_Changes(); DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); @@ -1359,7 +1361,7 @@ Int FlatShroudTextureShader::set(Int stage) void FlatShroudTextureShader::reset() { if (m_stageOfSet < MAX_TEXTURE_STAGES) - DX8Wrapper::Set_Texture(m_stageOfSet,nullptr); + g_renderBackend->Set_Texture(m_stageOfSet,nullptr); DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC,D3DCMP_LESSEQUAL); DX8Wrapper::Set_DX8_Texture_Stage_State(m_stageOfSet, D3DTSS_TEXCOORDINDEX, m_stageOfSet); DX8Wrapper::Set_DX8_Texture_Stage_State(m_stageOfSet, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); @@ -1400,15 +1402,15 @@ Int MaskTextureShader::set(Int pass) //force WW3D2 system to set it's states so it won't later overwrite our custom settings. VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); //no need to keep a reference since it's a preset. //For now we're always going to project the texture coming from the crossfade effect - DX8Wrapper::Set_Texture(0, ScreenCrossFadeFilter::getCurrentMaskTexture()); + g_renderBackend->Set_Texture(0, ScreenCrossFadeFilter::getCurrentMaskTexture()); ShaderClass shader=ShaderClass::_PresetOpaqueShader; shader.Set_Primary_Gradient(ShaderClass::GRADIENT_DISABLE); - DX8Wrapper::Set_Shader(shader); - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Set_Shader(shader); + g_renderBackend->Apply_Render_State_Changes(); D3DXMATRIX curView; DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); @@ -1467,7 +1469,7 @@ Int MaskTextureShader::set(Int pass) void MaskTextureShader::reset() { - DX8Wrapper::Set_Texture(0,nullptr); + g_renderBackend->Set_Texture(0,nullptr); DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_TEXCOORDINDEX, 0); DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); } @@ -1632,7 +1634,7 @@ void TerrainShader2Stage::updateNoise2(D3DXMATRIX *destMatrix,D3DXMATRIX *curVie Int TerrainShader2Stage::set(Int pass) { //force WW3D2 system to set it's states so it won't later overwrite our custom settings. - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); if (TheGlobalData && (TheGlobalData->m_bilinearTerrainTex || TheGlobalData->m_trilinearTerrainTex)) { DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MINFILTER, D3DTEXF_LINEAR); @@ -1801,7 +1803,7 @@ Int TerrainShader8Stage::set(Int pass) if (pass == 0) { //force WW3D2 system to set it's states so it won't later overwrite our custom settings. - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); @@ -1906,7 +1908,7 @@ Int TerrainShader8Stage::set(Int pass) DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_ALPHAOP, D3DTOP_DISABLE); DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_COLOROP, D3DTOP_DISABLE); DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_ALPHAOP, D3DTOP_DISABLE); - DX8Wrapper::Invalidate_Cached_Render_States(); + g_renderBackend->Invalidate_Cached_Render_States(); terrainShader2Stage.set(2); } @@ -1924,7 +1926,7 @@ void TerrainShader8Stage::reset() DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, nullptr); DX8Wrapper::_Get_D3D_Device8()->SetTexture(1, nullptr); - DX8Wrapper::Invalidate_Cached_Render_States(); + g_renderBackend->Invalidate_Cached_Render_States(); } Int TerrainShaderPixelShader::shutdown() @@ -2000,7 +2002,7 @@ Int TerrainShaderPixelShader::init() Int TerrainShaderPixelShader::set(Int pass) { //force WW3D2 system to set it's states so it won't later overwrite our custom settings. - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); //setup base pass DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, W3DShaderManager::getShaderTexture(0)->Peek_D3D_Texture()); @@ -2126,7 +2128,7 @@ void TerrainShaderPixelShader::reset() DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|3); - DX8Wrapper::Invalidate_Cached_Render_States(); + g_renderBackend->Invalidate_Cached_Render_States(); } ///Cloud layer rendering shader - used for objects similar to terrain which only need the cloud layer. @@ -2276,9 +2278,9 @@ Int RoadShaderPixelShader::init() Int RoadShaderPixelShader::set(Int pass) { - DX8Wrapper::Set_Texture(0,W3DShaderManager::getShaderTexture(0)); + g_renderBackend->Set_Texture(0,W3DShaderManager::getShaderTexture(0)); //force WW3D2 system to set it's states so it won't later overwrite our custom settings. - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); //tell pixel shader which UV set to use for each stage DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, 0 ); @@ -2317,8 +2319,8 @@ Int RoadShaderPixelShader::set(Int pass) DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); - DX8Wrapper::Set_Texture(1,W3DShaderManager::getShaderTexture(1)); - DX8Wrapper::Set_Texture(2,W3DShaderManager::getShaderTexture(2)); + g_renderBackend->Set_Texture(1,W3DShaderManager::getShaderTexture(1)); + g_renderBackend->Set_Texture(2,W3DShaderManager::getShaderTexture(2)); DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(m_dwBaseNoise2PixelShader); @@ -2359,7 +2361,7 @@ void RoadShaderPixelShader::reset() DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|3); - DX8Wrapper::Invalidate_Cached_Render_States(); + g_renderBackend->Invalidate_Cached_Render_States(); } Int RoadShader2Stage::init() @@ -2380,9 +2382,9 @@ Int RoadShader2Stage::init() Int RoadShader2Stage::set(Int pass) { //First stage always contains base texture. - DX8Wrapper::Set_Texture(0,W3DShaderManager::getShaderTexture(0)); + g_renderBackend->Set_Texture(0,W3DShaderManager::getShaderTexture(0)); //Force system to apply world/view transforms. - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC,D3DCMP_LESSEQUAL); DX8Wrapper::Set_DX8_Render_State(D3DRS_ZWRITEENABLE,FALSE); @@ -2434,7 +2436,7 @@ Int RoadShader2Stage::set(Int pass) if (W3DShaderManager::getCurrentShader() == W3DShaderManager::ST_ROAD_BASE_NOISE12) { //full shader, apply noise 1 in pass 0. - DX8Wrapper::Set_Texture(1,W3DShaderManager::getShaderTexture(1)); + g_renderBackend->Set_Texture(1,W3DShaderManager::getShaderTexture(1)); DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MINFILTER, D3DTEXF_LINEAR); DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); @@ -2445,14 +2447,14 @@ Int RoadShader2Stage::set(Int pass) { //single noise texture shader if (W3DShaderManager::getCurrentShader() == W3DShaderManager::ST_ROAD_BASE_NOISE1) { //cloud map - DX8Wrapper::Set_Texture(1,W3DShaderManager::getShaderTexture(1)); + g_renderBackend->Set_Texture(1,W3DShaderManager::getShaderTexture(1)); terrainShader2Stage.updateNoise1(&curView, &inv, false); //update curView with texture matrix DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MINFILTER, D3DTEXF_LINEAR); DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); } else { //light map - DX8Wrapper::Set_Texture(1,W3DShaderManager::getShaderTexture(2)); + g_renderBackend->Set_Texture(1,W3DShaderManager::getShaderTexture(2)); terrainShader2Stage.updateNoise2(&curView,&inv, false); //update curView with texture matrix DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MINFILTER, D3DTEXF_POINT); DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); @@ -2480,7 +2482,7 @@ Int RoadShader2Stage::set(Int pass) else DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MIPFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_Texture(1,W3DShaderManager::getShaderTexture(2)); + g_renderBackend->Set_Texture(1,W3DShaderManager::getShaderTexture(2)); terrainShader2Stage.updateNoise2(&curView, &inv, false); //update curView with texture matrix DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MINFILTER, D3DTEXF_POINT); @@ -2859,17 +2861,17 @@ void W3DShaderManager::startRenderToTexture() ShaderClass shader=ShaderClass::_PresetOpaqueSolidShader; shader.Set_Depth_Compare(ShaderClass::PASS_ALWAYS); shader.Set_Depth_Mask(ShaderClass::DEPTH_WRITE_DISABLE); - DX8Wrapper::Set_Shader(shader); + g_renderBackend->Set_Shader(shader); VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); //no need to keep a reference since it's a preset. drawViewport(0x00ffffff | (((Int)(TheWaterTransparency->m_minWaterOpacity*255.0f)) <<24)); DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_RED|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_BLUE); //disable writes to alpha } else //normal clear that overwrites everything. - DX8Wrapper::Clear(true, false, Vector3( 0.0f, 0.0f, 0.0f ), TheWaterTransparency->m_minWaterOpacity); + g_renderBackend->Clear(true, false, Vector3( 0.0f, 0.0f, 0.0f ), TheWaterTransparency->m_minWaterOpacity); } } @@ -3203,7 +3205,7 @@ Int W3DShaderManager::setShroudTex(Int stage) W3DShroud *shroud; if ((shroud=TheTerrainRenderObject->getShroud()) != nullptr) { - DX8Wrapper::Set_Texture(stage, shroud->getShroudTexture()); + g_renderBackend->Set_Texture(stage, shroud->getShroudTexture()); DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); @@ -3286,7 +3288,7 @@ void FlatTerrainShader2Stage::reset() Int FlatTerrainShader2Stage::set(Int pass) { //force WW3D2 system to set it's states so it won't later overwrite our custom settings. - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); if (TheGlobalData && (TheGlobalData->m_bilinearTerrainTex || TheGlobalData->m_trilinearTerrainTex)) { DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MINFILTER, D3DTEXF_LINEAR); @@ -3562,10 +3564,10 @@ Int FlatTerrainShaderPixelShader::set(Int pass) DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_Texture(0, W3DShaderManager::getShaderTexture(2)); - DX8Wrapper::Set_Texture(1, W3DShaderManager::getShaderTexture(2)); + g_renderBackend->Set_Texture(0, W3DShaderManager::getShaderTexture(2)); + g_renderBackend->Set_Texture(1, W3DShaderManager::getShaderTexture(2)); //force WW3D2 system to set it's states so it won't later overwrite our custom settings. - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); @@ -3702,7 +3704,7 @@ Int FlatTerrainShaderPixelShader::set(Int pass) DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(m_dwBaseNoise2PixelShader); } DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_ALPHABLENDENABLE, false); - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); DX8Wrapper::_Get_D3D_Device8()->SetTexture(curStage, W3DShaderManager::getShaderTexture(3)->Peek_D3D_Texture()); return TRUE; } @@ -3730,10 +3732,9 @@ void FlatTerrainShaderPixelShader::reset() DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|3); - DX8Wrapper::Invalidate_Cached_Render_States(); + g_renderBackend->Invalidate_Cached_Render_States(); } - diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp index f29f9803365..9d1f2b34d7c 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp @@ -37,6 +37,8 @@ #include "WW3D2/texture.h" #include "WW3D2/dx8indexbuffer.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/rinfo.h" #include "WW3D2/camera.h" #include "WW3D2/sortingrenderer.h" @@ -221,15 +223,15 @@ Bool W3DSmudgeManager::testHardwareSupport() } VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); //no need to keep a reference since it's a preset. ShaderClass shader=ShaderClass::_PresetOpaqueShader; shader.Set_Depth_Compare(ShaderClass::PASS_ALWAYS); shader.Set_Depth_Mask(ShaderClass::DEPTH_WRITE_DISABLE); - DX8Wrapper::Set_Shader(shader); - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Set_Shader(shader); + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices struct _TRANS_LIT_TEX_VERTEX { Vector4 p; @@ -440,15 +442,15 @@ void W3DSmudgeManager::render(RenderInfoClass &rinfo) REF_PTR_RELEASE(backBuffer); Matrix4x4 identity(true); - DX8Wrapper::Set_Transform(D3DTS_WORLD,identity); - DX8Wrapper::Set_Transform(D3DTS_VIEW,identity); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,identity); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW,identity); - DX8Wrapper::Set_Index_Buffer(m_indexBuffer,0); - //DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueSpriteShader); + g_renderBackend->Set_Index_Buffer(m_indexBuffer,0); + //g_renderBackend->Set_Shader(ShaderClass::_PresetOpaqueSpriteShader); - DX8Wrapper::Set_Shader(ShaderClass::_PresetAlphaShader); + g_renderBackend->Set_Shader(ShaderClass::_PresetAlphaShader); - DX8Wrapper::Set_Texture(0,m_backgroundTexture); + g_renderBackend->Set_Texture(0,m_backgroundTexture); //Need these states in case texture is non-power-of-2 DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); @@ -457,9 +459,9 @@ void W3DSmudgeManager::render(RenderInfoClass &rinfo) DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_MIPFILTER, D3DTEXF_NONE); VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); //Disable reading texture alpha since it's undefined. //DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_COLOROP,D3DTOP_SELECTARG1); @@ -533,15 +535,15 @@ void W3DSmudgeManager::render(RenderInfoClass &rinfo) } flushSmudges: - DX8Wrapper::Set_Vertex_Buffer(vb_access); + g_renderBackend->Set_Vertex_Buffer(vb_access); - DX8Wrapper::Draw_Triangles(0,smudgesInRenderBatch*4, 0, smudgesInRenderBatch*5); + g_renderBackend->Draw_Triangles(0,smudgesInRenderBatch*4, 0, smudgesInRenderBatch*5); //Debug Code which draws outline around smudge /* DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME); DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_ALPHABLENDENABLE,FALSE); DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_COLOROP,D3DTOP_SELECTARG2); - DX8Wrapper::Draw_Triangles( 0,smudgesInRenderBatch*4, 0, smudgesInRenderBatch*5); + g_renderBackend->Draw_Triangles( 0,smudgesInRenderBatch*4, 0, smudgesInRenderBatch*5); DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID); DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_ALPHABLENDENABLE,TRUE); DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_COLOROP,D3DTOP_SELECTARG1); diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSnow.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSnow.cpp index 62ece01ed24..303edc0da5e 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSnow.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSnow.cpp @@ -22,6 +22,8 @@ #include "W3DDevice/GameClient/HeightMap.h" #include "GameClient/View.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/rinfo.h" #include "WW3D2/camera.h" #include "WW3D2/assetmgr.h" @@ -392,12 +394,12 @@ void W3DSnowManager::render(RenderInfoClass &rinfo) m_heightTraveled=m_time*m_velocity+cameraOffset; //height that snow flake traveled this frame. Matrix4x4 identity(true); - DX8Wrapper::Set_Transform(D3DTS_WORLD,identity); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,identity); - DX8Wrapper::Set_Shader(ShaderClass::_PresetAlphaShader); + g_renderBackend->Set_Shader(ShaderClass::_PresetAlphaShader); VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); //make sure we have all the resources we need @@ -407,7 +409,7 @@ void W3DSnowManager::render(RenderInfoClass &rinfo) if (!usePointSprites && !m_indexBuffer) ReAcquireResources(); - DX8Wrapper::Set_Texture(0,m_snowTexture); + g_renderBackend->Set_Texture(0,m_snowTexture); if (!usePointSprites) { @@ -417,7 +419,7 @@ void W3DSnowManager::render(RenderInfoClass &rinfo) Vector3 snowCenter; - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); // Set the render states for using point sprites DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSPRITEENABLE, TRUE ); @@ -483,9 +485,9 @@ void W3DSnowManager::renderAsQuads(RenderInfoClass &rinfo, Int cubeOriginX, Int } Matrix4x4 identity(true); - DX8Wrapper::Set_Transform(D3DTS_VIEW,identity); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW,identity); - DX8Wrapper::Set_Index_Buffer(m_indexBuffer,0); + g_renderBackend->Set_Index_Buffer(m_indexBuffer,0); Int y=cubeOriginY; //loop counter. Int cubeOriginXRemainder = cubeOriginX; //loop counter - adjusted when not all particles fit into render buffer. @@ -563,8 +565,8 @@ void W3DSnowManager::renderAsQuads(RenderInfoClass &rinfo, Int cubeOriginX, Int //Render any particles that may be queued up. if (numberInBatch) { - DX8Wrapper::Set_Vertex_Buffer(vb_access); - DX8Wrapper::Draw_Triangles( 0,numberInBatch*2, 0, numberInBatch*4); + g_renderBackend->Set_Vertex_Buffer(vb_access); + g_renderBackend->Draw_Triangles( 0,numberInBatch*2, 0, numberInBatch*4); totalPart -= numberInBatch; } } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainBackground.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainBackground.cpp index 04e637acd33..6c17b92a610 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainBackground.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainBackground.cpp @@ -55,6 +55,8 @@ #include "W3DDevice/GameClient/TerrainTex.h" #include "W3DDevice/GameClient/HeightMap.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/dx8renderer.h" #include "WW3D2/camera.h" @@ -763,18 +765,18 @@ void W3DTerrainBackground::drawVisiblePolys(RenderInfoClass & rinfo, Bool disabl return; } // Setup the vertex buffer, shader & texture. - DX8Wrapper::Set_Index_Buffer(m_indexTerrain,0); - DX8Wrapper::Set_Vertex_Buffer(m_vertexTerrain); + g_renderBackend->Set_Index_Buffer(m_indexTerrain,0); + g_renderBackend->Set_Vertex_Buffer(m_vertexTerrain); if (!disableTextures) { if (m_terrainTexture4X) { - DX8Wrapper::Set_Texture(1, m_terrainTexture4X); + g_renderBackend->Set_Texture(1, m_terrainTexture4X); } else if (m_terrainTexture2X) { - DX8Wrapper::Set_Texture(1, m_terrainTexture2X); + g_renderBackend->Set_Texture(1, m_terrainTexture2X); } else { - DX8Wrapper::Set_Texture(1, m_terrainTexture); + g_renderBackend->Set_Texture(1, m_terrainTexture); } } - DX8Wrapper::Draw_Triangles( 0, m_curNumTerrainIndices/3, 0, m_curNumTerrainVertices); + g_renderBackend->Draw_Triangles( 0, m_curNumTerrainIndices/3, 0, m_curNumTerrainVertices); #else if (m_curNumTerrainIndices == 0) { return; @@ -783,18 +785,18 @@ void W3DTerrainBackground::drawVisiblePolys(RenderInfoClass & rinfo, Bool disabl return; } // Setup the vertex buffer, shader & texture. - DX8Wrapper::Set_Index_Buffer(m_indexTerrain,0); - DX8Wrapper::Set_Vertex_Buffer(m_vertexTerrain); + g_renderBackend->Set_Index_Buffer(m_indexTerrain,0); + g_renderBackend->Set_Vertex_Buffer(m_vertexTerrain); if (!disableTextures) { if (m_terrainTexture4X) { - DX8Wrapper::Set_Texture(0, m_terrainTexture4X); + g_renderBackend->Set_Texture(0, m_terrainTexture4X); } else if (m_terrainTexture2X) { - DX8Wrapper::Set_Texture(0, m_terrainTexture2X); + g_renderBackend->Set_Texture(0, m_terrainTexture2X); } else { - DX8Wrapper::Set_Texture(0, m_terrainTexture); + g_renderBackend->Set_Texture(0, m_terrainTexture); } } - DX8Wrapper::Draw_Triangles( 0, m_curNumTerrainIndices/3, 0, m_curNumTerrainVertices); + g_renderBackend->Draw_Triangles( 0, m_curNumTerrainIndices/3, 0, m_curNumTerrainVertices); #endif } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTreeBuffer.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTreeBuffer.cpp index 7ac29bec1b1..bd31e2d8516 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTreeBuffer.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTreeBuffer.cpp @@ -83,6 +83,8 @@ enum #include "W3DDevice/GameClient/W3DProjectedShadow.h" #include "WW3D2/camera.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/dx8renderer.h" #include "WW3D2/matinfo.h" #include "WW3D2/mesh.h" @@ -1569,8 +1571,8 @@ void W3DTreeBuffer::drawTrees(CameraClass * camera, RefRenderObjListIterator *pD //#define DEBUG_TEXTURE 1 #ifdef DEBUG_TEXTURE // Draw the combined texture for debugging. jba. [4/21/2003] // Setup the vertex buffer, shader & texture. - DX8Wrapper::Set_Shader(detailAlphaShader); - DX8Wrapper::Set_Texture(0,m_treeTexture); + g_renderBackend->Set_Shader(detailAlphaShader); + g_renderBackend->Set_Texture(0,m_treeTexture); DynamicIBAccessClass ib_access(BUFFER_TYPE_DYNAMIC_DX8, 6); //draw an infinite sky plane DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC_DX8, DX8_FVF_XYZNDUV2, 4); @@ -1622,29 +1624,29 @@ void W3DTreeBuffer::drawTrees(CameraClass * camera, RefRenderObjListIterator *pD } } - DX8Wrapper::Set_Index_Buffer(ib_access,0); - DX8Wrapper::Set_Vertex_Buffer(vb_access); + g_renderBackend->Set_Index_Buffer(ib_access,0); + g_renderBackend->Set_Vertex_Buffer(vb_access); Matrix3D tm(1); - DX8Wrapper::Set_Transform(D3DTS_WORLD,tm); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,tm); - DX8Wrapper::Draw_Triangles( 0,2, 0, 4); //draw a quad, 2 triangles, 4 verts + g_renderBackend->Draw_Triangles( 0,2, 0, 4); //draw a quad, 2 triangles, 4 verts #endif if (m_curNumTreeIndices[0] == 0) { return; } - DX8Wrapper::Set_Shader(detailAlphaShader); + g_renderBackend->Set_Shader(detailAlphaShader); - DX8Wrapper::Set_Texture(0,m_treeTexture); - DX8Wrapper::Set_Texture(1,nullptr); + g_renderBackend->Set_Texture(0,m_treeTexture); + g_renderBackend->Set_Texture(1,nullptr); DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_TEXCOORDINDEX, 0); DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_TEXCOORDINDEX, 1); // Draw all the trees. - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); W3DShaderManager::setShroudTex(1); - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); if (m_dwTreeVertexShader) { D3DXMATRIX matProj, matView, matWorld; @@ -1690,9 +1692,9 @@ void W3DTreeBuffer::drawTrees(CameraClass * camera, RefRenderObjListIterator *pD DX8Wrapper::_Get_D3D_Device8()->SetVertexShaderConstant( 33, &offset, 1 ); } - DX8Wrapper::Set_Vertex_Shader(m_dwTreeVertexShader); + g_renderBackend->Set_Vertex_Shader(m_dwTreeVertexShader); #if 0 - DX8Wrapper::Set_Pixel_Shader(m_dwTreePixelShader); + g_renderBackend->Set_Pixel_Shader(m_dwTreePixelShader); // a.c. 6/16 - allow switching between normal and 2X mode for terrain Real mulTwoX = 0.5f; if(TheGlobalData && TheGlobalData->m_useOverbright) @@ -1701,7 +1703,7 @@ void W3DTreeBuffer::drawTrees(CameraClass * camera, RefRenderObjListIterator *pD #endif } else { - DX8Wrapper::Set_Vertex_Shader(DX8_FVF_XYZNDUV1); + g_renderBackend->Set_Vertex_Shader(DX8_FVF_XYZNDUV1); } @@ -1710,22 +1712,22 @@ void W3DTreeBuffer::drawTrees(CameraClass * camera, RefRenderObjListIterator *pD if (m_curNumTreeIndices[bNdx]==0) { break; } - DX8Wrapper::Set_Index_Buffer(m_indexTree[bNdx],0); - DX8Wrapper::Set_Vertex_Buffer(m_vertexTree[bNdx]); + g_renderBackend->Set_Index_Buffer(m_indexTree[bNdx],0); + g_renderBackend->Set_Vertex_Buffer(m_vertexTree[bNdx],0); // Render the waving grass - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); if (m_dwTreeVertexShader) { DX8Wrapper::_Get_D3D_Device8()->SetVertexShader(m_dwTreeVertexShader); DX8Wrapper::_Get_D3D_Device8()->SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, 0); DX8Wrapper::_Get_D3D_Device8()->SetTextureStageState(1, D3DTSS_TEXCOORDINDEX, 1); DX8Wrapper::_Get_D3D_Device8()->SetTextureStageState(1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); } - DX8Wrapper::Draw_Triangles( 0, m_curNumTreeIndices[bNdx]/3, 0, m_curNumTreeVertices[bNdx]); + g_renderBackend->Draw_Triangles( 0, m_curNumTreeIndices[bNdx]/3, 0, m_curNumTreeVertices[bNdx]); } - DX8Wrapper::Set_Vertex_Shader(DX8_FVF_XYZNDUV1); - DX8Wrapper::Set_Pixel_Shader(0); - DX8Wrapper::Invalidate_Cached_Render_States(); //code above mucks around with W3D states so make sure we reset + g_renderBackend->Set_Vertex_Shader(DX8_FVF_XYZNDUV1); + g_renderBackend->Set_Pixel_Shader(0); + g_renderBackend->Invalidate_Cached_Render_States(); //code above mucks around with W3D states so make sure we reset } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp index e65724084c5..e68f01efb73 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp @@ -91,6 +91,8 @@ #include "W3DDevice/GameClient/W3DCustomScene.h" #include "WW3D2/dx8renderer.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/light.h" #include "WW3D2/predlod.h" #include "WW3D2/ww3d.h" @@ -1914,7 +1916,7 @@ void W3DView::draw() //The pass that rendered into a texture may have left the z-buffer in a weird state //so clear it before rendering normal scene. ///@todo: Don't clear z-buffer unless shader uses z-bias or anything else that would cause <= z to fail on normal render. - DX8Wrapper::Clear(false, true, Vector3(0.0f,0.0f,0.0f), TheWaterTransparency->m_minWaterOpacity); // Clear z but not color + g_renderBackend->Clear(false, true, Vector3(0.0f,0.0f,0.0f), TheWaterTransparency->m_minWaterOpacity); // Clear z but not color W3DDisplay::m_3DScene->setCustomPassMode(SCENE_PASS_DEFAULT); W3DDisplay::m_3DScene->doRender( m_3DCamera ); Coord2D deltaScroll; diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp index 4d4946c2b3a..1367aa9f55b 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp @@ -43,6 +43,8 @@ #include "WW3D2/camera.h" #include "WW3D2/scene.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/light.h" #include "d3dx8math.h" #include "WWLib/simplevec.h" @@ -208,12 +210,12 @@ static Bool wireframeForDebug = 0; void WaterRenderObjClass::setupJbaWaterShader() { if (!TheWaterTransparency->m_additiveBlend) - DX8Wrapper::Set_Shader(ShaderClass::_PresetAlphaShader); + g_renderBackend->Set_Shader(ShaderClass::_PresetAlphaShader); else - DX8Wrapper::Set_Shader(ShaderClass::_PresetAdditiveShader); + g_renderBackend->Set_Shader(ShaderClass::_PresetAdditiveShader); VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); m_riverTexture->Get_Filter().Set_Mag_Filter(TextureFilterClass::FILTER_TYPE_BEST); m_riverTexture->Get_Filter().Set_Min_Filter(TextureFilterClass::FILTER_TYPE_BEST); @@ -223,7 +225,7 @@ void WaterRenderObjClass::setupJbaWaterShader() // Setting *setting=&m_settings[m_tod]; - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_ADD ); if (!m_riverAlphaEdge->Is_Initialized()) m_riverAlphaEdge->Init(); @@ -911,7 +913,7 @@ void WaterRenderObjClass::ReAcquireResources() return; // Create reflection texture - m_pReflectionTexture = DX8Wrapper::Create_Render_Target (SEA_REFLECTION_SIZE, SEA_REFLECTION_SIZE); + m_pReflectionTexture = g_renderBackend->Create_Render_Target (SEA_REFLECTION_SIZE, SEA_REFLECTION_SIZE); } if (m_waterTrackSystem) @@ -1476,7 +1478,7 @@ void WaterRenderObjClass::renderMirror(CameraClass *cam) Matrix3D reflectedTransform(rRight,rUp,rN,rPos); - DX8Wrapper::Set_Render_Target_With_Z((TextureClass*)m_pReflectionTexture); + g_renderBackend->Set_Render_Target_With_Z((TextureClass*)m_pReflectionTexture); // Clear the backbuffer WW3D::Begin_Render(false,true,Vector3(0.0f,0.0f,0.0f)); //clearing only z-buffer since background always filled with clouds @@ -1672,7 +1674,7 @@ void WaterRenderObjClass::Render(RenderInfoClass & rinfo) m_alphaClippingTexture->Set_Mag_Filter(TextureClass::FILTER_TYPE_NONE); m_alphaClippingTexture->Set_Mip_Mapping(TextureClass::FILTER_TYPE_NONE); - DX8Wrapper::Set_Texture(0,m_alphaClippingTexture); + g_renderBackend->Set_Texture(0,m_alphaClippingTexture); //TODO: Will have to make sure that the shader system is not resetting my stage 1 setup //while rendering the scene @@ -1706,7 +1708,7 @@ void WaterRenderObjClass::Render(RenderInfoClass & rinfo) rinfo.Camera.Apply(); //force an update of all the camera dependent parameters like frustum clip planes //clear the z-buffer to remove changes made by objects inside mirror - DX8Wrapper::Clear(false,true,Vector3(0.1f,0.1f,0.1f)); + g_renderBackend->Clear(false,true,Vector3(0.1f,0.1f,0.1f)); } #endif @@ -1741,8 +1743,8 @@ void WaterRenderObjClass::Render(RenderInfoClass & rinfo) //Clean up after any pixel shaders. //Force render state apply so that the null texture gets applied to D3D, thus releasing shroud reference count. - DX8Wrapper::Apply_Render_State_Changes(); - DX8Wrapper::Invalidate_Cached_Render_States(); + g_renderBackend->Apply_Render_State_Changes(); + g_renderBackend->Invalidate_Cached_Render_States(); if (m_waterTrackSystem) m_waterTrackSystem->flush(rinfo); @@ -1811,12 +1813,12 @@ void WaterRenderObjClass::drawSea(RenderInfoClass & rinfo) matWW3D._23=1.0f; matWW3D._44=1.0f; - DX8Wrapper::Set_Transform(D3DTS_WORLD,Transform); //position the water surface - DX8Wrapper::Set_Texture(0,nullptr); //we'll be setting our own textures, so reset W3D - DX8Wrapper::Set_Texture(1,nullptr); //we'll be setting our own textures, so reset W3D + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,Transform); //position the water surface + g_renderBackend->Set_Texture(0,nullptr); //we'll be setting our own textures, so reset W3D + g_renderBackend->Set_Texture(1,nullptr); //we'll be setting our own textures, so reset W3D - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices Vector3 camTran; @@ -1969,7 +1971,7 @@ void WaterRenderObjClass::drawSea(RenderInfoClass & rinfo) m_pDev->SetPixelShader(0); //turn off pixel shader m_pDev->SetVertexShader(DX8_FVF_XYZDUV1); //turn off custom vertex shader - DX8Wrapper::Invalidate_Cached_Render_States(); + g_renderBackend->Invalidate_Cached_Render_States(); if (TheTerrainRenderObject->getShroud()) { @@ -2077,7 +2079,7 @@ void WaterRenderObjClass::renderSky() VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); ShaderClass m_shader2=ShaderClass::_PresetOpaqueShader; @@ -2085,9 +2087,9 @@ void WaterRenderObjClass::renderSky() m_shader2.Set_Depth_Compare(ShaderClass::PASS_ALWAYS); //no need to check against z-buffer, sky always rendered first. m_shader2.Set_Depth_Mask(ShaderClass::DEPTH_WRITE_DISABLE); //sky is always behind everything so no need to update z-buffer - DX8Wrapper::Set_Shader(m_shader2); + g_renderBackend->Set_Shader(m_shader2); - DX8Wrapper::Set_Texture(0,setting->skyTexture); + g_renderBackend->Set_Texture(0,setting->skyTexture); //draw an infinite sky plane DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,4); @@ -2126,14 +2128,14 @@ void WaterRenderObjClass::renderSky() } } - DX8Wrapper::Set_Index_Buffer(m_indexBuffer,0); - DX8Wrapper::Set_Vertex_Buffer(vb_access); + g_renderBackend->Set_Index_Buffer(m_indexBuffer,0); + g_renderBackend->Set_Vertex_Buffer(vb_access); Matrix3D tm(1); tm.Set_Translation(Vector3(0,0,0)); - DX8Wrapper::Set_Transform(D3DTS_WORLD,tm); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,tm); - DX8Wrapper::Draw_Triangles( 0,2, 0, 4); //draw a quad, 2 triangles, 4 verts + g_renderBackend->Draw_Triangles( 0,2, 0, 4); //draw a quad, 2 triangles, 4 verts } //------------------------------------------------------------------------------------------------- @@ -2173,11 +2175,11 @@ void WaterRenderObjClass::renderSkyBody(Matrix3D *mat) tm.Adjust_Translation(Vector3(SKYBODY_X,SKYBODY_Y,SKYBODY_HEIGHT)); - DX8Wrapper::Set_Transform(D3DTS_WORLD,tm); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,tm); VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); ShaderClass m_shader2=ShaderClass::_PresetAlphaShader; @@ -2185,13 +2187,13 @@ void WaterRenderObjClass::renderSkyBody(Matrix3D *mat) m_shader2.Set_Depth_Compare(ShaderClass::PASS_ALWAYS); //no need to check against z-buffer, sky always rendered first. m_shader2.Set_Depth_Mask(ShaderClass::DEPTH_WRITE_DISABLE); //sky is always behind everything so no need to update z-buffer - DX8Wrapper::Set_Shader(m_shader2); + g_renderBackend->Set_Shader(m_shader2); -// DX8Wrapper::Set_Shader(ShaderClass::/*_PresetAdditiveShader*//*_PresetOpaqueShader*/_PresetAlphaShader); -// DX8Wrapper::Set_Texture(0,setting->skyBodyTexture); +// g_renderBackend->Set_Shader(ShaderClass::/*_PresetAdditiveShader*//*_PresetOpaqueShader*/_PresetAlphaShader); +// g_renderBackend->Set_Texture(0,setting->skyBodyTexture); - DX8Wrapper::Set_Texture(0,m_alphaClippingTexture); + g_renderBackend->Set_Texture(0,m_alphaClippingTexture); //draw an infinite sky plane DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,4); @@ -2230,10 +2232,10 @@ void WaterRenderObjClass::renderSkyBody(Matrix3D *mat) } } - DX8Wrapper::Set_Index_Buffer(m_indexBuffer,0); - DX8Wrapper::Set_Vertex_Buffer(vb_access); + g_renderBackend->Set_Index_Buffer(m_indexBuffer,0); + g_renderBackend->Set_Vertex_Buffer(vb_access); - DX8Wrapper::Draw_Triangles( 0,2, 0, 4); //draw a quad, 2 triangles, 4 verts + g_renderBackend->Draw_Triangles( 0,2, 0, 4); //draw a quad, 2 triangles, 4 verts } //Defines for procedural water animation. @@ -2380,8 +2382,8 @@ void WaterRenderObjClass::renderWaterMesh() m_vertexBufferD3D->Unlock(); - DX8Wrapper::Set_Transform(D3DTS_WORLD,Transform); //position the water surface - DX8Wrapper::Set_Material(m_meshVertexMaterialClass); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,Transform); //position the water surface + g_renderBackend->Set_Material(m_meshVertexMaterialClass); ShaderClass::CullModeType oldCullMode=m_shaderClass.Get_Cull_Mode(); @@ -2390,13 +2392,13 @@ void WaterRenderObjClass::renderWaterMesh() m_shaderClass.Set_Cull_Mode(ShaderClass::CULL_MODE_ENABLE); //water should be visible from both sides - DX8Wrapper::Set_Shader(m_shaderClass); + g_renderBackend->Set_Shader(m_shaderClass); #if 1 setupFlatWaterShader(); #else - //DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); - DX8Wrapper::Set_Texture(0,setting->waterTexture); - DX8Wrapper::Set_Texture(1,setting->waterTexture); + //g_renderBackend->Set_Shader(ShaderClass::_PresetOpaqueShader); + g_renderBackend->Set_Texture(0,setting->waterTexture); + g_renderBackend->Set_Texture(1,setting->waterTexture); DX8Wrapper::Set_Light(0,*m_meshLight); DX8Wrapper::Set_Light(1,nullptr); @@ -2408,7 +2410,7 @@ void WaterRenderObjClass::renderWaterMesh() DX8Wrapper::Set_DX8_Render_State(D3DRS_LOCALVIEWER,TRUE); */ - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices #endif @@ -2449,8 +2451,8 @@ void WaterRenderObjClass::renderWaterMesh() m_vertexBufferD3DOffset += mx*my; //advance past vertices already in buffer - DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Set_Texture(1,nullptr); + g_renderBackend->Set_Texture(0,nullptr); + g_renderBackend->Set_Texture(1,nullptr); ShaderClass::Invalidate(); m_shaderClass.Set_Cull_Mode(oldCullMode); //water should be visible from both sides @@ -2717,7 +2719,7 @@ Real WaterRenderObjClass::getWaterHeight(Real x, Real y) //------------------------------------------------------------------------------------------------- void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) { - DX8Wrapper::Invalidate_Cached_Render_States(); ///@todo: Figure out why rivers don't draw without reset of all states. + g_renderBackend->Invalidate_Cached_Render_States(); ///@todo: Figure out why rivers don't draw without reset of all states. Int rectangleCount = pTrig->getNumPoints()/2; rectangleCount--; @@ -2900,10 +2902,10 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) Matrix3D tm(1); - DX8Wrapper::Set_Transform(D3DTS_WORLD,tm); //position the water surface - DX8Wrapper::Set_Index_Buffer(ib_access,0); - DX8Wrapper::Set_Vertex_Buffer(vb_access); - DX8Wrapper::Set_Texture(0,m_riverTexture); //set to blue + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,tm); //position the water surface + g_renderBackend->Set_Index_Buffer(ib_access,0); + g_renderBackend->Set_Vertex_Buffer(vb_access); + g_renderBackend->Set_Texture(0,m_riverTexture); //set to blue setupJbaWaterShader(); @@ -2922,7 +2924,7 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) if (wireframeForDebug) { DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME); } - DX8Wrapper::Draw_Triangles( 0,rectangleCount*2, 0, (rectangleCount+1)*2); + g_renderBackend->Draw_Triangles( 0,rectangleCount*2, 0, (rectangleCount+1)*2); if (wireframeForDebug) { DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID); } @@ -2941,20 +2943,20 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) void WaterRenderObjClass::setupFlatWaterShader() { - DX8Wrapper::Set_Texture(0,m_riverTexture); + g_renderBackend->Set_Texture(0,m_riverTexture); if (!TheWaterTransparency->m_additiveBlend) - DX8Wrapper::Set_Shader(ShaderClass::_PresetAlphaShader); + g_renderBackend->Set_Shader(ShaderClass::_PresetAlphaShader); else - DX8Wrapper::Set_Shader(ShaderClass::_PresetAdditiveShader); + g_renderBackend->Set_Shader(ShaderClass::_PresetAdditiveShader); VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); m_riverTexture->Get_Filter().Set_Mag_Filter(TextureFilterClass::FILTER_TYPE_BEST); m_riverTexture->Get_Filter().Set_Min_Filter(TextureFilterClass::FILTER_TYPE_BEST); m_riverTexture->Get_Filter().Set_Mip_Mapping(TextureFilterClass::FILTER_TYPE_BEST); - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices //Setup shroud to render in same pass as water if (m_trapezoidWaterPixelShader) @@ -3262,9 +3264,9 @@ void WaterRenderObjClass::drawTrapezoidWater(Vector3 points[4]) Matrix3D tm(1); - DX8Wrapper::Set_Transform(D3DTS_WORLD,tm); //position the water surface - DX8Wrapper::Set_Index_Buffer(ib_access,0); - DX8Wrapper::Set_Vertex_Buffer(vb_access); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,tm); //position the water surface + g_renderBackend->Set_Index_Buffer(ib_access,0); + g_renderBackend->Set_Vertex_Buffer(vb_access); setupFlatWaterShader();// lorenzen sez use the alpha shader @@ -3300,7 +3302,7 @@ void WaterRenderObjClass::drawTrapezoidWater(Vector3 points[4]) // } //#endif // FEATHER_WATER //#endif //WAVY_WATER - DX8Wrapper::Draw_Triangles( 0,rectangleCount*2, 0, (rectangleCount+1)*2);//lorenzen thinks this is where to itereate the soft shoreline effect + g_renderBackend->Draw_Triangles( 0,rectangleCount*2, 0, (rectangleCount+1)*2);//lorenzen thinks this is where to itereate the soft shoreline effect } @@ -3309,7 +3311,7 @@ void WaterRenderObjClass::drawTrapezoidWater(Vector3 points[4]) if (false) { DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME); m_pDev->SetRenderState(D3DRS_ALPHABLENDENABLE , false); - DX8Wrapper::Draw_Triangles( 0,rectangleCount*2, 0, (rectangleCount+1)*2); + g_renderBackend->Draw_Triangles( 0,rectangleCount*2, 0, (rectangleCount+1)*2); m_pDev->SetRenderState(D3DRS_ALPHABLENDENABLE , true); DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID); } @@ -3342,7 +3344,7 @@ void WaterRenderObjClass::drawTrapezoidWater(Vector3 points[4]) //Shroud shader uses z-compare of EQUAL which wouldn't work on water because it doesn't //write to the zbuffer. Change to LESSEQUAL. DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL); - DX8Wrapper::Draw_Triangles( 0,rectangleCount*2, 0, (rectangleCount+1)*2); + g_renderBackend->Draw_Triangles( 0,rectangleCount*2, 0, (rectangleCount+1)*2); DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_ZFUNC, D3DCMP_EQUAL); W3DShaderManager::resetShader(W3DShaderManager::ST_SHROUD_TEXTURE); } @@ -3375,12 +3377,12 @@ void WaterRenderObjClass::renderSkyBody(Matrix3D *mat) V3=-vRight-vUp; VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vmat); + g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); - DX8Wrapper::Set_Shader(ShaderClass::/*_PresetAdditiveShader*//*_PresetOpaqueShader*/_PresetAlphaShader); -// DX8Wrapper::Set_Texture(0,setting->skyBodyTexture); + g_renderBackend->Set_Shader(ShaderClass::/*_PresetAdditiveShader*//*_PresetOpaqueShader*/_PresetAlphaShader); +// g_renderBackend->Set_Texture(0,setting->skyBodyTexture); - DX8Wrapper::Set_Texture(0,m_alphaClippingTexture); + g_renderBackend->Set_Texture(0,m_alphaClippingTexture); //draw an infinite sky plane DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC_DX8,4); @@ -3419,15 +3421,15 @@ void WaterRenderObjClass::renderSkyBody(Matrix3D *mat) } } - DX8Wrapper::Set_Index_Buffer(m_indexBuffer,0); - DX8Wrapper::Set_Vertex_Buffer(vb_access); + g_renderBackend->Set_Index_Buffer(m_indexBuffer,0); + g_renderBackend->Set_Vertex_Buffer(vb_access); Matrix3D tm(1); //set position of skybody in world // tm.Set_Translation(Vector3(40,0,0)); - DX8Wrapper::Set_Transform(D3DTS_WORLD,tm); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,tm); - DX8Wrapper::Draw_Triangles( 0,2, 0, 4); //draw a quad, 2 triangles, 4 verts + g_renderBackend->Draw_Triangles( 0,2, 0, 4); //draw a quad, 2 triangles, 4 verts } #endif @@ -3502,4 +3504,3 @@ void WaterRenderObjClass::loadPostProcess() } - diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWaterTracks.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWaterTracks.cpp index fb3bf4e3cd6..3004ad2da98 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWaterTracks.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWaterTracks.cpp @@ -62,6 +62,8 @@ #include "WW3D2/camera.h" #include "WW3D2/assetmgr.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" //number of vertex pages allocated - allows double buffering of vertex updates. //while one is being rendered, another is being updated. Improves HW parallelism. @@ -473,8 +475,8 @@ Int WaterTracksObj::render(DX8VertexBufferClass *vertexBuffer, Int batchStart) Int idxCount=(m_y-1)*(m_x*2+2) - 2; //index count - DX8Wrapper::Set_Index_Buffer(TheWaterTracksRenderSystem->m_indexBuffer,batchStart); - DX8Wrapper::Draw_Strip(0,idxCount-2,0,m_x*m_y); //there are always n-2 primitives for n index strip. + g_renderBackend->Set_Index_Buffer(TheWaterTracksRenderSystem->m_indexBuffer,batchStart); + g_renderBackend->Draw_Strip(0,idxCount-2,0,m_x*m_y); //there are always n-2 primitives for n index strip. return batchStart+m_x*m_y; //return new offset into unused area of vertex buffer } @@ -886,15 +888,15 @@ Try improving the fit to vertical surfaces like cliffs. diffuseLight=REAL_TO_INT(shadeB) | (REAL_TO_INT(shadeG) << 8) | (REAL_TO_INT(shadeR) << 16); Matrix3D tm(1); ///set to identity - DX8Wrapper::Set_Transform(D3DTS_WORLD,tm); //position the water surface + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,tm); //position the water surface - DX8Wrapper::Set_Material(m_vertexMaterialClass); - DX8Wrapper::Set_Shader(m_shaderClass); + g_renderBackend->Set_Material(m_vertexMaterialClass); + g_renderBackend->Set_Shader(m_shaderClass); - DX8Wrapper::Set_Vertex_Buffer(m_vertexBuffer); + g_renderBackend->Set_Vertex_Buffer(m_vertexBuffer); DX8Wrapper::Set_DX8_Render_State(D3DRS_ZBIAS,8); //Force apply of render states so we can override them. - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); if (TheTerrainRenderObject->getShroud()) { @@ -919,7 +921,7 @@ Try improving the fit to vertical surfaces like cliffs. while( mod ) { if (LastTextureType != mod->m_type) - DX8Wrapper::Set_Texture(0,mod->m_stageZeroTexture); + g_renderBackend->Set_Texture(0,mod->m_stageZeroTexture); Int vertsRendered=mod->render(m_vertexBuffer,m_batchStart); @@ -1296,7 +1298,7 @@ void TestWaterUpdate() Real ydiff=terrainPointEnd.y - terrainPointStart.y; if (sqrt (xdiff * xdiff + ydiff * ydiff) <= waveTypeInfo[currentWaveType].m_finalWidth) { TheDisplay->drawLine(mouseAnchor.x, mouseAnchor.y, screenPoint.x, screenPoint.y,1,0xffccccff); - DX8Wrapper::Invalidate_Cached_Render_States(); + g_renderBackend->Invalidate_Cached_Render_States(); ShaderClass::Invalidate(); } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h index 054c8c962b8..be8c9577cdc 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h @@ -190,7 +190,7 @@ class IRenderBackend // Vertex / index buffers // ------------------------------------------------------------------------- - virtual void Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int stream) = 0; + virtual void Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int stream = 0) = 0; virtual void Set_Vertex_Buffer(const DynamicVBAccessClass & vba) = 0; virtual void Set_Index_Buffer(const IndexBufferClass * ib, unsigned short index_base_offset) = 0; virtual void Set_Index_Buffer(const DynamicIBAccessClass & iba, unsigned short index_base_offset) = 0; @@ -303,8 +303,8 @@ class IRenderBackend // Render targets // ------------------------------------------------------------------------- - virtual TextureClass * Create_Render_Target(int width, int height, WW3DFormat format) = 0; - virtual void Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass * ztexture) = 0; + virtual TextureClass * Create_Render_Target(int width, int height, WW3DFormat format = WW3D_FORMAT_UNKNOWN) = 0; + virtual void Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass * ztexture = nullptr) = 0; virtual bool Is_Render_To_Texture() = 0; virtual void Set_Shadow_Map(int idx, ZTextureClass * ztex) = 0; virtual ZTextureClass * Get_Shadow_Map(int idx) = 0; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h index 8a6f3d26495..52c254a84e4 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h @@ -58,6 +58,18 @@ #include "dx8indexbuffer.h" #include "WW3D2/vertmaterial.h" +// TheSuperHackers @refactor bobtista 10/04/2026 deprecation sweep. +// Flag DX8Wrapper methods that have an IRenderBackend equivalent so the +// compiler lists every remaining call site as a warning. The WW3D2 library +// itself (g_ww3d2 STATIC) defines GGC_ALLOW_DX8WRAPPER to suppress the +// warning inside DX8Backend.cpp / dx8wrapper.cpp and the rest of WW3D2, +// which legitimately call DX8Wrapper directly. VC6 tools get a no-op. +#if defined(_MSC_VER) && _MSC_VER >= 1400 && !defined(GGC_ALLOW_DX8WRAPPER) +# define GGC_RB_DEPRECATED __declspec(deprecated("migrate to g_renderBackend equivalent")) +#else +# define GGC_RB_DEPRECATED +#endif + /* ** Registry value names */ @@ -281,30 +293,30 @@ class DX8Wrapper static void Do_Onetime_Device_Dependent_Inits(); static void Do_Onetime_Device_Dependent_Shutdowns(); - static bool Is_Device_Lost() { return IsDeviceLost; } + GGC_RB_DEPRECATED static bool Is_Device_Lost() { return IsDeviceLost; } static bool Is_Initted() { return IsInitted; } - static bool Has_Stencil (); + GGC_RB_DEPRECATED static bool Has_Stencil (); static void Get_Format_Name(unsigned int format, StringClass *tex_format); /* ** Rendering */ - static void Begin_Scene(); - static void End_Scene(bool flip_frame = true); + GGC_RB_DEPRECATED static void Begin_Scene(); + GGC_RB_DEPRECATED static void End_Scene(bool flip_frame = true); // Flip until the primary buffer is visible. - static void Flip_To_Primary(); + GGC_RB_DEPRECATED static void Flip_To_Primary(); - static void Clear(bool clear_color, bool clear_z_stencil, const Vector3 &color, float dest_alpha=0.0f, float z=1.0f, unsigned int stencil=0); + GGC_RB_DEPRECATED static void Clear(bool clear_color, bool clear_z_stencil, const Vector3 &color, float dest_alpha=0.0f, float z=1.0f, unsigned int stencil=0); - static void Set_Viewport(CONST D3DVIEWPORT8* pViewport); + GGC_RB_DEPRECATED static void Set_Viewport(CONST D3DVIEWPORT8* pViewport); - static void Set_Vertex_Buffer(const VertexBufferClass* vb, unsigned stream=0); - static void Set_Vertex_Buffer(const DynamicVBAccessClass& vba); - static void Set_Index_Buffer(const IndexBufferClass* ib,unsigned short index_base_offset); - static void Set_Index_Buffer(const DynamicIBAccessClass& iba,unsigned short index_base_offset); - static void Set_Index_Buffer_Index_Offset(unsigned offset); + GGC_RB_DEPRECATED static void Set_Vertex_Buffer(const VertexBufferClass* vb, unsigned stream=0); + GGC_RB_DEPRECATED static void Set_Vertex_Buffer(const DynamicVBAccessClass& vba); + GGC_RB_DEPRECATED static void Set_Index_Buffer(const IndexBufferClass* ib,unsigned short index_base_offset); + GGC_RB_DEPRECATED static void Set_Index_Buffer(const DynamicIBAccessClass& iba,unsigned short index_base_offset); + GGC_RB_DEPRECATED static void Set_Index_Buffer_Index_Offset(unsigned offset); static void Get_Render_State(RenderStateStruct& state); static void Set_Render_State(const RenderStateStruct& state); @@ -312,19 +324,19 @@ class DX8Wrapper static void Set_DX8_Material(const D3DMATERIAL8* mat); - static void Set_Gamma(float gamma,float bright,float contrast,bool calibrate=true,bool uselimit=true); + GGC_RB_DEPRECATED static void Set_Gamma(float gamma,float bright,float contrast,bool calibrate=true,bool uselimit=true); // Set_ and Get_Transform() functions take the matrix in Westwood convention format. - static void Set_Projection_Transform_With_Z_Bias(const Matrix4x4& matrix,float znear, float zfar); // pointer to 16 matrices + GGC_RB_DEPRECATED static void Set_Projection_Transform_With_Z_Bias(const Matrix4x4& matrix,float znear, float zfar); // pointer to 16 matrices - static void Set_Transform(D3DTRANSFORMSTATETYPE transform,const Matrix4x4& m); - static void Set_Transform(D3DTRANSFORMSTATETYPE transform,const Matrix3D& m); - static void Get_Transform(D3DTRANSFORMSTATETYPE transform, Matrix4x4& m); - static void Set_World_Identity(); - static void Set_View_Identity(); - static bool Is_World_Identity(); - static bool Is_View_Identity(); + GGC_RB_DEPRECATED static void Set_Transform(D3DTRANSFORMSTATETYPE transform,const Matrix4x4& m); + GGC_RB_DEPRECATED static void Set_Transform(D3DTRANSFORMSTATETYPE transform,const Matrix3D& m); + GGC_RB_DEPRECATED static void Get_Transform(D3DTRANSFORMSTATETYPE transform, Matrix4x4& m); + GGC_RB_DEPRECATED static void Set_World_Identity(); + GGC_RB_DEPRECATED static void Set_View_Identity(); + GGC_RB_DEPRECATED static bool Is_World_Identity(); + GGC_RB_DEPRECATED static bool Is_View_Identity(); // Note that *_DX8_Transform() functions take the matrix in DX8 format - transposed from Westwood convention. @@ -336,33 +348,33 @@ class DX8Wrapper static void Set_DX8_Clip_Plane(DWORD Index, CONST float* pPlane); static void Set_DX8_Texture_Stage_State(unsigned stage, D3DTEXTURESTAGESTATETYPE state, unsigned value); static void Set_DX8_Texture(unsigned int stage, IDirect3DBaseTexture8* texture); - static void Set_Light_Environment(LightEnvironmentClass* light_env); - static LightEnvironmentClass* Get_Light_Environment() { return Light_Environment; } - static void Set_Fog(bool enable, const Vector3 &color, float start, float end); + GGC_RB_DEPRECATED static void Set_Light_Environment(LightEnvironmentClass* light_env); + GGC_RB_DEPRECATED static LightEnvironmentClass* Get_Light_Environment() { return Light_Environment; } + GGC_RB_DEPRECATED static void Set_Fog(bool enable, const Vector3 &color, float start, float end); // Deferred - static void Set_Shader(const ShaderClass& shader); - static void Get_Shader(ShaderClass& shader); - static void Set_Texture(unsigned stage,TextureBaseClass* texture); - static void Set_Material(const VertexMaterialClass* material); + GGC_RB_DEPRECATED static void Set_Shader(const ShaderClass& shader); + GGC_RB_DEPRECATED static void Get_Shader(ShaderClass& shader); + GGC_RB_DEPRECATED static void Set_Texture(unsigned stage,TextureBaseClass* texture); + GGC_RB_DEPRECATED static void Set_Material(const VertexMaterialClass* material); static void Set_Light(unsigned index,const D3DLIGHT8* light); - static void Set_Light(unsigned index,const LightClass &light); + GGC_RB_DEPRECATED static void Set_Light(unsigned index,const LightClass &light); - static void Apply_Render_State_Changes(); // Apply deferred render state changes (will be called automatically by Draw...) + GGC_RB_DEPRECATED static void Apply_Render_State_Changes(); // Apply deferred render state changes (will be called automatically by Draw...) - static void Draw_Triangles( + GGC_RB_DEPRECATED static void Draw_Triangles( unsigned buffer_type, unsigned short start_index, unsigned short polygon_count, unsigned short min_vertex_index, unsigned short vertex_count); - static void Draw_Triangles( + GGC_RB_DEPRECATED static void Draw_Triangles( unsigned short start_index, unsigned short polygon_count, unsigned short min_vertex_index, unsigned short vertex_count); - static void Draw_Strip( + GGC_RB_DEPRECATED static void Draw_Strip( unsigned short start_index, unsigned short index_count, unsigned short min_vertex_index, @@ -483,13 +495,13 @@ class DX8Wrapper /* ** Render target interface. If render target format is WW3D_FORMAT_UNKNOWN, current display format is used. */ - static TextureClass * Create_Render_Target (int width, int height, WW3DFormat format = WW3D_FORMAT_UNKNOWN); + GGC_RB_DEPRECATED static TextureClass * Create_Render_Target (int width, int height, WW3DFormat format = WW3D_FORMAT_UNKNOWN); static void Set_Render_Target (IDirect3DSurface8 *render_target, bool use_default_depth_buffer = false); static void Set_Render_Target (IDirect3DSurface8* render_target, IDirect3DSurface8* dpeth_buffer); static void Set_Render_Target (IDirect3DSwapChain8 *swap_chain); - static bool Is_Render_To_Texture() { return IsRenderToTexture; } + GGC_RB_DEPRECATED static bool Is_Render_To_Texture() { return IsRenderToTexture; } // for depth map support KJM V static void Create_Render_Target @@ -501,26 +513,26 @@ class DX8Wrapper TextureClass** target, ZTextureClass** depth_buffer ); - static void Set_Render_Target_With_Z (TextureClass * texture, ZTextureClass* ztexture=nullptr); + GGC_RB_DEPRECATED static void Set_Render_Target_With_Z (TextureClass * texture, ZTextureClass* ztexture=nullptr); - static void Set_Shadow_Map(int idx, ZTextureClass* ztex) { Shadow_Map[idx]=ztex; } - static ZTextureClass* Get_Shadow_Map(int idx) { return Shadow_Map[idx]; } + GGC_RB_DEPRECATED static void Set_Shadow_Map(int idx, ZTextureClass* ztex) { Shadow_Map[idx]=ztex; } + GGC_RB_DEPRECATED static ZTextureClass* Get_Shadow_Map(int idx) { return Shadow_Map[idx]; } // for depth map support KJM ^ // shader system updates KJM v - static void Apply_Default_State(); + GGC_RB_DEPRECATED static void Apply_Default_State(); - static void Set_Vertex_Shader(DWORD vertex_shader); - static void Set_Pixel_Shader(DWORD pixel_shader); + GGC_RB_DEPRECATED static void Set_Vertex_Shader(DWORD vertex_shader); + GGC_RB_DEPRECATED static void Set_Pixel_Shader(DWORD pixel_shader); - static void Set_Vertex_Shader_Constant(int reg, const void* data, int count); - static void Set_Pixel_Shader_Constant(int reg, const void* data, int count); + GGC_RB_DEPRECATED static void Set_Vertex_Shader_Constant(int reg, const void* data, int count); + GGC_RB_DEPRECATED static void Set_Pixel_Shader_Constant(int reg, const void* data, int count); static DWORD Get_Vertex_Processing_Behavior() { return Vertex_Processing_Behavior; } // Needed by scene lighting class - static void Set_Ambient(const Vector3& color); - static const Vector3& Get_Ambient() { return Ambient_Color; } + GGC_RB_DEPRECATED static void Set_Ambient(const Vector3& color); + GGC_RB_DEPRECATED static const Vector3& Get_Ambient() { return Ambient_Color; } // shader system updates KJM ^ @@ -564,7 +576,7 @@ class DX8Wrapper static const char* Get_DX8_Debug_Monitor_Token_Name(unsigned value); static const char* Get_DX8_Blend_Op_Name(unsigned value); - static void Invalidate_Cached_Render_States(); + GGC_RB_DEPRECATED static void Invalidate_Cached_Render_States(); static void Set_Draw_Polygon_Low_Bound_Limit(unsigned n) { DrawPolygonLowBoundLimit=n; } diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DCustomEdging.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DCustomEdging.cpp index 89fc77e9a97..9c903210770 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DCustomEdging.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DCustomEdging.cpp @@ -57,6 +57,8 @@ #include "W3DDevice/GameClient/W3DDynamicLight.h" #include "WW3D2/camera.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/dx8renderer.h" #include "WW3D2/mesh.h" #include "WW3D2/meshmdl.h" @@ -354,45 +356,45 @@ void W3DCustomEdging::drawEdging(WorldHeightMap *pMap, Int minX, Int maxX, Int m } TextureClass *edgeTex = pMap->getEdgeTerrainTexture(); // Setup the vertex buffer, shader & texture. - DX8Wrapper::Set_Index_Buffer(m_indexEdging,0); - DX8Wrapper::Set_Vertex_Buffer(m_vertexEdging); - DX8Wrapper::Set_Shader(detailAlphaTestShader); + g_renderBackend->Set_Index_Buffer(m_indexEdging,0); + g_renderBackend->Set_Vertex_Buffer(m_vertexEdging); + g_renderBackend->Set_Shader(detailAlphaTestShader); #ifdef RTS_DEBUG - //DX8Wrapper::Set_Shader(detailShader); // shows clipping. + //g_renderBackend->Set_Shader(detailShader); // shows clipping. #endif - DX8Wrapper::Set_Texture(0,terrainTexture); - DX8Wrapper::Set_Texture(1,edgeTex); - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Set_Texture(0,terrainTexture); + g_renderBackend->Set_Texture(1,edgeTex); + g_renderBackend->Apply_Render_State_Changes(); DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF,0x7B); DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC,D3DCMP_LESSEQUAL); //pass pixels who's alpha is not zero DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHATESTENABLE, true); //test pixels if transparent(clipped) before rendering. - DX8Wrapper::Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); + g_renderBackend->Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); - DX8Wrapper::Set_Texture(0,edgeTex); - DX8Wrapper::Set_Texture(1, nullptr); + g_renderBackend->Set_Texture(0,edgeTex); + g_renderBackend->Set_Texture(1, nullptr); // Draw the custom edge. - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF,0x84); DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC,D3DCMP_GREATEREQUAL); //pass pixels who's alpha is not zero DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHATESTENABLE, true); //test pixels if transparent(clipped) before rendering. - DX8Wrapper::Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); + g_renderBackend->Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); #if 0 // Dumps out unmasked data. DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,false); DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHATESTENABLE, false); //test pixels if transparent(clipped) before rendering. - DX8Wrapper::Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); + g_renderBackend->Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); #endif - DX8Wrapper::Set_Texture(1, nullptr); + g_renderBackend->Set_Texture(1, nullptr); if (cloudTexture) { - DX8Wrapper::Set_Shader(detailOpaqueShader); - DX8Wrapper::Apply_Render_State_Changes(); - DX8Wrapper::Set_Texture(1,edgeTex); - DX8Wrapper::Apply_Render_State_Changes(); - DX8Wrapper::Set_Texture(0,cloudTexture); - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Set_Shader(detailOpaqueShader); + g_renderBackend->Apply_Render_State_Changes(); + g_renderBackend->Set_Texture(1,edgeTex); + g_renderBackend->Apply_Render_State_Changes(); + g_renderBackend->Set_Texture(0,cloudTexture); + g_renderBackend->Apply_Render_State_Changes(); #if 1 DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAARG1, D3DTA_CURRENT ); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 ); @@ -411,14 +413,14 @@ void W3DCustomEdging::drawEdging(WorldHeightMap *pMap, Int minX, Int maxX, Int m DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,true); DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_DESTCOLOR); DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_ZERO); - DX8Wrapper::Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); + g_renderBackend->Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); } if (noiseTexture) { - DX8Wrapper::Set_Texture(1, nullptr); - DX8Wrapper::Set_Texture(0,noiseTexture); - DX8Wrapper::Apply_Render_State_Changes(); - DX8Wrapper::Set_Texture(1,edgeTex); - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Set_Texture(1, nullptr); + g_renderBackend->Set_Texture(0,noiseTexture); + g_renderBackend->Apply_Render_State_Changes(); + g_renderBackend->Set_Texture(1,edgeTex); + g_renderBackend->Apply_Render_State_Changes(); DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF,0x80); DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC,D3DCMP_NOTEQUAL); //pass pixels who's alpha is not zero @@ -426,7 +428,7 @@ void W3DCustomEdging::drawEdging(WorldHeightMap *pMap, Int minX, Int maxX, Int m DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,true); DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_DESTCOLOR); DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_ZERO); - DX8Wrapper::Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); + g_renderBackend->Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); } } diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 29ef7f98967..0ab71167154 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -91,6 +91,8 @@ static void drawFramerateBar(); #include "WW3D2/dx8caps.h" #include "WW3D2/ww3dformat.h" #include "WW3D2/agg_def.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/render2dsentence.h" #include "WW3D2/sortingrenderer.h" #include "WW3D2/textureloader.h" @@ -498,7 +500,7 @@ void W3DDisplay::setGamma(Real gamma, Real bright, Real contrast, Bool calibrate if (m_windowed) return; //we don't allow gamma to change in window because it would affect desktop. - DX8Wrapper::Set_Gamma(gamma,bright,contrast,calibrate, false); + g_renderBackend->Set_Gamma(gamma,bright,contrast,calibrate, false); } /** Set resolution of display */ diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt b/Generals/Code/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt index ef4965eb60d..d07be1e40e1 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt @@ -235,6 +235,7 @@ target_sources(g_ww3d2 PRIVATE ${WW3D2_SRC}) target_compile_definitions(g_ww3d2 PRIVATE $<$:WINVER=0x0500> + GGC_ALLOW_DX8WRAPPER ) target_precompile_headers(g_ww3d2 PRIVATE diff --git a/Generals/Code/Tools/W3DView/CMakeLists.txt b/Generals/Code/Tools/W3DView/CMakeLists.txt index 4b1c4169642..3c1d4a08f68 100644 --- a/Generals/Code/Tools/W3DView/CMakeLists.txt +++ b/Generals/Code/Tools/W3DView/CMakeLists.txt @@ -16,6 +16,8 @@ target_link_libraries(g_w3dview PRIVATE winmm ) +target_compile_definitions(g_w3dview PRIVATE GGC_ALLOW_DX8WRAPPER) + if(WIN32 OR "${CMAKE_SYSTEM}" MATCHES "Windows") target_compile_definitions(g_w3dview PRIVATE _AFXDLL) set_target_properties(g_w3dview PROPERTIES OUTPUT_NAME "W3DViewV${RTS_BUILD_OUTPUT_SUFFIX}") diff --git a/Generals/Code/Tools/WorldBuilder/CMakeLists.txt b/Generals/Code/Tools/WorldBuilder/CMakeLists.txt index 1a65bb55d3d..ef6c3a80c7f 100644 --- a/Generals/Code/Tools/WorldBuilder/CMakeLists.txt +++ b/Generals/Code/Tools/WorldBuilder/CMakeLists.txt @@ -200,7 +200,7 @@ target_include_directories(g_worldbuilder PRIVATE res ) -target_compile_definitions(g_worldbuilder PRIVATE _AFXDLL) +target_compile_definitions(g_worldbuilder PRIVATE _AFXDLL GGC_ALLOW_DX8WRAPPER) target_precompile_headers(g_worldbuilder PRIVATE [["Utility/CppMacros.h"]] # Must be first, to be removed when abandoning VC6 diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DCustomEdging.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DCustomEdging.cpp index 8758e7d395f..1e275631658 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DCustomEdging.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DCustomEdging.cpp @@ -57,6 +57,8 @@ #include "W3DDevice/GameClient/W3DDynamicLight.h" #include "WW3D2/camera.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/dx8renderer.h" #include "WW3D2/mesh.h" #include "WW3D2/meshmdl.h" @@ -354,45 +356,45 @@ void W3DCustomEdging::drawEdging(WorldHeightMap *pMap, Int minX, Int maxX, Int m } TextureClass *edgeTex = pMap->getEdgeTerrainTexture(); // Setup the vertex buffer, shader & texture. - DX8Wrapper::Set_Index_Buffer(m_indexEdging,0); - DX8Wrapper::Set_Vertex_Buffer(m_vertexEdging); - DX8Wrapper::Set_Shader(detailAlphaTestShader); + g_renderBackend->Set_Index_Buffer(m_indexEdging,0); + g_renderBackend->Set_Vertex_Buffer(m_vertexEdging); + g_renderBackend->Set_Shader(detailAlphaTestShader); #ifdef RTS_DEBUG - //DX8Wrapper::Set_Shader(detailShader); // shows clipping. + //g_renderBackend->Set_Shader(detailShader); // shows clipping. #endif - DX8Wrapper::Set_Texture(0,terrainTexture); - DX8Wrapper::Set_Texture(1,edgeTex); - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Set_Texture(0,terrainTexture); + g_renderBackend->Set_Texture(1,edgeTex); + g_renderBackend->Apply_Render_State_Changes(); DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF,0x7B); DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC,D3DCMP_LESSEQUAL); //pass pixels who's alpha is not zero DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHATESTENABLE, true); //test pixels if transparent(clipped) before rendering. - DX8Wrapper::Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); + g_renderBackend->Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); - DX8Wrapper::Set_Texture(0,edgeTex); - DX8Wrapper::Set_Texture(1, nullptr); + g_renderBackend->Set_Texture(0,edgeTex); + g_renderBackend->Set_Texture(1, nullptr); // Draw the custom edge. - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF,0x84); DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC,D3DCMP_GREATEREQUAL); //pass pixels who's alpha is not zero DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHATESTENABLE, true); //test pixels if transparent(clipped) before rendering. - DX8Wrapper::Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); + g_renderBackend->Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); #if 0 // Dumps out unmasked data. DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,false); DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHATESTENABLE, false); //test pixels if transparent(clipped) before rendering. - DX8Wrapper::Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); + g_renderBackend->Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); #endif - DX8Wrapper::Set_Texture(1, nullptr); + g_renderBackend->Set_Texture(1, nullptr); if (cloudTexture) { - DX8Wrapper::Set_Shader(detailOpaqueShader); - DX8Wrapper::Apply_Render_State_Changes(); - DX8Wrapper::Set_Texture(1,edgeTex); - DX8Wrapper::Apply_Render_State_Changes(); - DX8Wrapper::Set_Texture(0,cloudTexture); - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Set_Shader(detailOpaqueShader); + g_renderBackend->Apply_Render_State_Changes(); + g_renderBackend->Set_Texture(1,edgeTex); + g_renderBackend->Apply_Render_State_Changes(); + g_renderBackend->Set_Texture(0,cloudTexture); + g_renderBackend->Apply_Render_State_Changes(); #if 1 DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAARG1, D3DTA_CURRENT ); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 ); @@ -411,14 +413,14 @@ void W3DCustomEdging::drawEdging(WorldHeightMap *pMap, Int minX, Int maxX, Int m DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,true); DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_DESTCOLOR); DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_ZERO); - DX8Wrapper::Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); + g_renderBackend->Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); } if (noiseTexture) { - DX8Wrapper::Set_Texture(1, nullptr); - DX8Wrapper::Set_Texture(0,noiseTexture); - DX8Wrapper::Apply_Render_State_Changes(); - DX8Wrapper::Set_Texture(1,edgeTex); - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Set_Texture(1, nullptr); + g_renderBackend->Set_Texture(0,noiseTexture); + g_renderBackend->Apply_Render_State_Changes(); + g_renderBackend->Set_Texture(1,edgeTex); + g_renderBackend->Apply_Render_State_Changes(); DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF,0x80); DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC,D3DCMP_NOTEQUAL); //pass pixels who's alpha is not zero @@ -426,7 +428,7 @@ void W3DCustomEdging::drawEdging(WorldHeightMap *pMap, Int minX, Int maxX, Int m DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,true); DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_DESTCOLOR); DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_ZERO); - DX8Wrapper::Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); + g_renderBackend->Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); } } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 4e755562d15..8941965c08b 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -92,6 +92,8 @@ static void drawFramerateBar(); #include "WW3D2/dx8caps.h" #include "WW3D2/ww3dformat.h" #include "WW3D2/agg_def.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/render2dsentence.h" #include "WW3D2/sortingrenderer.h" #include "WW3D2/textureloader.h" @@ -629,7 +631,7 @@ void W3DDisplay::setGamma(Real gamma, Real bright, Real contrast, Bool calibrate if (m_windowed) return; //we don't allow gamma to change in window because it would affect desktop. - DX8Wrapper::Set_Gamma(gamma,bright,contrast,calibrate, false); + g_renderBackend->Set_Gamma(gamma,bright,contrast,calibrate, false); } /** Set resolution of display */ diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt index 52e3470e6fd..4ab7f12e259 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt @@ -240,6 +240,7 @@ target_sources(z_ww3d2 PRIVATE ${WW3D2_SRC}) target_compile_definitions(z_ww3d2 PRIVATE $<$:WINVER=0x0500> + GGC_ALLOW_DX8WRAPPER ) target_precompile_headers(z_ww3d2 PRIVATE diff --git a/GeneralsMD/Code/Tools/W3DView/CMakeLists.txt b/GeneralsMD/Code/Tools/W3DView/CMakeLists.txt index bdbaaa4cde6..0a679efb2de 100644 --- a/GeneralsMD/Code/Tools/W3DView/CMakeLists.txt +++ b/GeneralsMD/Code/Tools/W3DView/CMakeLists.txt @@ -16,6 +16,8 @@ target_link_libraries(z_w3dview PRIVATE zi_always ) +target_compile_definitions(z_w3dview PRIVATE GGC_ALLOW_DX8WRAPPER) + if(WIN32 OR "${CMAKE_SYSTEM}" MATCHES "Windows") target_compile_definitions(z_w3dview PRIVATE _AFXDLL) set_target_properties(z_w3dview PROPERTIES OUTPUT_NAME "W3DViewZH${RTS_BUILD_OUTPUT_SUFFIX}") diff --git a/GeneralsMD/Code/Tools/WorldBuilder/CMakeLists.txt b/GeneralsMD/Code/Tools/WorldBuilder/CMakeLists.txt index c377a85c87c..db846881c5e 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/CMakeLists.txt +++ b/GeneralsMD/Code/Tools/WorldBuilder/CMakeLists.txt @@ -230,7 +230,7 @@ target_link_libraries(z_worldbuilder PRIVATE if(WIN32 OR "${CMAKE_SYSTEM}" MATCHES "Windows") target_link_options(z_worldbuilder PRIVATE /NODEFAULTLIB:libci.lib /NODEFAULTLIB:libc.lib) - target_compile_definitions(z_worldbuilder PRIVATE _AFXDLL) + target_compile_definitions(z_worldbuilder PRIVATE _AFXDLL GGC_ALLOW_DX8WRAPPER) target_sources(z_worldbuilder PRIVATE res/WorldBuilder.rc) set_target_properties(z_worldbuilder PROPERTIES OUTPUT_NAME "WorldBuilderZH${RTS_BUILD_OUTPUT_SUFFIX}") else() From 6a5409977bd1860cc59c46b1031242991740093b Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 21 Apr 2026 14:02:47 -0400 Subject: [PATCH 040/523] feat(ww3d2): extend IRenderBackend with ZBias/FillMode/DepthTest/DepthFunc/ColorWriteMask and migrate callers (cherry picked from commit f9e4dbdc687c1aa592a07e7a57372d3edd0311aa) --- .../Source/W3DDevice/GameClient/HeightMap.cpp | 4 +- .../W3DDevice/GameClient/TerrainTex.cpp | 38 ++-- .../W3DDevice/GameClient/W3DShaderManager.cpp | 198 +++++++++++------- .../W3DDevice/GameClient/Water/W3DWater.cpp | 19 +- .../GameClient/Water/W3DWaterTracks.cpp | 8 +- .../Source/WWVegas/WW3D2/DX8Backend.cpp | 40 ++++ .../Source/WWVegas/WW3D2/DX8Backend.h | 10 + .../Source/WWVegas/WW3D2/IRenderBackend.h | 26 +++ .../GameClient/Shadow/W3DVolumetricShadow.cpp | 12 +- .../Source/W3DDevice/GameClient/W3DScene.cpp | 56 ++--- 10 files changed, 261 insertions(+), 150 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp index 73a2f97adf2..7c814657da9 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp @@ -1964,10 +1964,10 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) devicePasses=1; //one pass solid, next in wireframe. g_renderBackend->Apply_Render_State_Changes(); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG2, D3DTA_TFACTOR ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR,0xff808080); + g_renderBackend->Set_Texture_Factor(0xff808080); doMultiPassWireFrame=TRUE; renderTerrainPass(&rinfo.Camera); - DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR,0xff008000); + g_renderBackend->Set_Texture_Factor(0xff008000); return; } } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/TerrainTex.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/TerrainTex.cpp index ae2e0b3e548..329c72a9fcd 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/TerrainTex.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/TerrainTex.cpp @@ -52,6 +52,7 @@ #include "W3DDevice/GameClient/TileData.h" #include "Common/GlobalData.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" #include "d3dx8tex.h" /****************************************************************************** @@ -464,7 +465,7 @@ void TerrainTextureClass::Apply(unsigned int stage) DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_DISABLE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, 0 ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,false); + g_renderBackend->Set_Alpha_Blend_Enable(false); } #endif @@ -535,9 +536,8 @@ void AlphaTerrainTextureClass::Apply(unsigned int stage) DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, 1 ); // Blend the result using the alpha. (came from diffuse mod texture) - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,true); - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA); + g_renderBackend->Set_Alpha_Blend_Enable(true); + g_renderBackend->Set_Blend_Factors(RB_BLEND_SRC_ALPHA, RB_BLEND_INV_SRC_ALPHA); // Disable stage 2. DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_DISABLE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); @@ -729,9 +729,8 @@ void LightMapTerrainTextureClass::Apply(unsigned int stage) if (stage==0) { - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,true); - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_DESTCOLOR); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_ZERO); + g_renderBackend->Set_Alpha_Blend_Enable(true); + g_renderBackend->Set_Blend_Factors(RB_BLEND_DEST_COLOR, RB_BLEND_ZERO); } #endif } @@ -871,9 +870,8 @@ void AlphaEdgeTextureClass::Apply(unsigned int stage) DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 ); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, 1 ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,true); - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA); + g_renderBackend->Set_Alpha_Blend_Enable(true); + g_renderBackend->Set_Blend_Factors(RB_BLEND_SRC_ALPHA, RB_BLEND_INV_SRC_ALPHA); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_DISABLE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); @@ -890,9 +888,8 @@ void AlphaEdgeTextureClass::Apply(unsigned int stage) DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAARG2, D3DTA_TEXTURE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2 ); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXCOORDINDEX, 1 ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,true); - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_ONE); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_ZERO); + g_renderBackend->Set_Alpha_Blend_Enable(true); + g_renderBackend->Set_Blend_Factors(RB_BLEND_ONE, RB_BLEND_ZERO); } #endif @@ -1008,9 +1005,8 @@ void CloudMapTerrainTextureClass::Apply(unsigned int stage) DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_DISABLE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,true); - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_DESTCOLOR); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_ZERO); + g_renderBackend->Set_Alpha_Blend_Enable(true); + g_renderBackend->Set_Blend_Factors(RB_BLEND_DEST_COLOR, RB_BLEND_ZERO); } else if (stage==1) { DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG2, D3DTA_CURRENT ); @@ -1050,9 +1046,8 @@ void CloudMapTerrainTextureClass::restore() DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXCOORDINDEX, 0 ); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,false); - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA); + g_renderBackend->Set_Alpha_Blend_Enable(false); + g_renderBackend->Set_Blend_Factors(RB_BLEND_SRC_ALPHA, RB_BLEND_INV_SRC_ALPHA); if (TheGlobalData && !TheGlobalData->m_multiPassTerrain) @@ -1131,9 +1126,8 @@ void ScorchTextureClass::Apply(unsigned int stage) DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLOROP, D3DTOP_MODULATE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 ); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, 0 ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,true); - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA); + g_renderBackend->Set_Alpha_Blend_Enable(true); + g_renderBackend->Set_Blend_Factors(RB_BLEND_SRC_ALPHA, RB_BLEND_INV_SRC_ALPHA); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_DISABLE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp index fa65137a70a..273719bb3a4 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp @@ -79,6 +79,32 @@ // Turn this on to turn off pixel shaders. jba[4/3/2003] #define do_not_DISABLE_PIXEL_SHADERS 1 +// TheSuperHackers @refactor bobtista 11/04/2026 shader-pass +// texture binding helper. W3DShaderManager historically binds textures +// for its custom shader passes by calling _Get_D3D_Device8()->SetTexture +// directly, bypassing DX8Wrapper's render-state cache and the +// g_renderBackend abstraction. The direct-bind is required because +// Apply_Render_State_Changes is not called between passes, so the +// legacy code stays. But this means the bgfx backend never sees the +// terrain / water / shroud / noise textures for these passes and +// renders white where the shader expected a sampled image. +// +// This helper keeps the immediate d3d bind (dx8 fast path) and also +// notifies g_renderBackend->Set_Texture so BgfxBackend captures the +// binding into its own sampler state. On the dx8 backend the render- +// backend call bounces through DX8Wrapper::Set_Texture which caches +// and dirties the state for the next normal draw - harmless because +// the direct bind already put the correct texture on the device. +static inline void W3DShaderManager_BindStageTexture(unsigned stage, TextureClass * tex) +{ + IDirect3DTexture8 * raw = (tex != NULL) ? tex->Peek_D3D_Texture() : NULL; + DX8Wrapper::_Get_D3D_Device8()->SetTexture(stage, raw); + if (g_renderBackend != NULL) + { + g_renderBackend->Set_Texture(stage, tex); + } +} + /** Interface definition for custom shaders we define in our app. These shaders can perform more complex operations than those allowed in the WW3D2 shader system. */ @@ -90,8 +116,8 @@ class W3DShaderInterface ///do any custom resetting necessary to bring W3D in sync. virtual void reset() { ShaderClass::Invalidate(); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, nullptr); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(1, nullptr);}; + W3DShaderManager_BindStageTexture(0, NULL); + W3DShaderManager_BindStageTexture(1, NULL);}; virtual Int init() = 0; ///Set_Texture(0,nullptr); g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC,D3DCMP_ALWAYS); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZWRITEENABLE,FALSE); + g_renderBackend->Set_Depth_Func(RB_CMP_ALWAYS); + g_renderBackend->Set_Depth_Write_Enable(false); g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices return true; @@ -419,8 +445,8 @@ Int ScreenBWFilter::set(FilterModes mode) g_renderBackend->Set_Texture(0,nullptr); g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC,D3DCMP_ALWAYS); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZWRITEENABLE,FALSE); + g_renderBackend->Set_Depth_Func(RB_CMP_ALWAYS); + g_renderBackend->Set_Depth_Write_Enable(false); g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices hr=DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(m_dwBWPixelShader); @@ -559,7 +585,7 @@ Bool ScreenBWFilterDOT3::postRender(FilterModes mode, Coord2D &scrollDelta,Bool //Draw B&W version first if (DX8Wrapper::Get_Current_Caps()->Support_Dot3()) { //Override W3D states with customizations for grayscale - DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR, 0x80A5CA8E); + g_renderBackend->Set_Texture_Factor(0x80A5CA8E); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG0, D3DTA_TFACTOR | D3DTA_ALPHAREPLICATE); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG2, D3DTA_TFACTOR | D3DTA_ALPHAREPLICATE); @@ -570,7 +596,7 @@ Bool ScreenBWFilterDOT3::postRender(FilterModes mode, Coord2D &scrollDelta,Bool } else { //doesn't have DOT3 blend mode so fake it another way. - DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR, 0x60606060); + g_renderBackend->Set_Texture_Factor(0x60606060); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG2, D3DTA_TFACTOR); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLOROP, D3DTOP_MODULATE); @@ -641,8 +667,8 @@ Int ScreenBWFilterDOT3::set(FilterModes mode) g_renderBackend->Set_Texture(0,nullptr); g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC,D3DCMP_ALWAYS); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZWRITEENABLE,FALSE); + g_renderBackend->Set_Depth_Func(RB_CMP_ALWAYS); + g_renderBackend->Set_Depth_Write_Enable(false); g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices return true; @@ -795,7 +821,7 @@ Bool ScreenCrossFadeFilter::postRender(FilterModes mode, Coord2D &scrollDelta,Bo DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,tex); //previously rendered frame inside this texture if (mode == FM_VIEW_CROSSFADE_CIRCLE) - { DX8Wrapper::_Get_D3D_Device8()->SetTexture(1,m_fadePatternTexture->Peek_D3D_Texture()); + { W3DShaderManager_BindStageTexture(1, m_fadePatternTexture); //Use the current fade level to scale the mask texture, for other modes the texture //comes pre-scaled so doesn't require uv scaling. radius = (1.0f-m_curFadeValue)*2.0f; @@ -879,8 +905,8 @@ Int ScreenCrossFadeFilter::set(FilterModes mode) DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_MIPFILTER, D3DTEXF_NONE); } - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC,D3DCMP_ALWAYS); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZWRITEENABLE,FALSE); + g_renderBackend->Set_Depth_Func(RB_CMP_ALWAYS); + g_renderBackend->Set_Depth_Write_Enable(false); return true; } @@ -984,13 +1010,11 @@ Bool ScreenMotionBlurFilter::postRender(FilterModes mode, Coord2D &scrollDelta,B if (m_additive) { - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_ONE); + g_renderBackend->Set_Blend_Factors(RB_BLEND_SRC_ALPHA, RB_BLEND_ONE); } else { - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA); + g_renderBackend->Set_Blend_Factors(RB_BLEND_SRC_ALPHA, RB_BLEND_INV_SRC_ALPHA); } - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,false); + g_renderBackend->Set_Alpha_Blend_Enable(false); //draw polygons like this is very inefficient but for only 2 triangles, it's //not worth bothering with index/vertex buffers. g_renderBackend->Apply_Render_State_Changes(); @@ -1062,7 +1086,7 @@ Bool ScreenMotionBlurFilter::postRender(FilterModes mode, Coord2D &scrollDelta,B pDev->SetTextureStageState(0,D3DTSS_ALPHAARG2, D3DTA_TEXTURE); pDev->SetTextureStageState(0,D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANS_LIT_TEX_VERTEX)); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,true); + g_renderBackend->Set_Alpha_Blend_Enable(true); g_renderBackend->Apply_Render_State_Changes(); { @@ -1152,8 +1176,8 @@ Int ScreenMotionBlurFilter::set(FilterModes mode) g_renderBackend->Set_Texture(1,nullptr); g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC,D3DCMP_ALWAYS); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZWRITEENABLE,FALSE); + g_renderBackend->Set_Depth_Func(RB_CMP_ALWAYS); + g_renderBackend->Set_Depth_Write_Enable(false); g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices } return TRUE; @@ -1224,7 +1248,7 @@ Int ShroudTextureShader::set(Int stage) DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC,D3DCMP_EQUAL); + g_renderBackend->Set_Depth_Func(RB_CMP_EQUAL); //We need to scale so shroud texel stretches over one full terrain cell. Each texel //is 1/128 the size of full texture. (assuming 128x128 vid-mem texture). @@ -1269,7 +1293,7 @@ Int ShroudTextureShader::set(Int stage) void ShroudTextureShader::reset() { g_renderBackend->Set_Texture(m_stageOfSet,nullptr); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC,D3DCMP_LESSEQUAL); + g_renderBackend->Set_Depth_Func(RB_CMP_LESS_EQUAL); DX8Wrapper::Set_DX8_Texture_Stage_State(m_stageOfSet, D3DTSS_TEXCOORDINDEX, m_stageOfSet); DX8Wrapper::Set_DX8_Texture_Stage_State(m_stageOfSet, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); } @@ -1362,7 +1386,7 @@ void FlatShroudTextureShader::reset() { if (m_stageOfSet < MAX_TEXTURE_STAGES) g_renderBackend->Set_Texture(m_stageOfSet,nullptr); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC,D3DCMP_LESSEQUAL); + g_renderBackend->Set_Depth_Func(RB_CMP_LESS_EQUAL); DX8Wrapper::Set_DX8_Texture_Stage_State(m_stageOfSet, D3DTSS_TEXCOORDINDEX, m_stageOfSet); DX8Wrapper::Set_DX8_Texture_Stage_State(m_stageOfSet, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); } @@ -1633,6 +1657,14 @@ void TerrainShader2Stage::updateNoise2(D3DXMATRIX *destMatrix,D3DXMATRIX *curVie Int TerrainShader2Stage::set(Int pass) { + static bool s_loggedTerrainShader = false; + if (!s_loggedTerrainShader) + { + s_loggedTerrainShader = true; + WWDEBUG_SAY(("[BgfxBackend] TerrainShader2Stage::set first fire pass=%d", pass)); + } + g_renderBackend->Override_Terrain_Blend(true); + //force WW3D2 system to set it's states so it won't later overwrite our custom settings. g_renderBackend->Apply_Render_State_Changes(); @@ -1658,7 +1690,7 @@ Int TerrainShader2Stage::set(Int pass) switch (pass) { case 0: - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, W3DShaderManager::getShaderTexture(0)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(0, W3DShaderManager::getShaderTexture(0)); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); @@ -1669,11 +1701,11 @@ Int TerrainShader2Stage::set(Int pass) DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_DISABLE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, 0 ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,false); + g_renderBackend->Override_Texcoord_Index(0, 0); + g_renderBackend->Override_Alpha_Blend_Enable(false); break; case 1: - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, W3DShaderManager::getShaderTexture(1)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(0, W3DShaderManager::getShaderTexture(1)); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); @@ -1682,11 +1714,10 @@ Int TerrainShader2Stage::set(Int pass) DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLOROP, D3DTOP_MODULATE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, 1 ); + g_renderBackend->Override_Texcoord_Index(0, 1); // Blend the result using the alpha. (came from diffuse mod texture) - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,true); - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA); + g_renderBackend->Override_Alpha_Blend_Enable(true); + g_renderBackend->Override_Blend(D3DBLEND_SRCALPHA, D3DBLEND_INVSRCALPHA); // Disable stage 2. DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_DISABLE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); @@ -1709,9 +1740,8 @@ Int TerrainShader2Stage::set(Int pass) DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); //blend into frame buffer - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,true); - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_DESTCOLOR); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_ZERO); + g_renderBackend->Set_Alpha_Blend_Enable(true); + g_renderBackend->Set_Blend_Factors(RB_BLEND_DEST_COLOR, RB_BLEND_ZERO); D3DXMATRIX inv; float det; @@ -1720,7 +1750,7 @@ Int TerrainShader2Stage::set(Int pass) if (W3DShaderManager::getCurrentShader() == W3DShaderManager::ST_TERRAIN_BASE_NOISE12) { //setup cloud pass - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, W3DShaderManager::getShaderTexture(2)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(0, W3DShaderManager::getShaderTexture(2)); updateNoise1(&curView,&inv); //update curView with texture matrix DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE0, curView); @@ -1729,7 +1759,7 @@ Int TerrainShader2Stage::set(Int pass) DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); //setup noise pass - DX8Wrapper::_Get_D3D_Device8()->SetTexture(1, W3DShaderManager::getShaderTexture(3)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(1, W3DShaderManager::getShaderTexture(3)); updateNoise2(&curView,&inv); DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE1, curView); @@ -1753,7 +1783,7 @@ Int TerrainShader2Stage::set(Int pass) // Now setup the texture pipeline. if (W3DShaderManager::getCurrentShader() == W3DShaderManager::ST_TERRAIN_BASE_NOISE1) { //setup cloud pass - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, W3DShaderManager::getShaderTexture(2)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(0, W3DShaderManager::getShaderTexture(2)); updateNoise1(&curView,&inv); //update curView with texture matrix DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MINFILTER, D3DTEXF_LINEAR); DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); @@ -1761,7 +1791,7 @@ Int TerrainShader2Stage::set(Int pass) else { //setup noise pass - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, W3DShaderManager::getShaderTexture(3)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(0, W3DShaderManager::getShaderTexture(3)); updateNoise2(&curView,&inv); //update curView with texture matrix DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MINFILTER, D3DTEXF_POINT); DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); @@ -1802,6 +1832,14 @@ Int TerrainShader8Stage::set(Int pass) { if (pass == 0) { + static bool s_logged8Stage = false; + if (!s_logged8Stage) + { + s_logged8Stage = true; + WWDEBUG_SAY(("[BgfxBackend] TerrainShader8Stage::set first fire")); + } + g_renderBackend->Override_Terrain_Blend(true); + //force WW3D2 system to set it's states so it won't later overwrite our custom settings. g_renderBackend->Apply_Render_State_Changes(); @@ -1829,8 +1867,8 @@ Int TerrainShader8Stage::set(Int pass) DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); } - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, W3DShaderManager::getShaderTexture(0)->Peek_D3D_Texture()); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(1, W3DShaderManager::getShaderTexture(1)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(0, W3DShaderManager::getShaderTexture(0)); + W3DShaderManager_BindStageTexture(1, W3DShaderManager::getShaderTexture(1)); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLOROP, D3DTOP_MODULATE); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, 0); @@ -2001,12 +2039,14 @@ Int TerrainShaderPixelShader::init() Int TerrainShaderPixelShader::set(Int pass) { + g_renderBackend->Override_Terrain_Blend(true); + //force WW3D2 system to set it's states so it won't later overwrite our custom settings. g_renderBackend->Apply_Render_State_Changes(); //setup base pass - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, W3DShaderManager::getShaderTexture(0)->Peek_D3D_Texture()); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(1, W3DShaderManager::getShaderTexture(1)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(0, W3DShaderManager::getShaderTexture(0)); + W3DShaderManager_BindStageTexture(1, W3DShaderManager::getShaderTexture(1)); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); @@ -2056,8 +2096,8 @@ Int TerrainShaderPixelShader::set(Int pass) { //full shader DX8Wrapper::Set_DX8_Texture_Stage_State(3, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); DX8Wrapper::Set_DX8_Texture_Stage_State(3, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(2, W3DShaderManager::getShaderTexture(2)->Peek_D3D_Texture()); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(3, W3DShaderManager::getShaderTexture(3)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(2, W3DShaderManager::getShaderTexture(2)); + W3DShaderManager_BindStageTexture(3, W3DShaderManager::getShaderTexture(3)); DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(m_dwBaseNoise2PixelShader); DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_MINFILTER, D3DTEXF_LINEAR); @@ -2082,14 +2122,14 @@ Int TerrainShaderPixelShader::set(Int pass) if (W3DShaderManager::getCurrentShader() == W3DShaderManager::ST_TERRAIN_BASE_NOISE1) { //cloud map - DX8Wrapper::_Get_D3D_Device8()->SetTexture(2, W3DShaderManager::getShaderTexture(2)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(2, W3DShaderManager::getShaderTexture(2)); terrainShader2Stage.updateNoise1(&curView,&inv); //update curView with texture matrix DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_MINFILTER, D3DTEXF_LINEAR); DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); } else { //light map - DX8Wrapper::_Get_D3D_Device8()->SetTexture(2, W3DShaderManager::getShaderTexture(3)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(2, W3DShaderManager::getShaderTexture(3)); terrainShader2Stage.updateNoise2(&curView,&inv); //update curView with texture matrix DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_MINFILTER, D3DTEXF_POINT); DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); @@ -2184,7 +2224,7 @@ Int CloudTextureShader::set(Int stage) DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_ALPHAARG2, D3DTA_CURRENT ); DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(stage, W3DShaderManager::getShaderTexture(stage)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(stage, W3DShaderManager::getShaderTexture(stage)); m_stageOfSet=stage; return TRUE; @@ -2193,7 +2233,7 @@ Int CloudTextureShader::set(Int stage) void CloudTextureShader::reset() { //Free reference to texture - DX8Wrapper::_Get_D3D_Device8()->SetTexture(m_stageOfSet, nullptr); + W3DShaderManager_BindStageTexture(m_stageOfSet, NULL); //Turn off texture projection DX8Wrapper::Set_DX8_Texture_Stage_State( m_stageOfSet, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); DX8Wrapper::Set_DX8_Texture_Stage_State( m_stageOfSet, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|m_stageOfSet); @@ -2285,13 +2325,13 @@ Int RoadShaderPixelShader::set(Int pass) //tell pixel shader which UV set to use for each stage DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, 0 ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC,D3DCMP_LESSEQUAL); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZWRITEENABLE,FALSE); - DX8Wrapper::Set_DX8_Render_State(D3DRS_LIGHTING, FALSE); + g_renderBackend->Set_Depth_Func(RB_CMP_LESS_EQUAL); + g_renderBackend->Set_Depth_Write_Enable(false); + g_renderBackend->Set_Lighting_Enable(false); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,true); //blend roads into terrain - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA); + g_renderBackend->Set_Alpha_Blend_Enable(true); //blend roads into terrain + g_renderBackend->Set_Blend_Factors(RB_BLEND_SRC_ALPHA, RB_BLEND_INV_SRC_ALPHA); + g_renderBackend->Override_Alpha_Blend_Enable(true); D3DXMATRIX curView; DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); @@ -2386,9 +2426,9 @@ Int RoadShader2Stage::set(Int pass) //Force system to apply world/view transforms. g_renderBackend->Apply_Render_State_Changes(); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC,D3DCMP_LESSEQUAL); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZWRITEENABLE,FALSE); - DX8Wrapper::Set_DX8_Render_State(D3DRS_LIGHTING, FALSE); + g_renderBackend->Set_Depth_Func(RB_CMP_LESS_EQUAL); + g_renderBackend->Set_Depth_Write_Enable(false); + g_renderBackend->Set_Lighting_Enable(false); // Modulate the diffuse color with the texture as lighting comes from diffuse. DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); @@ -2399,12 +2439,12 @@ Int RoadShader2Stage::set(Int pass) DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, 0 ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,true); //blend roads into terrain + g_renderBackend->Set_Alpha_Blend_Enable(true); //blend roads into terrain + g_renderBackend->Override_Alpha_Blend_Enable(true); if (pass == 0) { - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA); + g_renderBackend->Set_Blend_Factors(RB_BLEND_SRC_ALPHA, RB_BLEND_INV_SRC_ALPHA); if (W3DShaderManager::getCurrentShader() >= W3DShaderManager::ST_ROAD_BASE_NOISE1) { //second texture unit will contain a noise pass @@ -2513,8 +2553,7 @@ Int RoadShader2Stage::set(Int pass) //Modulate into existing roads with clouds applied. - only apply where roads are transparent by //using road texture as a mask. - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_ZERO); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_SRCCOLOR); + g_renderBackend->Set_Blend_Factors(RB_BLEND_ZERO, RB_BLEND_SRC_COLOR); DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE0, curView); } @@ -2857,7 +2896,7 @@ void W3DShaderManager::startRenderToTexture() if (m_currentFilter == FT_VIEW_MOTION_BLUR_FILTER || m_currentFilter == FT_VIEW_CROSSFADE) { //these filters rely on the previous frame being visible so we must be careful about clearing //frame buffer. Only clear the alpha channel - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_ALPHA); //only clear alpha + g_renderBackend->Set_Color_Write_Enable(false, false, false, true); //only clear alpha ShaderClass shader=ShaderClass::_PresetOpaqueSolidShader; shader.Set_Depth_Compare(ShaderClass::PASS_ALWAYS); shader.Set_Depth_Mask(ShaderClass::DEPTH_WRITE_DISABLE); @@ -2868,7 +2907,7 @@ void W3DShaderManager::startRenderToTexture() REF_PTR_RELEASE(vmat); //no need to keep a reference since it's a preset. drawViewport(0x00ffffff | (((Int)(TheWaterTransparency->m_minWaterOpacity*255.0f)) <<24)); - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_RED|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_BLUE); //disable writes to alpha + g_renderBackend->Set_Color_Write_Enable(true, true, true, false); //disable writes to alpha } else //normal clear that overwrites everything. g_renderBackend->Clear(true, false, Vector3( 0.0f, 0.0f, 0.0f ), TheWaterTransparency->m_minWaterOpacity); @@ -3287,6 +3326,9 @@ void FlatTerrainShader2Stage::reset() Int FlatTerrainShader2Stage::set(Int pass) { + static bool s_loggedFlat2 = false; + if (!s_loggedFlat2) { s_loggedFlat2 = true; WWDEBUG_SAY(("[BgfxBackend] FlatTerrainShader2Stage::set first fire pass=%d", pass)); } + g_renderBackend->Override_Terrain_Blend(true); //force WW3D2 system to set it's states so it won't later overwrite our custom settings. g_renderBackend->Apply_Render_State_Changes(); @@ -3320,7 +3362,7 @@ Int FlatTerrainShader2Stage::set(Int pass) DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); if (W3DShaderManager::getShaderTexture(0)) { - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, W3DShaderManager::getShaderTexture(0)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(0, W3DShaderManager::getShaderTexture(0)); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG2, D3DTA_CURRENT ); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLOROP, D3DTOP_MODULATE ); @@ -3382,7 +3424,7 @@ Int FlatTerrainShader2Stage::set(Int pass) DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXCOORDINDEX, 0 ); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|0); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,false); + g_renderBackend->Set_Alpha_Blend_Enable(false); break; case 1: // Noise/cloud pass @@ -3402,9 +3444,8 @@ Int FlatTerrainShader2Stage::set(Int pass) DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); //blend into frame buffer - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,true); - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_DESTCOLOR); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_ZERO); + g_renderBackend->Set_Alpha_Blend_Enable(true); + g_renderBackend->Set_Blend_Factors(RB_BLEND_DEST_COLOR, RB_BLEND_ZERO); D3DXMATRIX inv; float det; @@ -3419,7 +3460,7 @@ Int FlatTerrainShader2Stage::set(Int pass) //clouds always need bilinear filtering DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MINFILTER, D3DTEXF_LINEAR); DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, W3DShaderManager::getShaderTexture(2)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(0, W3DShaderManager::getShaderTexture(2)); //setup noise pass @@ -3439,14 +3480,14 @@ Int FlatTerrainShader2Stage::set(Int pass) DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(1, W3DShaderManager::getShaderTexture(3)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(1, W3DShaderManager::getShaderTexture(3)); } else { //only 1 noise or cloud texture // Now setup the texture pipeline. if (W3DShaderManager::getCurrentShader() == W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE1) { //setup cloud pass - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, W3DShaderManager::getShaderTexture(2)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(0, W3DShaderManager::getShaderTexture(2)); terrainShader2Stage.updateNoise1(&curView,&inv); //update curView with texture matrix DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MINFILTER, D3DTEXF_LINEAR); DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); @@ -3454,7 +3495,7 @@ Int FlatTerrainShader2Stage::set(Int pass) else { //setup noise pass - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, W3DShaderManager::getShaderTexture(3)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(0, W3DShaderManager::getShaderTexture(3)); terrainShader2Stage.updateNoise2(&curView,&inv); //update curView with texture matrix DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MINFILTER, D3DTEXF_POINT); DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); @@ -3558,6 +3599,7 @@ Int FlatTerrainShaderPixelShader::init() Int FlatTerrainShaderPixelShader::set(Int pass) { + g_renderBackend->Override_Terrain_Blend(true); //setup base pass Int curStage = 1; // setup terrain [3/31/2003] @@ -3637,7 +3679,7 @@ Int FlatTerrainShaderPixelShader::set(Int pass) DX8Wrapper::Set_DX8_Texture_Stage_State( curStage, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); DX8Wrapper::Set_DX8_Texture_Stage_State( curStage, D3DTSS_MINFILTER, D3DTEXF_LINEAR); DX8Wrapper::Set_DX8_Texture_Stage_State( curStage, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(curStage, shroud->getShroudTexture()->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(curStage, shroud->getShroudTexture()); curStage++; if (curStage==1) curStage++; } @@ -3658,7 +3700,7 @@ Int FlatTerrainShaderPixelShader::set(Int pass) DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(curStage, W3DShaderManager::getShaderTexture(2)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(curStage, W3DShaderManager::getShaderTexture(2)); terrainShader2Stage.updateNoise1(&curView,&inv); //update curView with texture matrix DX8Wrapper::_Set_DX8_Transform((D3DTRANSFORMSTATETYPE )(D3DTS_TEXTURE0+curStage), curView); DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_MINFILTER, D3DTEXF_LINEAR); @@ -3685,7 +3727,7 @@ Int FlatTerrainShaderPixelShader::set(Int pass) DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(curStage, W3DShaderManager::getShaderTexture(3)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(curStage, W3DShaderManager::getShaderTexture(3)); terrainShader2Stage.updateNoise2(&curView,&inv); //update curView with texture matrix DX8Wrapper::_Set_DX8_Transform((D3DTRANSFORMSTATETYPE )(D3DTS_TEXTURE0+curStage), curView); DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_MINFILTER, D3DTEXF_LINEAR); @@ -3705,7 +3747,7 @@ Int FlatTerrainShaderPixelShader::set(Int pass) } DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_ALPHABLENDENABLE, false); g_renderBackend->Apply_Render_State_Changes(); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(curStage, W3DShaderManager::getShaderTexture(3)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(curStage, W3DShaderManager::getShaderTexture(3)); return TRUE; } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp index 1367aa9f55b..382afb94909 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp @@ -2912,7 +2912,7 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) //In additive blending we need to use the alpha at the edges of river to darken //rgb instead. if (TheWaterTransparency->m_additiveBlend) - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); + g_renderBackend->Set_Blend_Factors(RB_BLEND_SRC_ALPHA, RB_BLEND_ONE); if (m_riverWaterPixelShader) DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(m_riverWaterPixelShader); DWORD cull; @@ -2933,7 +2933,7 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) //restore blend mode to what W3D expects. if (TheWaterTransparency->m_additiveBlend) - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_ONE ); + g_renderBackend->Set_Blend_Factors(RB_BLEND_ONE, RB_BLEND_ONE); DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_CULLMODE, cull); @@ -3272,9 +3272,11 @@ void WaterRenderObjClass::drawTrapezoidWater(Vector3 points[4]) //If video card supports it and it's enabled, feather the water edge using destination alpha if (DX8Wrapper::getBackBufferFormat() == WW3D_FORMAT_A8R8G8B8 && TheGlobalData->m_showSoftWaterEdge && TheWaterTransparency->m_transparentWaterDepth !=0) - { DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_DESTALPHA ); - if (!TheWaterTransparency->m_additiveBlend) - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, D3DBLEND_INVDESTALPHA ); + { + if (TheWaterTransparency->m_additiveBlend) + g_renderBackend->Set_Blend_Factors(RB_BLEND_DEST_ALPHA, RB_BLEND_ONE); + else + g_renderBackend->Set_Blend_Factors(RB_BLEND_DEST_ALPHA, RB_BLEND_INV_DEST_ALPHA); } @@ -3319,13 +3321,12 @@ void WaterRenderObjClass::drawTrapezoidWater(Vector3 points[4]) if (m_riverWaterPixelShader) DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(0); //Restore alpha blend to default values since we may have changed them to feather edges. if (!TheWaterTransparency->m_additiveBlend) - { DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); + { + g_renderBackend->Set_Blend_Factors(RB_BLEND_SRC_ALPHA, RB_BLEND_INV_SRC_ALPHA); } else { - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_ONE ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, D3DBLEND_ONE ); + g_renderBackend->Set_Blend_Factors(RB_BLEND_ONE, RB_BLEND_ONE); } if (TheTerrainRenderObject->getShroud()) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWaterTracks.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWaterTracks.cpp index 3004ad2da98..e2cdd9cec89 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWaterTracks.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWaterTracks.cpp @@ -894,7 +894,7 @@ Try improving the fit to vertical surfaces like cliffs. g_renderBackend->Set_Shader(m_shaderClass); g_renderBackend->Set_Vertex_Buffer(m_vertexBuffer); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZBIAS,8); + g_renderBackend->Set_Z_Bias(8); //Force apply of render states so we can override them. g_renderBackend->Apply_Render_State_Changes(); @@ -911,7 +911,7 @@ Try improving the fit to vertical surfaces like cliffs. //Shroud shader uses z-compare of EQUAL which wouldn't work on water because it doesn't //write to the zbuffer. Change to LESSEQUAL. - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC, D3DCMP_LESSEQUAL); + g_renderBackend->Set_Depth_Func(RB_CMP_LESS_EQUAL); } Int LastTextureType=-1; @@ -930,11 +930,11 @@ Try improving the fit to vertical surfaces like cliffs. mod = mod->m_nextSystem; } - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZBIAS,0); + g_renderBackend->Set_Z_Bias(0); if (TheTerrainRenderObject->getShroud()) { //we used the shroud shader, so reset it. - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC, D3DCMP_EQUAL); + g_renderBackend->Set_Depth_Func(RB_CMP_EQUAL); W3DShaderManager::resetShader(W3DShaderManager::ST_SHROUD_TEXTURE); } } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp index 3b6086d5bc2..63c6e2d4fd0 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp @@ -273,6 +273,46 @@ void DX8Backend::Set_Stencil_ZFail_Op(StencilOp op) DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILZFAIL, static_cast(op)); } +void DX8Backend::Set_Z_Bias(int bias) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_ZBIAS, static_cast(bias)); +} + +void DX8Backend::Set_Fill_Mode(FillMode mode) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_FILLMODE, static_cast(mode)); +} + +void DX8Backend::Set_Depth_Test_Enable(bool enable) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_ZENABLE, enable ? TRUE : FALSE); +} + +void DX8Backend::Set_Depth_Write_Enable(bool enable) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_ZWRITEENABLE, enable ? TRUE : FALSE); +} + +void DX8Backend::Set_Depth_Func(CompareFunc func) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC, static_cast(func)); +} + +void DX8Backend::Set_Color_Write_Mask(unsigned mask) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE, mask); +} + +void DX8Backend::Set_Lighting_Enable(bool enable) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_LIGHTING, enable ? TRUE : FALSE); +} + +void DX8Backend::Set_Texture_Factor(unsigned argb) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR, argb); +} + // -- Transforms -------------------------------------------------------------- void DX8Backend::Set_Transform(TransformKind transform, const Matrix4x4 & m) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h index b1f04a1a1b5..f79f1dd8b52 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h @@ -84,6 +84,16 @@ class DX8Backend : public IRenderBackend virtual void Set_Stencil_Fail_Op(StencilOp op); virtual void Set_Stencil_ZFail_Op(StencilOp op); + // render-state extension (see IRenderBackend.h). + virtual void Set_Z_Bias(int bias); + virtual void Set_Fill_Mode(FillMode mode); + virtual void Set_Depth_Test_Enable(bool enable); + virtual void Set_Depth_Write_Enable(bool enable); + virtual void Set_Depth_Func(CompareFunc func); + virtual void Set_Color_Write_Mask(unsigned mask); + virtual void Set_Lighting_Enable(bool enable); + virtual void Set_Texture_Factor(unsigned argb); + // -- Transforms ----------------------------------------------------------- virtual void Set_Transform(TransformKind transform, const Matrix4x4 & m); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h index be8c9577cdc..57876053b57 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h @@ -117,6 +117,15 @@ enum CompareFunc RB_CMP_ALWAYS = 8 }; +// TheSuperHackers @refactor bobtista 14/04/2026 D3DFILLMODE +// values match D3DFILL_* so DX8Backend can cast directly. +enum FillMode +{ + RB_FILL_POINT = 1, // D3DFILL_POINT + RB_FILL_WIREFRAME = 2, // D3DFILL_WIREFRAME + RB_FILL_SOLID = 3 // D3DFILL_SOLID +}; + enum StencilOp { // Values match D3DSTENCILOP_* 1..8 directly so DX8Backend can cast. @@ -240,6 +249,23 @@ class IRenderBackend virtual void Set_Stencil_Fail_Op(StencilOp op) = 0; virtual void Set_Stencil_ZFail_Op(StencilOp op) = 0; + // TheSuperHackers @refactor bobtista 14/04/2026 + // render-state remainders. These wrap the last D3DRS_* values still + // being set directly by the terrain / scene / water / snow code + // (ZBIAS, FILLMODE, ZENABLE/ZFUNC, COLORWRITEENABLE as DWORD mask). + // The DWORD variant of Set_Color_Write_Mask coexists with the + // boolean Set_Color_Write_Enable — callers that receive a saved + // bitmask from GetRenderState use this, callers that know the four + // channel flags use the boolean form. + virtual void Set_Z_Bias(int bias) = 0; + virtual void Set_Fill_Mode(FillMode mode) = 0; + virtual void Set_Depth_Test_Enable(bool enable) = 0; + virtual void Set_Depth_Write_Enable(bool enable) = 0; + virtual void Set_Depth_Func(CompareFunc func) = 0; + virtual void Set_Color_Write_Mask(unsigned mask) = 0; + virtual void Set_Lighting_Enable(bool enable) = 0; + virtual void Set_Texture_Factor(unsigned argb) = 0; + // ------------------------------------------------------------------------- // Transforms // ------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp index 4ddbb7d92bf..c627eefcebb 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp @@ -3483,7 +3483,7 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) //disable writes to color buffer if (DX8Wrapper::Get_Current_Caps()->Get_DX8_Caps().PrimitiveMiscCaps & D3DPMISCCAPS_COLORWRITEENABLE) { DX8Wrapper::_Get_D3D_Device8()->GetRenderState(D3DRS_COLORWRITEENABLE, &oldColorWriteEnable); - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,0); + g_renderBackend->Set_Color_Write_Mask(0); } else { //device does not support disabling writes to color buffer so fake it through alpha blending @@ -3600,13 +3600,11 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) //m_pDev->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID); - // Note: oldColorWriteEnable is a captured DWORD bitmask read - // directly via m_pDev->GetRenderState earlier in this function. The - // restore can't go through Set_Color_Write_Enable(r,g,b,a) without - // re-decoding the bitmask, so it stays on Set_DX8_Render_State. The - // whole save/restore pair migrates together in a future phase. + // Restore the captured DWORD mask via the new DWORD + // variant Set_Color_Write_Mask (the boolean Set_Color_Write_Enable + // would need to re-decode the bitmask). if (oldColorWriteEnable != 0x12345678) - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,oldColorWriteEnable); + g_renderBackend->Set_Color_Write_Mask(oldColorWriteEnable); // // render the big transparent square of shadows in the stencil buffer diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp index d21d59e4fa6..39c085d6831 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp @@ -993,8 +993,8 @@ void RTS3DScene::Render(RenderInfoClass & rinfo) //a projected alpha texture which will later be used to determine where //wireframe should be visible. ///@todo: Clearing to black may not be needed if the scene already did the clear. - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_ALPHA); - DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 0); + g_renderBackend->Set_Color_Write_Mask(D3DCOLORWRITEENABLE_ALPHA); + g_renderBackend->Set_Z_Bias(0); //Since all objects will be rendered with same material, disable resetting until all are done. m_maskMaterialPass->setAllowUninstall(FALSE); @@ -1003,7 +1003,7 @@ void RTS3DScene::Render(RenderInfoClass & rinfo) m_maskMaterialPass->setAllowUninstall(TRUE); m_maskMaterialPass->UnInstall_Materials(); - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_BLUE|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_RED); + g_renderBackend->Set_Color_Write_Mask(D3DCOLORWRITEENABLE_BLUE|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_RED); ShaderClass::Invalidate(); } @@ -1018,8 +1018,8 @@ void RTS3DScene::Render(RenderInfoClass & rinfo) //wireframe should be visible. ///@todo: Clearing to black may not be needed if the scene already did the clear. g_renderBackend->Clear(true, false, Vector3(0.0f,0.0f,0.0f),1.0f); // Clear color but not z - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_ALPHA); - DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 0); + g_renderBackend->Set_Color_Write_Mask(D3DCOLORWRITEENABLE_ALPHA); + g_renderBackend->Set_Z_Bias(0); //We're only filling the z-buffer so ignore normal textures and state changes to speed things up. m_customPassMode = SCENE_PASS_ALPHA_MASK; @@ -1031,10 +1031,10 @@ void RTS3DScene::Render(RenderInfoClass & rinfo) m_maskMaterialPass->setAllowUninstall(TRUE); m_maskMaterialPass->UnInstall_Materials(); - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_BLUE|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_RED); + g_renderBackend->Set_Color_Write_Mask(D3DCOLORWRITEENABLE_BLUE|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_RED); WW3D::Enable_Coloring(0xff008000); WW3D::Enable_Texturing(false); - DX8Wrapper::Set_DX8_Render_State(D3DRS_FILLMODE,D3DFILL_WIREFRAME); + g_renderBackend->Set_Fill_Mode(RB_FILL_WIREFRAME); //Move maximum z-buffer value in a little to shift all z-values closer //and thus forcing line to appear on top of previous pass. @@ -1046,7 +1046,7 @@ void RTS3DScene::Render(RenderInfoClass & rinfo) // DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 4); Customized_Render(rinfo); //render wireframe where z-test passes Flush(rinfo); - DX8Wrapper::Set_DX8_Render_State(D3DRS_FILLMODE,D3DFILL_SOLID); + g_renderBackend->Set_Fill_Mode(RB_FILL_SOLID); rinfo.Camera.Set_Zbuffer_Range(nearZ, farZ); rinfo.Camera.Apply(); @@ -1062,32 +1062,32 @@ void RTS3DScene::Render(RenderInfoClass & rinfo) //old W3D custom rendering code. //Disable writes to color buffer to save memory bandwidth - we only need Z. - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,0); - DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 0); + g_renderBackend->Set_Color_Write_Mask(0); + g_renderBackend->Set_Z_Bias(0); Customized_Render(rinfo); Flush(rinfo); //Re-enable writes to color buffer. - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_BLUE|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_RED); + g_renderBackend->Set_Color_Write_Mask(D3DCOLORWRITEENABLE_BLUE|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_RED); switch (Get_Extra_Pass_Polygon_Mode()) { case EXTRA_PASS_LINE: WW3D::Enable_Texturing(false); - DX8Wrapper::Set_DX8_Render_State(D3DRS_FILLMODE,D3DFILL_WIREFRAME); - DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 7); + g_renderBackend->Set_Fill_Mode(RB_FILL_WIREFRAME); + g_renderBackend->Set_Z_Bias(7); Customized_Render(rinfo); break; case EXTRA_PASS_CLEAR_LINE: g_renderBackend->Clear(true, false, Vector3(0.0f,0.0f,0.0f), 0.0f); // Clear color but not z WW3D::Enable_Texturing(false); WW3D::Enable_Coloring(0xff008000); - DX8Wrapper::Set_DX8_Render_State(D3DRS_FILLMODE,D3DFILL_WIREFRAME); - DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 7); + g_renderBackend->Set_Fill_Mode(RB_FILL_WIREFRAME); + g_renderBackend->Set_Z_Bias(7); Customized_Render(rinfo); break; } Flush(rinfo); - DX8Wrapper::Set_DX8_Render_State(D3DRS_FILLMODE,D3DFILL_SOLID); - DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 0); + g_renderBackend->Set_Fill_Mode(RB_FILL_SOLID); + g_renderBackend->Set_Z_Bias(0); WW3D::Enable_Texturing(old_enable); WW3D::Enable_Coloring(0); ShaderClass::Invalidate(); @@ -1289,7 +1289,7 @@ void renderStenciledPlayerColor( UnsignedInt color, UnsignedInt stencilRef, Bool // Set stencil states g_renderBackend->Set_Stencil_Enable(true); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZENABLE, TRUE ); + g_renderBackend->Set_Depth_Test_Enable(true); DWORD oldColorWriteEnable=0x12345678; if (clear) { @@ -1302,13 +1302,13 @@ void renderStenciledPlayerColor( UnsignedInt color, UnsignedInt stencilRef, Bool g_renderBackend->Set_Stencil_ZFail_Op(RB_STENCIL_OP_REPLACE); g_renderBackend->Set_Stencil_Pass_Op(RB_STENCIL_OP_REPLACE); //pixels which had occluded player colors, get MSB set. g_renderBackend->Set_Stencil_Fail_Op(RB_STENCIL_OP_ZERO); //pixels which had no occluded player colors are cleared. - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC, D3DCMP_NEVER ); //fail all access to the frame buffer to improve memory bandwidth + g_renderBackend->Set_Depth_Func(RB_CMP_NEVER); //fail all access to the frame buffer to improve memory bandwidth //disable writes to color buffer if (DX8Wrapper::Get_Current_Caps()->Get_DX8_Caps().PrimitiveMiscCaps & D3DPMISCCAPS_COLORWRITEENABLE) { DX8Wrapper::_Get_D3D_Device8()->GetRenderState(D3DRS_COLORWRITEENABLE, &oldColorWriteEnable); - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,0); + g_renderBackend->Set_Color_Write_Mask(0); } else { @@ -1339,10 +1339,10 @@ void renderStenciledPlayerColor( UnsignedInt color, UnsignedInt stencilRef, Bool g_renderBackend->Set_Stencil_Enable(false); g_renderBackend->Set_Alpha_Blend_Enable(false); //restore shader state g_renderBackend->Set_Blend_Factors(RB_BLEND_ONE, RB_BLEND_ZERO); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC, D3DCMP_ALWAYS); + g_renderBackend->Set_Depth_Func(RB_CMP_ALWAYS); if (oldColorWriteEnable != 0x12345678) - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,oldColorWriteEnable); + g_renderBackend->Set_Color_Write_Mask(oldColorWriteEnable); } @@ -1399,7 +1399,7 @@ void RTS3DScene::flushOccludedObjectsIntoStencil(RenderInfoClass & rinfo) } g_renderBackend->Set_Stencil_Enable(true); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZENABLE, TRUE ); + g_renderBackend->Set_Depth_Test_Enable(true); g_renderBackend->Set_Stencil_Mask(0xffffffff); g_renderBackend->Set_Stencil_Write_Mask(0xffffffff); //Always store player index into stencil unless it is occluded by another @@ -1483,7 +1483,7 @@ void RTS3DScene::flushOccludedObjectsIntoStencil(RenderInfoClass & rinfo) //Stencil buffer is now filled with color indices of potentially occluded objects. We now draw //occluder objects so they cover up and modify stencil MSB wherever they are in front of other objects. g_renderBackend->Set_Stencil_Enable(true); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZENABLE, TRUE ); + g_renderBackend->Set_Depth_Test_Enable(true); g_renderBackend->Set_Stencil_Ref(0xffffffff); g_renderBackend->Set_Stencil_Mask(0xffffffff); //isolate lowest player color g_renderBackend->Set_Stencil_Write_Mask(0x80); //only write to MSB @@ -1566,7 +1566,7 @@ void RTS3DScene::flushOccludedObjectsIntoStencil(RenderInfoClass & rinfo) //Reset scene ambient because we sometimes mess around with it to make objects //glow, etc. when processing drawables. This is a good place to do it because this //function gets called right after we flush regular render objects. - DX8Wrapper::Set_DX8_Render_State(D3DRS_AMBIENT,DX8Wrapper::Convert_Color(this->Get_Ambient_Light(),0.0f)); + g_renderBackend->Set_Ambient(this->Get_Ambient_Light()); } /*Version which does not require stencil buffer*/ @@ -1585,7 +1585,7 @@ void RTS3DScene::flushOccludedObjects(RenderInfoClass & rinfo) { //Set all stencil pixels of potentially occluded objects to 128. g_renderBackend->Set_Stencil_Enable(true); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZENABLE, TRUE ); + g_renderBackend->Set_Depth_Test_Enable(true); g_renderBackend->Set_Stencil_Ref(128); g_renderBackend->Set_Stencil_Mask(0xffffffff); g_renderBackend->Set_Stencil_Write_Mask(0xffffffff); @@ -1639,7 +1639,7 @@ void RTS3DScene::flushOccludedObjects(RenderInfoClass & rinfo) //Reset scene ambient because we sometimes mess around with it to make objects //glow, etc. when processing drawables. This is a good place to do it because this //function gets called right after we flush regular render objects. - DX8Wrapper::Set_DX8_Render_State(D3DRS_AMBIENT,DX8Wrapper::Convert_Color(this->Get_Ambient_Light(),0.0f)); + g_renderBackend->Set_Ambient(this->Get_Ambient_Light()); } void RTS3DScene::flushTranslucentObjects(RenderInfoClass & rinfo) @@ -1671,7 +1671,7 @@ void RTS3DScene::flushTranslucentObjects(RenderInfoClass & rinfo) //Reset scene ambient because we sometimes mess around with it to make objects //glow, etc. when processing drawables. This is a good place to do it because this //function gets called right after we flush regular render objects. - DX8Wrapper::Set_DX8_Render_State(D3DRS_AMBIENT,DX8Wrapper::Convert_Color(this->Get_Ambient_Light(),0.0f)); + g_renderBackend->Set_Ambient(this->Get_Ambient_Light()); } //============================================================================= From b0084567cf1b20abb7a86c1de6396132512553d0 Mon Sep 17 00:00:00 2001 From: bobtista Date: Mon, 1 Jun 2026 09:55:14 +1000 Subject: [PATCH 041/523] fix(ww3d): route shroud upload through IRenderBackend::Upload_Texture_Region (POOL_DEFAULT can't be locked) --- .../Source/WWVegas/WW3D2/BgfxBackend.cpp | 13 ++++ .../Source/WWVegas/WW3D2/BgfxBackend.h | 8 ++ .../Source/WWVegas/WW3D2/DX8Backend.cpp | 73 +++++++++++++++++++ .../Source/WWVegas/WW3D2/DX8Backend.h | 8 ++ .../Source/WWVegas/WW3D2/IRenderBackend.h | 15 ++++ .../Source/W3DDevice/GameClient/W3DShroud.cpp | 32 ++++++-- 6 files changed, 141 insertions(+), 8 deletions(-) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp index 5ed88947926..b7763258be1 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -157,6 +157,19 @@ void BgfxBackend::Set_Texture(unsigned int /*stage*/, TextureBaseClass * /*textu { } +void BgfxBackend::Upload_Texture_Region( + TextureClass * /*dst_texture*/, + unsigned int /*dst_level*/, + unsigned int /*dst_x*/, unsigned int /*dst_y*/, + const void * /*src_data*/, + unsigned int /*src_pitch*/, + unsigned int /*region_width*/, unsigned int /*region_height*/, + WW3DFormat /*format*/) +{ + // Stub on render-backend-interface. The bgfx texture mirror lands in + // the bgfx-features topic; until then bgfx is not the active backend. +} + void BgfxBackend::Apply_Render_State_Changes() { } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h index 74fae465b0c..0904691d6a6 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h @@ -69,6 +69,14 @@ class BgfxBackend : public IRenderBackend virtual void Get_Shader(ShaderClass & shader); virtual void Set_Material(const VertexMaterialClass * material); virtual void Set_Texture(unsigned int stage, TextureBaseClass * texture); + virtual void Upload_Texture_Region( + TextureClass * dst_texture, + unsigned int dst_level, + unsigned int dst_x, unsigned int dst_y, + const void * src_data, + unsigned int src_pitch, + unsigned int region_width, unsigned int region_height, + WW3DFormat format); virtual void Apply_Render_State_Changes(); virtual void Apply_Default_State(); virtual void Invalidate_Cached_Render_States(); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp index 63c6e2d4fd0..f6e55ceb693 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp @@ -24,6 +24,8 @@ #include "DX8Backend.h" #include "dx8wrapper.h" +#include "formconv.h" +#include "texture.h" #include "vector3.h" #include "matrix4.h" #include "matrix3d.h" @@ -150,6 +152,77 @@ void DX8Backend::Set_Texture(unsigned int stage, TextureBaseClass * texture) DX8Wrapper::Set_Texture(stage, texture); } +void DX8Backend::Upload_Texture_Region( + TextureClass * dst_texture, + unsigned int dst_level, + unsigned int dst_x, unsigned int dst_y, + const void * src_data, + unsigned int src_pitch, + unsigned int region_width, unsigned int region_height, + WW3DFormat format) +{ + // TheSuperHackers @feature bobtista 01/06/2026 POOL_SYSTEMMEM staging + + // IDirect3DDevice8::CopyRects is the only valid transport for writing + // POOL_DEFAULT texture levels (which cannot be locked). Callers like + // W3DShroud's destination texture rely on this path. + if (dst_texture == nullptr || + src_data == nullptr || + region_width == 0 || + region_height == 0 || + format == WW3D_FORMAT_UNKNOWN) + { + return; + } + IDirect3DTexture8 * native_texture = dst_texture->Peek_D3D_Texture(); + IDirect3DDevice8 * device = DX8Wrapper::_Get_D3D_Device8(); + if (native_texture == nullptr || device == nullptr) + { + return; + } + IDirect3DSurface8 * dst_surface = nullptr; + if (FAILED(native_texture->GetSurfaceLevel(dst_level, &dst_surface)) || + dst_surface == nullptr) + { + return; + } + IDirect3DSurface8 * staging = nullptr; + const D3DFORMAT d3d_format = WW3DFormat_To_D3DFormat(format); + if (SUCCEEDED(device->CreateImageSurface( + region_width, region_height, d3d_format, &staging)) && + staging != nullptr) + { + D3DLOCKED_RECT staging_lock; + ::ZeroMemory(&staging_lock, sizeof(staging_lock)); + if (SUCCEEDED(staging->LockRect(&staging_lock, nullptr, 0))) + { + const unsigned bytes_per_pixel = ::Get_Bytes_Per_Pixel(format); + const unsigned row_bytes = region_width * bytes_per_pixel; + const unsigned char * src_row = + static_cast(src_data); + unsigned char * dst_row = + static_cast(staging_lock.pBits); + for (unsigned int y = 0; y < region_height; ++y) + { + memcpy(dst_row, src_row, row_bytes); + src_row += src_pitch; + dst_row += staging_lock.Pitch; + } + DX8_ErrorCode(staging->UnlockRect()); + RECT cr_src_rect = { + 0, 0, + static_cast(region_width), + static_cast(region_height) }; + POINT cr_dst_point = { + static_cast(dst_x), + static_cast(dst_y) }; + DX8_ErrorCode(device->CopyRects( + staging, &cr_src_rect, 1, dst_surface, &cr_dst_point)); + } + staging->Release(); + } + dst_surface->Release(); +} + void DX8Backend::Apply_Render_State_Changes() { DX8Wrapper::Apply_Render_State_Changes(); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h index f79f1dd8b52..b562337f4ff 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h @@ -65,6 +65,14 @@ class DX8Backend : public IRenderBackend virtual void Get_Shader(ShaderClass & shader); virtual void Set_Material(const VertexMaterialClass * material); virtual void Set_Texture(unsigned int stage, TextureBaseClass * texture); + virtual void Upload_Texture_Region( + TextureClass * dst_texture, + unsigned int dst_level, + unsigned int dst_x, unsigned int dst_y, + const void * src_data, + unsigned int src_pitch, + unsigned int region_width, unsigned int region_height, + WW3DFormat format); virtual void Apply_Render_State_Changes(); virtual void Apply_Default_State(); virtual void Invalidate_Cached_Render_States(); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h index 57876053b57..ebcfbe57a38 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h @@ -214,6 +214,21 @@ class IRenderBackend virtual void Set_Material(const VertexMaterialClass * material) = 0; virtual void Set_Texture(unsigned int stage, TextureBaseClass * texture) = 0; + // TheSuperHackers @feature bobtista 01/06/2026 Backend-neutral CPU -> GPU + // texture region upload. The DX8 backend implements this with a + // POOL_SYSTEMMEM staging surface and IDirect3DDevice8::CopyRects -- the + // only legal POOL_SYSTEMMEM -> POOL_DEFAULT transport in DX8. Callers + // such as W3DShroud that previously talked to D3D8 directly via + // DX8Wrapper::_Copy_DX8_Rects route through here instead. + virtual void Upload_Texture_Region( + TextureClass * dst_texture, + unsigned int dst_level, + unsigned int dst_x, unsigned int dst_y, + const void * src_data, + unsigned int src_pitch, + unsigned int region_width, unsigned int region_height, + WW3DFormat format) = 0; + virtual void Apply_Render_State_Changes() = 0; virtual void Apply_Default_State() = 0; virtual void Invalidate_Cached_Render_States() = 0; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp index e6ac47d4c7c..1b921a6aa02 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp @@ -717,14 +717,30 @@ void W3DShroud::render(CameraClass *cam) { //USE_PERF_TIMER(shroudCopy) - // TheSuperHackers @refactor bobtista 10/04/2026 SurfaceClass::Copy - // in place of _Copy_DX8_Rects. - pDestSurface->Copy( - dstPoint.x, dstPoint.y, - srcRect.left, srcRect.top, - srcRect.right - srcRect.left, - srcRect.bottom - srcRect.top, - m_pSrcTexture); + // TheSuperHackers @bugfix bobtista 01/06/2026 Upload via IRenderBackend::Upload_Texture_Region: + // the destination is POOL_DEFAULT, where SurfaceClass::Copy's LockRect fallback silently + // no-ops; CopyRects is the only legal SYSTEMMEM to DEFAULT transport in DX8. + SurfaceClass::SurfaceDescription src_desc; + m_pSrcTexture->Get_Description(src_desc); + const unsigned int region_width = + static_cast(srcRect.right - srcRect.left); + const unsigned int region_height = + static_cast(srcRect.bottom - srcRect.top); + const unsigned int bytes_per_pixel = + ::Get_Bytes_Per_Pixel(src_desc.Format); + const unsigned char * src_origin = + static_cast(m_srcTextureData) + + srcRect.top * m_srcTexturePitch + + srcRect.left * bytes_per_pixel; + g_renderBackend->Upload_Texture_Region( + m_pDstTexture, + 0, + static_cast(dstPoint.x), + static_cast(dstPoint.y), + src_origin, + m_srcTexturePitch, + region_width, region_height, + src_desc.Format); } REF_PTR_RELEASE (pDestSurface); From 951a7961321fb8d0cdb40c44486c815ae50cbae0 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Fri, 5 Jun 2026 16:00:49 +1000 Subject: [PATCH 042/523] bugfix(bgfx): guard g_renderBackend against null in addShadow and shroud render --- .../W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp | 4 +++- .../Source/W3DDevice/GameClient/W3DShroud.cpp | 8 +++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp index c627eefcebb..5140de0c59f 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp @@ -3813,7 +3813,9 @@ void W3DVolumetricShadowManager::reset() // ============================================================================ W3DVolumetricShadow* W3DVolumetricShadowManager::addShadow(RenderObjClass *robj, Shadow::ShadowTypeInfo *shadowInfo, Drawable *draw) { - if (!g_renderBackend->Has_Stencil() || !robj || !TheGlobalData->m_useShadowVolumes) + // TheSuperHackers @bugfix bobtista 05/06/2026 Guard g_renderBackend, which can be + // null before the backend exists or during a device-lost/reset window. + if (!g_renderBackend || !g_renderBackend->Has_Stencil() || !robj || !TheGlobalData->m_useShadowVolumes) return nullptr; //right now we require a stencil buffer W3DShadowGeometry *sg=nullptr; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp index 1b921a6aa02..49e07b3c41e 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp @@ -528,11 +528,9 @@ void W3DShroud::render(CameraClass *cam) if (!m_pSrcTexture) return; //nothing to update from. Must be in reset state. - // TheSuperHackers @refactor bobtista 10/04/2026 Replaced the - // raw _Get_D3D_Device8()->TestCooperativeLevel() check with the abstracted - // device-lost flag on IRenderBackend. Same intent: skip rendering this - // frame if the device isn't ready. - if (g_renderBackend->Is_Device_Lost()) + // TheSuperHackers @refactor bobtista 10/04/2026 Skip the frame via the abstracted device-lost + // flag; g_renderBackend can be null before the backend exists or during reset. + if (!g_renderBackend || g_renderBackend->Is_Device_Lost()) return; //device not ready to render anything #if defined(RTS_DEBUG) From e8653d4885495f0481708c4064ca54f74684bc32 Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 21 Apr 2026 14:02:46 -0400 Subject: [PATCH 043/523] feat(bgfx): bootstrap bgfx backend - popup window, shaderc integration, first triangle (cherry picked from commit 597ef1a3abee3a5a96635a99ea00c9e7b625527a) --- .../Source/WWVegas/WW3D2/BgfxBackend.cpp | 324 +++++++++++++++++- .../Source/WWVegas/WW3D2/BgfxBackend.h | 5 + .../Source/WWVegas/WW3D2/CMakeLists.txt | 10 + .../Source/WWVegas/WW3D2/DX8Backend.cpp | 28 +- .../Source/WWVegas/WW3D2/DX8Backend.h | 5 + .../Source/WWVegas/WW3D2/IRenderBackend.h | 17 + .../Source/WWVegas/WW3D2/dx8wrapper.cpp | 12 + .../WWVegas/WW3D2/shaders/fs_passthrough.sc | 12 + .../WWVegas/WW3D2/shaders/varying.def.sc | 6 + .../WWVegas/WW3D2/shaders/vs_passthrough.sc | 14 + .../Libraries/Source/WWVegas/WW3D2/ww3d.cpp | 18 + cmake/bgfx.cmake | 103 ++++++ 12 files changed, 544 insertions(+), 10 deletions(-) create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_passthrough.sc create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_passthrough.sc diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp index b7763258be1..1f32f3d46c8 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -31,14 +31,31 @@ #include "BgfxBackend.h" #include "vector3.h" +#include "wwdebug.h" // Including the bgfx header here is intentional: it forces a compile-time // dependency on the bgfx headers when GGC_RENDER_BACKEND=bgfx. If bgfx // isn't available the build fails here, which is the right place to // catch dependency problems. #include - -#include +#include + +// TheSuperHackers @refactor bobtista 11/04/2026 BgfxBackend +// now creates its own top-level popup window and hands that HWND to bgfx::init. +// Required because Windows DWM promotes whichever swapchain is actively +// presenting to a HWND, so sharing the game's HWND with DX8 loses DX8's +// output. A separate HWND sidesteps the conflict entirely. +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include + +// TheSuperHackers @refactor bobtista 11/04/2026 compiled shader +// bytecode. These headers are generated at build time by ggc_compile_bgfx_shader +// (cmake/bgfx.cmake) and end up in the target's binary dir. They define +// vs_passthrough_dx11[] and fs_passthrough_dx11[] as static const uint8_t +// arrays we hand to bgfx::createShader via bgfx::makeRef. +#include "vs_passthrough_dx11.bin.h" +#include "fs_passthrough_dx11.bin.h" namespace { @@ -51,21 +68,253 @@ namespace // Shared static vector returned from Get_Ambient(). Later work will replace // this with real per-frame ambient tracking. const Vector3 kZeroVec3(0.0f, 0.0f, 0.0f); + +// TheSuperHackers @refactor bobtista 11/04/2026 Tracks +// whether bgfx::init has been called successfully, so Begin_Scene / +// End_Scene / Shutdown can skip bgfx calls if init was never reached. +bool g_bgfxInitialized = false; + +// TheSuperHackers @refactor bobtista 11/04/2026 The +// popup window BgfxBackend owns and hands to bgfx::init. Null until +// Initialize runs, nulled again by Shutdown. +HWND g_bgfxWindow = nullptr; + +const wchar_t * const kBgfxWindowClass = L"GGC_BgfxDebugWindow"; +const int kBgfxWindowWidth = 512; +const int kBgfxWindowHeight = 384; + +// TheSuperHackers @refactor bobtista 11/04/2026 program handle +// for the passthrough shader pair. Created once in Initialize after bgfx::init +// succeeds, destroyed in Shutdown before bgfx::shutdown. +bgfx::ProgramHandle g_passthroughProgram = BGFX_INVALID_HANDLE; + +// Vertex layout used by the test triangle. Position + packed RGBA color. +// Initialized in Initialize since bgfx::VertexLayout::begin needs bgfx to be +// up and running (it queries the active renderer for the pos type). +bgfx::VertexLayout g_triangleLayout; + +struct TriangleVertex +{ + float x; + float y; + float z; + uint32_t abgr; +}; } BgfxBackend::BgfxBackend() { - std::fprintf(stderr, - "[BgfxBackend] stub backend constructed. " - "Most IRenderBackend methods are no-ops; the game will NOT " - "render correctly through this backend yet. " - "\n"); + WWDEBUG_SAY(("[BgfxBackend] backend constructed. " + "Most IRenderBackend methods are still no-ops; DX8Wrapper " + "still owns the real device. bgfx is initialized in Noop " + "renderer mode only.")); } BgfxBackend::~BgfxBackend() { } +// -- Backend lifecycle ------------------------------------------------------- +// +// TheSuperHackers @refactor bobtista 11/04/2026 BgfxBackend +// creates its own top-level popup window and hands that HWND to bgfx::init. +// Session 2b proved that DWM promotes whichever swapchain is actively +// presenting to the game's main HWND, so sharing one window with DX8 loses +// DX8's output. Using a separate window sidesteps the conflict entirely: +// DX8 owns the main game window, bgfx owns a small debug popup next to it. +// Once we're ready to make bgfx the primary renderer we'll destroy the +// debug window and point bgfx at the main HWND. + +namespace +{ +// Minimal window procedure for the bgfx debug window. DefWindowProc handles +// everything we care about for a pure rendering target (close, resize, focus). +LRESULT CALLBACK BgfxDebugWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + return DefWindowProcW(hwnd, msg, wParam, lParam); +} + +// Register a window class for the bgfx debug window. Returns true on success +// or if the class was already registered. +bool RegisterBgfxDebugWindowClass() +{ + WNDCLASSEXW wc; + ZeroMemory(&wc, sizeof(wc)); + wc.cbSize = sizeof(WNDCLASSEXW); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.lpfnWndProc = BgfxDebugWndProc; + wc.hInstance = GetModuleHandleW(nullptr); + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.hbrBackground = reinterpret_cast(COLOR_WINDOW + 1); + wc.lpszClassName = kBgfxWindowClass; + + if (RegisterClassExW(&wc) == 0) + { + const DWORD err = GetLastError(); + if (err == ERROR_CLASS_ALREADY_EXISTS) + { + return true; + } + WWDEBUG_SAY(("[BgfxBackend] RegisterClassExW failed, GetLastError=%lu.", + err)); + return false; + } + return true; +} + +// Create the bgfx debug popup window. Returns the HWND or nullptr on failure. +HWND CreateBgfxDebugWindow() +{ + if (!RegisterBgfxDebugWindowClass()) + { + return nullptr; + } + + const DWORD style = WS_OVERLAPPEDWINDOW; + const DWORD exStyle = WS_EX_NOACTIVATE; + + RECT rc = { 0, 0, kBgfxWindowWidth, kBgfxWindowHeight }; + AdjustWindowRectEx(&rc, style, FALSE, exStyle); + + HWND hwnd = CreateWindowExW( + exStyle, + kBgfxWindowClass, + L"bgfx backend", + style, + 100, 100, + rc.right - rc.left, rc.bottom - rc.top, + nullptr, + nullptr, + GetModuleHandleW(nullptr), + nullptr); + + if (hwnd == nullptr) + { + WWDEBUG_SAY(("[BgfxBackend] CreateWindowExW failed, GetLastError=%lu.", + GetLastError())); + return nullptr; + } + + ShowWindow(hwnd, SW_SHOWNOACTIVATE); + return hwnd; +} +} + +void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) +{ + if (g_bgfxInitialized) + { + WWDEBUG_SAY(("[BgfxBackend] Initialize called twice; ignoring.")); + return; + } + + g_bgfxWindow = CreateBgfxDebugWindow(); + if (g_bgfxWindow == nullptr) + { + WWDEBUG_SAY(("[BgfxBackend] Could not create debug window. " + "Backend will remain dormant.")); + return; + } + + // Force bgfx into single-threaded rendering mode by calling + // bgfx::renderFrame BEFORE bgfx::init. Makes bgfx::frame() fully + // synchronous on the calling thread. + bgfx::renderFrame(); + + bgfx::PlatformData pd; + pd.ndt = nullptr; + pd.nwh = g_bgfxWindow; + pd.context = nullptr; + pd.backBuffer = nullptr; + pd.backBufferDS = nullptr; + bgfx::setPlatformData(pd); + + bgfx::Init initArgs; + initArgs.type = bgfx::RendererType::Count; // auto-select (D3D11 on Windows) + initArgs.resolution.width = static_cast(kBgfxWindowWidth); + initArgs.resolution.height = static_cast(kBgfxWindowHeight); + initArgs.resolution.reset = BGFX_RESET_NONE; + initArgs.platformData = pd; + + if (!bgfx::init(initArgs)) + { + WWDEBUG_SAY(("[BgfxBackend] bgfx::init FAILED on debug window. " + "Backend will remain dormant.")); + DestroyWindow(g_bgfxWindow); + g_bgfxWindow = nullptr; + return; + } + + g_bgfxInitialized = true; + + // Configure view 0 to clear the debug window to a dark teal so it's + // visually obvious bgfx is running and alive. + bgfx::setViewClear(0, + BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, + 0x1a3b5cff, // dark teal, 0xRRGGBBAA + 1.0f, + 0); + bgfx::setViewRect(0, 0, 0, + static_cast(kBgfxWindowWidth), + static_cast(kBgfxWindowHeight)); + + // TheSuperHackers @refactor bobtista 11/04/2026 create the + // passthrough shader program and vertex layout so End_Scene can submit + // a test triangle. If shader creation fails the backend still runs but + // the triangle is skipped. + g_triangleLayout + .begin() + .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true) + .end(); + + const bgfx::Memory * vsMem = bgfx::makeRef(vs_passthrough_dx11, + sizeof(vs_passthrough_dx11)); + const bgfx::Memory * fsMem = bgfx::makeRef(fs_passthrough_dx11, + sizeof(fs_passthrough_dx11)); + bgfx::ShaderHandle vsHandle = bgfx::createShader(vsMem); + bgfx::ShaderHandle fsHandle = bgfx::createShader(fsMem); + if (bgfx::isValid(vsHandle) && bgfx::isValid(fsHandle)) + { + bgfx::setName(vsHandle, "vs_passthrough"); + bgfx::setName(fsHandle, "fs_passthrough"); + g_passthroughProgram = bgfx::createProgram(vsHandle, fsHandle, true); + } + else + { + WWDEBUG_SAY(("[BgfxBackend] passthrough shader createShader FAILED.")); + } + + const bgfx::RendererType::Enum selected = bgfx::getRendererType(); + const char * rendererName = bgfx::getRendererName(selected); + WWDEBUG_SAY(("[BgfxBackend] bgfx::init OK on debug window " + "(renderer=%s, %dx%d, hwnd=%p, passthrough=%s).", + rendererName, kBgfxWindowWidth, kBgfxWindowHeight, + g_bgfxWindow, + bgfx::isValid(g_passthroughProgram) ? "ok" : "FAILED")); +} + +void BgfxBackend::Shutdown() +{ + if (g_bgfxInitialized) + { + if (bgfx::isValid(g_passthroughProgram)) + { + bgfx::destroy(g_passthroughProgram); + g_passthroughProgram = BGFX_INVALID_HANDLE; + } + bgfx::shutdown(); + g_bgfxInitialized = false; + WWDEBUG_SAY(("[BgfxBackend] bgfx::shutdown complete.")); + } + + if (g_bgfxWindow != nullptr) + { + DestroyWindow(g_bgfxWindow); + g_bgfxWindow = nullptr; + } +} + // -- Device state queries ---------------------------------------------------- bool BgfxBackend::Is_Device_Lost() const @@ -94,13 +343,74 @@ void BgfxBackend::Set_Gamma(float /*gamma*/, float /*bright*/, float /*contrast* } // -- Frame lifecycle --------------------------------------------------------- +// +// TheSuperHackers @refactor bobtista 11/04/2026 Begin_Scene +// submits a rotating view setup for view 0. End_Scene submits a test +// triangle via the passthrough shader then calls bgfx::frame() to present. +// This is the first real bgfx draw call. Future work +// replace the test triangle with actual scene geometry submitted through +// the IRenderBackend draw methods. + +namespace +{ +void SubmitTestTriangle() +{ + if (!bgfx::isValid(g_passthroughProgram)) + { + return; + } + + if (bgfx::getAvailTransientVertexBuffer(3, g_triangleLayout) < 3) + { + return; + } + + // Explicitly set view 0's transforms to identity each frame. Without + // this, u_modelViewProj in the vertex shader is whatever bgfx happens + // to have from a previous view transform call (undefined on first use). + static const float identity[16] = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + bgfx::setViewTransform(0, identity, identity); + + bgfx::TransientVertexBuffer tvb; + bgfx::allocTransientVertexBuffer(&tvb, 3, g_triangleLayout); + TriangleVertex * verts = reinterpret_cast(tvb.data); + + // Triangle in clip space. Z = 0.5 puts it safely inside D3D11's [0,1] + // depth range away from both near and far clip planes. Format of the + // color field is ABGR packed (bgfx convention): 0xAABBGGRR as a u32. + verts[0].x = 0.0f; verts[0].y = 0.5f; verts[0].z = 0.5f; verts[0].abgr = 0xff0000ff; // red top + verts[1].x = 0.5f; verts[1].y = -0.5f; verts[1].z = 0.5f; verts[1].abgr = 0xff00ff00; // green right + verts[2].x = -0.5f; verts[2].y = -0.5f; verts[2].z = 0.5f; verts[2].abgr = 0xffff0000; // blue left + + bgfx::setVertexBuffer(0, &tvb); + // No culling — eliminate winding-order confusion while diagnosing. + bgfx::setState(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); + bgfx::submit(0, g_passthroughProgram); +} +} void BgfxBackend::Begin_Scene() { + if (!g_bgfxInitialized) + { + return; + } + bgfx::touch(0); } void BgfxBackend::End_Scene(bool /*flip_frame*/) { + if (!g_bgfxInitialized) + { + return; + } + SubmitTestTriangle(); + bgfx::frame(); } void BgfxBackend::Flip_To_Primary() diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h index 0904691d6a6..13bdbaf9396 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h @@ -37,6 +37,11 @@ class BgfxBackend : public IRenderBackend BgfxBackend(); virtual ~BgfxBackend(); + // -- Backend lifecycle ---------------------------------------------------- + + virtual void Initialize(void * hwnd, int width, int height); + virtual void Shutdown(); + // -- Device state queries ------------------------------------------------- virtual bool Is_Device_Lost() const; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt index 1c716e2e365..dd74a30600a 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt @@ -249,6 +249,16 @@ if(GGC_RENDER_BACKEND STREQUAL "bgfx") ${CMAKE_CURRENT_SOURCE_DIR}/BgfxBackend.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BgfxBackend.h ) + + # TheSuperHackers @refactor bobtista 11/04/2026 shader + # compilation. Only compile these shaders for the bgfx build. The + # ggc_compile_bgfx_shader helper (cmake/bgfx.cmake) creates the + # ggc_bgfx_shaders STATIC library on first call and attaches every + # generated .bin.h to it. corei_ww3d2 links it INTERFACE so g_ww3d2 / + # z_ww3d2 inherit the include directory and build order dependency. + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_passthrough.sc) + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_passthrough.sc) + target_link_libraries(corei_ww3d2 INTERFACE ggc_bgfx_shaders) endif() # Propagate the active backend's compile define to every translation unit diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp index f6e55ceb693..0fce7ce0153 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp @@ -40,6 +40,20 @@ DX8Backend::~DX8Backend() { } +// -- Backend lifecycle ------------------------------------------------------- +// +// DX8Backend is a passive forwarder: DX8Wrapper::Init has already done the +// real device creation before this runs, so there is nothing to do here. +// These exist so the abstract interface has a uniform lifecycle hook. + +void DX8Backend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) +{ +} + +void DX8Backend::Shutdown() +{ +} + // -- Device state queries ---------------------------------------------------- bool DX8Backend::Is_Device_Lost() const @@ -68,15 +82,23 @@ void DX8Backend::Set_Gamma(float gamma, float bright, float contrast, bool calib } // -- Frame lifecycle --------------------------------------------------------- +// +// TheSuperHackers @refactor bobtista 11/04/2026 +// Begin_Scene/End_Scene are intentionally empty during the bgfx cutover. +// ww3d.cpp's WW3D::Begin_Render/End_Render call DX8Wrapper::Begin_Scene / +// End_Scene directly because the D3D8 device is still the primary renderer +// in both the =dx8 and =bgfx builds. The g_renderBackend->Begin_Scene / +// End_Scene pair is a parallel per-frame hook used by BgfxBackend to call +// bgfx::touch / bgfx::frame alongside the DX8 pipeline. When DX8 is finally +// removed these will re-acquire their original forwarding +// behavior. void DX8Backend::Begin_Scene() { - DX8Wrapper::Begin_Scene(); } -void DX8Backend::End_Scene(bool flip_frame) +void DX8Backend::End_Scene(bool /*flip_frame*/) { - DX8Wrapper::End_Scene(flip_frame); } void DX8Backend::Flip_To_Primary() diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h index b562337f4ff..b8e9418ec08 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h @@ -33,6 +33,11 @@ class DX8Backend : public IRenderBackend DX8Backend(); virtual ~DX8Backend(); + // -- Backend lifecycle ---------------------------------------------------- + + virtual void Initialize(void * hwnd, int width, int height); + virtual void Shutdown(); + // -- Device state queries ------------------------------------------------- virtual bool Is_Device_Lost() const; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h index ebcfbe57a38..e463b891af8 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h @@ -171,6 +171,23 @@ class IRenderBackend public: virtual ~IRenderBackend() {} + // ------------------------------------------------------------------------- + // Backend lifecycle + // ------------------------------------------------------------------------- + // + // TheSuperHackers @refactor bobtista 11/04/2026 + // Initialize is called once after DX8Wrapper has finished its own device + // setup, with the game's main HWND and current back-buffer dimensions. + // DX8Backend treats it as a no-op (DX8Wrapper still owns the real device). + // BgfxBackend uses it to call bgfx::init. Shutdown is the symmetric + // teardown, called before DX8Wrapper releases its device. + // + // These are in addition to the existing per-scene Begin_Scene / End_Scene + // pair, which are called every frame. + + virtual void Initialize(void * hwnd, int width, int height) = 0; + virtual void Shutdown() = 0; + // ------------------------------------------------------------------------- // Device state queries // ------------------------------------------------------------------------- diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp index cbc88a5853f..7a9ccb78ab3 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp @@ -396,6 +396,12 @@ void DX8Wrapper::Do_Onetime_Device_Dependent_Inits() // IRenderBackend instance now that the D3D device is ready. See // Core/Libraries/Source/WWVegas/WW3D2/RENDER_BACKEND.md. Init_Render_Backend(); + + // TheSuperHackers @refactor bobtista 11/04/2026 + // Hand the HWND and current back-buffer dimensions to the render backend + // so it can perform any API-specific initialization (bgfx::init for the + // bgfx backend, no-op for DX8Backend). + g_renderBackend->Initialize(_Hwnd, ResolutionWidth, ResolutionHeight); } inline DWORD F2DW(float f) { return *((unsigned*)&f); } @@ -459,6 +465,12 @@ void DX8Wrapper::Do_Onetime_Device_Dependent_Shutdowns() // TheSuperHackers @refactor bobtista 10/04/2026 Tear down the render // backend before the D3D device is released so any backend-owned // resources get released first. See RENDER_BACKEND.md. + if (g_renderBackend != nullptr) + { + // Symmetric counterpart to the Initialize call + // in Do_Onetime_Device_Dependent_Inits. + g_renderBackend->Shutdown(); + } Shutdown_Render_Backend(); /* diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_passthrough.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_passthrough.sc new file mode 100644 index 00000000000..20f409053c7 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_passthrough.sc @@ -0,0 +1,12 @@ +$input v_color0 + +// TheSuperHackers @refactor bobtista 11/04/2026 trivial +// passthrough fragment shader. Writes vertex color straight to the frame +// buffer. + +#include + +void main() +{ + gl_FragColor = v_color0; +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc new file mode 100644 index 00000000000..2aa3cb425c0 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc @@ -0,0 +1,6 @@ +vec4 v_color0 : COLOR0 = vec4(1.0, 0.0, 0.0, 1.0); +vec2 v_texcoord0 : TEXCOORD0 = vec2(0.0, 0.0); + +vec3 a_position : POSITION; +vec4 a_color0 : COLOR0; +vec2 a_texcoord0 : TEXCOORD0; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_passthrough.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_passthrough.sc new file mode 100644 index 00000000000..47b7fe85a3c --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_passthrough.sc @@ -0,0 +1,14 @@ +$input a_position, a_color0 +$output v_color0 + +// TheSuperHackers @refactor bobtista 11/04/2026 trivial +// passthrough vertex shader. Transforms position by the bgfx-provided +// model/view/projection matrix and forwards vertex color. + +#include + +void main() +{ + gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0)); + v_color0 = a_color0; +} diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp index 5f1c1ac6692..c4c41701ffb 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp @@ -109,6 +109,7 @@ #include "WW3D2/rddesc.h" #include "WWMath/Vector3i.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" #include "WWLib/TARGA.h" #include "WW3D2/sortingrenderer.h" #include "WWLib/thread.h" @@ -863,6 +864,14 @@ WW3DErrorType WW3D::Begin_Render(bool clear,bool clearz,const Vector3 & color, f DX8Wrapper::Clear(clear, clearz, color, dest_alpha); } + // TheSuperHackers @refactor bobtista 11/04/2026 Per-frame hook that + // forwards Begin_Scene to the active render backend (a no-op on DX8, + // where Begin_Scene is empty). + if (g_renderBackend != nullptr) + { + g_renderBackend->Begin_Scene(); + } + // Notify D3D that we are beginning to render the frame DX8Wrapper::Begin_Scene(); @@ -1105,6 +1114,15 @@ WW3DErrorType WW3D::End_Render(bool flip_frame) IsRendering = false; + // TheSuperHackers @refactor bobtista 11/04/2026 Per-frame hook that + // forwards End_Scene to the active render backend; BgfxBackend::End_Scene + // calls bgfx::frame() to submit and present the frame. + if (g_renderBackend != nullptr) + { + WWPROFILE("g_renderBackend::End_Scene"); + g_renderBackend->End_Scene(flip_frame); + } + { WWPROFILE("DX8Wrapper::End_Scene"); DX8Wrapper::End_Scene(flip_frame); diff --git a/cmake/bgfx.cmake b/cmake/bgfx.cmake index 63fd3b6f3c0..d14b50da4cd 100644 --- a/cmake/bgfx.cmake +++ b/cmake/bgfx.cmake @@ -37,3 +37,106 @@ foreach(_t bgfx bx bimg shaderc bimg_decode bimg_encode) set_target_properties(${_t} PROPERTIES FOLDER "Dependencies/bgfx") endif() endforeach() + +# TheSuperHackers @refactor bobtista 11/04/2026 shader compilation. +# bgfx shaders are authored in ".sc" files (GLSL-ish with bgfx pragmas) and +# compiled by the shaderc tool (built as part of bgfx.cmake) into per-platform +# bytecode. The --bin2c option emits a C header with the compiled bytecode as +# a uint8_t array, which we then #include from BgfxBackend.cpp and hand to +# bgfx::createShader via bgfx::makeRef. +# +# For now we only target Direct3D 11 on Windows (profile s_5_0). Later +# phases can add GLSL / SPIR-V / Metal variants if we need non-Windows hosts. +# +# Usage in callers: +# ggc_compile_bgfx_shader() # once per shader file +# Then link the ggc_bgfx_shaders target into whatever library consumes the +# generated headers (from either the corei_ww3d2 INTERFACE chain or directly). +# +# The generated header ends up at ${CMAKE_BINARY_DIR}/ggc_bgfx_shaders/ +# with the C array named from the basename, e.g. vs_passthrough_dx11. +# +# The varying.def.sc file is resolved relative to the .sc file's directory +# and must exist there. +# +# The ggc_bgfx_shaders target is a STATIC library with a single dummy .cpp so +# the generated header files have a concrete library to live on. Making it a +# real library (not INTERFACE) is deliberate: CMake's INCLUDE_DIRECTORIES +# propagation through INTERFACE libraries was flaky for our use case and the +# dependency from consumers to the custom_command outputs wasn't firing +# reliably. STATIC + PUBLIC include dirs + link chain is the robust path. + +# Shaderc include path (where bgfx_shader.sh lives in the fetched bgfx tree). +set(GGC_BGFX_SHADER_INCLUDE_DIR "${bgfx_cmake_SOURCE_DIR}/bgfx/src" CACHE INTERNAL "") + +# Shared output directory for every compiled shader header. +set(GGC_BGFX_SHADERS_OUT_DIR "${CMAKE_BINARY_DIR}/ggc_bgfx_shaders" CACHE INTERNAL "") + +# One-time setup: create the ggc_bgfx_shaders target with a dummy source so +# it compiles as a real STATIC library. +function(ggc_bgfx_shaders_init) + if(TARGET ggc_bgfx_shaders) + return() + endif() + + file(MAKE_DIRECTORY "${GGC_BGFX_SHADERS_OUT_DIR}") + set(_dummy "${GGC_BGFX_SHADERS_OUT_DIR}/_ggc_bgfx_shaders_dummy.cpp") + if(NOT EXISTS "${_dummy}") + file(WRITE "${_dummy}" + "// Auto-generated. Exists only so ggc_bgfx_shaders has a compilable source.\n" + "namespace { char ggc_bgfx_shaders_anchor = 0; }\n") + endif() + + add_library(ggc_bgfx_shaders STATIC "${_dummy}") + set_target_properties(ggc_bgfx_shaders PROPERTIES FOLDER "Dependencies/bgfx") + target_include_directories(ggc_bgfx_shaders PUBLIC "${GGC_BGFX_SHADERS_OUT_DIR}") +endfunction() + +function(ggc_compile_bgfx_shader source_sc) + if(NOT TARGET shaderc) + message(FATAL_ERROR "ggc_compile_bgfx_shader: shaderc target not available. " + "Ensure BGFX_BUILD_TOOLS_SHADER=ON and bgfx.cmake is included.") + endif() + + ggc_bgfx_shaders_init() + + get_filename_component(_sc_abs "${source_sc}" ABSOLUTE) + get_filename_component(_sc_dir "${_sc_abs}" DIRECTORY) + get_filename_component(_sc_name "${_sc_abs}" NAME_WE) + + if(_sc_name MATCHES "^vs_") + set(_shader_type "vertex") + elseif(_sc_name MATCHES "^fs_") + set(_shader_type "fragment") + else() + message(FATAL_ERROR "ggc_compile_bgfx_shader: '${_sc_name}' must start with vs_ or fs_.") + endif() + + set(_out_header "${GGC_BGFX_SHADERS_OUT_DIR}/${_sc_name}_dx11.bin.h") + set(_varname "${_sc_name}_dx11") + set(_varying_def "${_sc_dir}/varying.def.sc") + + add_custom_command( + OUTPUT "${_out_header}" + COMMAND "$" + -f "${_sc_abs}" + -o "${_out_header}" + --bin2c "${_varname}" + -i "${GGC_BGFX_SHADER_INCLUDE_DIR}" + --platform windows + --profile s_5_0 + --type "${_shader_type}" + --varyingdef "${_varying_def}" + -O 3 + MAIN_DEPENDENCY "${_sc_abs}" + DEPENDS "${_varying_def}" shaderc + COMMENT "Compiling bgfx shader ${_sc_name}" + VERBATIM + ) + + target_sources(ggc_bgfx_shaders PRIVATE "${_out_header}") + set_source_files_properties("${_out_header}" PROPERTIES + GENERATED TRUE + HEADER_FILE_ONLY TRUE + ) +endfunction() From b9fe35a00522dbf7f67d3a612e31f6e596951634 Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 21 Apr 2026 14:02:46 -0400 Subject: [PATCH 044/523] feat(bgfx): FVF vertex layouts and ShaderClass to bgfx program translation (cherry picked from commit 8ee798606876ff0be05eb0f8aba56f672f43f300) --- .../Source/WWVegas/WW3D2/BgfxBackend.cpp | 622 +++++++++++++++++- .../Source/WWVegas/WW3D2/CMakeLists.txt | 10 + .../WWVegas/WW3D2/shaders/fs_solid_lit.sc | 12 + .../WWVegas/WW3D2/shaders/fs_textured_lit.sc | 17 + .../WW3D2/shaders/fs_textured_lit_atest.sc | 30 + .../WW3D2/shaders/fs_textured_unlit.sc | 15 + .../WWVegas/WW3D2/shaders/vs_solid_lit.sc | 17 + .../WWVegas/WW3D2/shaders/vs_textured_lit.sc | 16 + .../WW3D2/shaders/vs_textured_unlit.sc | 15 + 9 files changed, 742 insertions(+), 12 deletions(-) create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_solid_lit.sc create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit.sc create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit_atest.sc create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_unlit.sc create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_solid_lit.sc create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_textured_lit.sc create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_textured_unlit.sc diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp index 1f32f3d46c8..ff929c30efc 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -30,6 +30,10 @@ #include "BgfxBackend.h" +#include "dx8fvf.h" +#include "dx8indexbuffer.h" +#include "dx8vertexbuffer.h" +#include "shader.h" #include "vector3.h" #include "wwdebug.h" @@ -57,6 +61,17 @@ #include "vs_passthrough_dx11.bin.h" #include "fs_passthrough_dx11.bin.h" +// TheSuperHackers @refactor bobtista 11/04/2026 preset shader +// programs. These cover the three most common ShaderClass preset buckets +// (lit textured, unlit textured, alpha-tested lit textured). See +#include "vs_textured_lit_dx11.bin.h" +#include "fs_textured_lit_dx11.bin.h" +#include "vs_textured_unlit_dx11.bin.h" +#include "fs_textured_unlit_dx11.bin.h" +#include "fs_textured_lit_atest_dx11.bin.h" +#include "vs_solid_lit_dx11.bin.h" +#include "fs_solid_lit_dx11.bin.h" + namespace { // Anchor a reference to one bgfx symbol so the linker must resolve bgfx @@ -88,11 +103,101 @@ const int kBgfxWindowHeight = 384; // succeeds, destroyed in Shutdown before bgfx::shutdown. bgfx::ProgramHandle g_passthroughProgram = BGFX_INVALID_HANDLE; +// TheSuperHackers @refactor bobtista 11/04/2026 preset shader +// programs. Created in Initialize, destroyed in Shutdown. Not yet used by +// any draw call - that wiring lands in a later session along with the +// ShaderClass preset -> ProgramHandle mapping table. +bgfx::ProgramHandle g_texturedLitProgram = BGFX_INVALID_HANDLE; +bgfx::ProgramHandle g_texturedUnlitProgram = BGFX_INVALID_HANDLE; +bgfx::ProgramHandle g_texturedLitAtestProgram = BGFX_INVALID_HANDLE; +bgfx::ProgramHandle g_solidLitProgram = BGFX_INVALID_HANDLE; + +// Sampler uniform shared by all textured fragment shaders. Bound to stage 0. +bgfx::UniformHandle g_sTex0 = BGFX_INVALID_HANDLE; +// Alpha-test parameters consumed by fs_textured_lit_atest. .x is the +// reference threshold in [0, 1]; engine writes ShaderClass alpha-ref / 255. +// Named u_atestParams (not u_alphaRef) to avoid bgfx_shader.sh's internal +// u_alphaRef4 conflict. See fs_textured_lit_atest.sc for details. +bgfx::UniformHandle g_uAtestParams = BGFX_INVALID_HANDLE; + // Vertex layout used by the test triangle. Position + packed RGBA color. // Initialized in Initialize since bgfx::VertexLayout::begin needs bgfx to be // up and running (it queries the active renderer for the pos type). bgfx::VertexLayout g_triangleLayout; +// TheSuperHackers @refactor bobtista 11/04/2026 standard +// vertex layouts. One per common FVF format +// FVF table). Initialized in Initialize, used by the vertex +// buffer creation path. Names follow the FVF tags - P=position, +// N=normal, D=diffuse (color0), T=texcoord. +bgfx::VertexLayout g_layoutP; // XYZ +bgfx::VertexLayout g_layoutPN; // XYZ + NORMAL +bgfx::VertexLayout g_layoutPNT1; // XYZ + NORMAL + TEX1 +bgfx::VertexLayout g_layoutPNT2; // XYZ + NORMAL + TEX1 + TEX2 +bgfx::VertexLayout g_layoutPT1; // XYZ + TEX1 +bgfx::VertexLayout g_layoutPDT1; // XYZ + DIFFUSE + TEX1 +bgfx::VertexLayout g_layoutPNDT1; // XYZ + NORMAL + DIFFUSE + TEX1 +bgfx::VertexLayout g_layoutPNDT2; // XYZ + NORMAL + DIFFUSE + TEX1 + TEX2 + +void BuildStandardVertexLayouts() +{ + g_layoutP + .begin() + .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) + .end(); + + g_layoutPN + .begin() + .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::Normal, 3, bgfx::AttribType::Float) + .end(); + + g_layoutPNT1 + .begin() + .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::Normal, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float) + .end(); + + g_layoutPNT2 + .begin() + .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::Normal, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float) + .add(bgfx::Attrib::TexCoord1, 2, bgfx::AttribType::Float) + .end(); + + g_layoutPT1 + .begin() + .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float) + .end(); + + g_layoutPDT1 + .begin() + .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true) + .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float) + .end(); + + g_layoutPNDT1 + .begin() + .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::Normal, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true) + .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float) + .end(); + + g_layoutPNDT2 + .begin() + .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::Normal, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true) + .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float) + .add(bgfx::Attrib::TexCoord1, 2, bgfx::AttribType::Float) + .end(); +} + struct TriangleVertex { float x; @@ -162,6 +267,293 @@ bool RegisterBgfxDebugWindowClass() return true; } +// TheSuperHackers @refactor bobtista 11/04/2026 ShaderClass +// translation table. Maps a ShaderClass instance to (program handle, +// bgfx state bits). Defined but not yet called by any draw path - the +// wiring lands in a later session along with the per-stage TSS uber-shader. +// +// Mapping rules: +// - alpha-test enabled & textured & lit -> g_texturedLitAtestProgram +// - textured & lit -> g_texturedLitProgram +// - textured & !lit -> g_texturedUnlitProgram +// - !textured & lit -> g_solidLitProgram +// - else -> g_passthroughProgram (debug) +// +// State bit translation is mechanical: depth compare, depth write, color +// write, blend factors, cull. Detail-blend / fog / specular gradient are +// not handled here yet - they need shader code, not state bits, and that +// lands when we add the terrain shader and the fixed-function lighting +// path. + +uint64_t TranslateBlendFactor(ShaderClass::SrcBlendFuncType src) +{ + switch (src) + { + case ShaderClass::SRCBLEND_ZERO: return BGFX_STATE_BLEND_ZERO; + case ShaderClass::SRCBLEND_ONE: return BGFX_STATE_BLEND_ONE; + case ShaderClass::SRCBLEND_SRC_ALPHA: return BGFX_STATE_BLEND_SRC_ALPHA; + case ShaderClass::SRCBLEND_ONE_MINUS_SRC_ALPHA: return BGFX_STATE_BLEND_INV_SRC_ALPHA; + default: return BGFX_STATE_BLEND_ONE; + } +} + +uint64_t TranslateBlendFactor(ShaderClass::DstBlendFuncType dst) +{ + switch (dst) + { + case ShaderClass::DSTBLEND_ZERO: return BGFX_STATE_BLEND_ZERO; + case ShaderClass::DSTBLEND_ONE: return BGFX_STATE_BLEND_ONE; + case ShaderClass::DSTBLEND_SRC_COLOR: return BGFX_STATE_BLEND_SRC_COLOR; + case ShaderClass::DSTBLEND_ONE_MINUS_SRC_COLOR: return BGFX_STATE_BLEND_INV_SRC_COLOR; + case ShaderClass::DSTBLEND_SRC_ALPHA: return BGFX_STATE_BLEND_SRC_ALPHA; + case ShaderClass::DSTBLEND_ONE_MINUS_SRC_ALPHA: return BGFX_STATE_BLEND_INV_SRC_ALPHA; + default: return BGFX_STATE_BLEND_ZERO; + } +} + +uint64_t TranslateDepthCompare(ShaderClass::DepthCompareType cmp) +{ + switch (cmp) + { + case ShaderClass::PASS_NEVER: return BGFX_STATE_DEPTH_TEST_NEVER; + case ShaderClass::PASS_LESS: return BGFX_STATE_DEPTH_TEST_LESS; + case ShaderClass::PASS_EQUAL: return BGFX_STATE_DEPTH_TEST_EQUAL; + case ShaderClass::PASS_LEQUAL: return BGFX_STATE_DEPTH_TEST_LEQUAL; + case ShaderClass::PASS_GREATER: return BGFX_STATE_DEPTH_TEST_GREATER; + case ShaderClass::PASS_NOTEQUAL: return BGFX_STATE_DEPTH_TEST_NOTEQUAL; + case ShaderClass::PASS_GEQUAL: return BGFX_STATE_DEPTH_TEST_GEQUAL; + case ShaderClass::PASS_ALWAYS: return BGFX_STATE_DEPTH_TEST_ALWAYS; + default: return BGFX_STATE_DEPTH_TEST_LEQUAL; + } +} + +bgfx::ProgramHandle PickProgramForShader(const ShaderClass & shader) +{ + const bool textured = (shader.Get_Texturing() != ShaderClass::TEXTURING_DISABLE); + const bool lit = (shader.Get_Primary_Gradient() != ShaderClass::GRADIENT_DISABLE); + const bool atest = (shader.Get_Alpha_Test() != ShaderClass::ALPHATEST_DISABLE); + + if (atest && textured) + { + return g_texturedLitAtestProgram; + } + if (textured && lit) + { + return g_texturedLitProgram; + } + if (textured && !lit) + { + return g_texturedUnlitProgram; + } + if (!textured && lit) + { + return g_solidLitProgram; + } + return g_passthroughProgram; +} + +uint64_t BuildBgfxStateForShader(const ShaderClass & shader) +{ + uint64_t state = 0; + + state |= TranslateDepthCompare(shader.Get_Depth_Compare()); + + if (shader.Get_Depth_Mask() == ShaderClass::DEPTH_WRITE_ENABLE) + { + state |= BGFX_STATE_WRITE_Z; + } + + if (shader.Get_Color_Mask() == ShaderClass::COLOR_WRITE_ENABLE) + { + state |= BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A; + } + + const uint64_t srcBits = TranslateBlendFactor(shader.Get_Src_Blend_Func()); + const uint64_t dstBits = TranslateBlendFactor(shader.Get_Dst_Blend_Func()); + state |= BGFX_STATE_BLEND_FUNC(srcBits, dstBits); + + if (shader.Get_Cull_Mode() == ShaderClass::CULL_MODE_ENABLE) + { + // W3D used D3D's clockwise = front by default. bgfx CW culls the + // clockwise face (i.e. the back face when CW is the front), so + // BGFX_STATE_CULL_CW matches the W3D convention. + state |= BGFX_STATE_CULL_CW; + } + + return state; +} + +[[maybe_unused]] const auto kShaderTranslationAnchor = &PickProgramForShader; + +// TheSuperHackers @refactor bobtista 11/04/2026 generic FVF +// to bgfx::VertexLayout translator. Walks the FVFInfoClass offset +// table and emits attributes in offset order. Handles arbitrary FVF +// combinations including padding and unused texcoord stages by issuing +// skip() calls between attributes whose offsets do not abut. +// +// The bgfx layout API does not let you assign explicit offsets, only +// running totals via add()/skip(). So this helper accumulates the +// offsets in source order and emits skip() calls to fast-forward to +// each attribute's true offset, then adds the attribute itself. +// +// Returns true if a layout was built. Caller must have called begin() +// already and must call end() afterwards. + +void AddAttribAtOffset(bgfx::VertexLayout & layout, + unsigned & cursor, + unsigned target_offset, + bgfx::Attrib::Enum attr, + uint8_t count, + bgfx::AttribType::Enum type, + bool normalized, + unsigned attr_size_bytes) +{ + if (target_offset > cursor) + { + layout.skip(static_cast(target_offset - cursor)); + cursor = target_offset; + } + layout.add(attr, count, type, normalized); + cursor += attr_size_bytes; +} + +bool BuildBgfxLayoutForFVF(const FVFInfoClass & fvf, bgfx::VertexLayout & out) +{ + const unsigned bits = fvf.Get_FVF(); + const unsigned totalSize = fvf.Get_FVF_Size(); + + out.begin(); + unsigned cursor = 0; + + if ((bits & D3DFVF_XYZ) == D3DFVF_XYZ) + { + AddAttribAtOffset(out, cursor, fvf.Get_Location_Offset(), + bgfx::Attrib::Position, 3, bgfx::AttribType::Float, false, + 3 * sizeof(float)); + } + else if ((bits & D3DFVF_XYZRHW) == D3DFVF_XYZRHW) + { + // Pre-transformed: 4 floats (x, y, z, rhw). bgfx has no native + // pre-transformed attribute - declare as 4-component position + // and the shader is expected to bypass the projection matrix. + AddAttribAtOffset(out, cursor, fvf.Get_Location_Offset(), + bgfx::Attrib::Position, 4, bgfx::AttribType::Float, false, + 4 * sizeof(float)); + } + + if ((bits & D3DFVF_NORMAL) == D3DFVF_NORMAL) + { + AddAttribAtOffset(out, cursor, fvf.Get_Normal_Offset(), + bgfx::Attrib::Normal, 3, bgfx::AttribType::Float, false, + 3 * sizeof(float)); + } + + if ((bits & D3DFVF_DIFFUSE) == D3DFVF_DIFFUSE) + { + // D3DCOLOR is BGRA u8x4 packed; bgfx Color0 with normalized=true + // matches that on D3D11. + AddAttribAtOffset(out, cursor, fvf.Get_Diffuse_Offset(), + bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true, + sizeof(uint32_t)); + } + + if ((bits & D3DFVF_SPECULAR) == D3DFVF_SPECULAR) + { + AddAttribAtOffset(out, cursor, fvf.Get_Specular_Offset(), + bgfx::Attrib::Color1, 4, bgfx::AttribType::Uint8, true, + sizeof(uint32_t)); + } + + // Texcoord sets - decode each stage's size from the FVF bits. + const bgfx::Attrib::Enum kTexAttr[8] = { + bgfx::Attrib::TexCoord0, bgfx::Attrib::TexCoord1, + bgfx::Attrib::TexCoord2, bgfx::Attrib::TexCoord3, + bgfx::Attrib::TexCoord4, bgfx::Attrib::TexCoord5, + bgfx::Attrib::TexCoord6, bgfx::Attrib::TexCoord7, + }; + + const unsigned numTex = (bits & D3DFVF_TEXCOUNT_MASK) >> D3DFVF_TEXCOUNT_SHIFT; + for (unsigned i = 0; i < numTex && i < 8; ++i) + { + unsigned componentCount = 2; + if ((static_cast(bits) & D3DFVF_TEXCOORDSIZE1(i)) == D3DFVF_TEXCOORDSIZE1(i)) componentCount = 1; + else if ((static_cast(bits) & D3DFVF_TEXCOORDSIZE3(i)) == D3DFVF_TEXCOORDSIZE3(i)) componentCount = 3; + else if ((static_cast(bits) & D3DFVF_TEXCOORDSIZE4(i)) == D3DFVF_TEXCOORDSIZE4(i)) componentCount = 4; + // else default 2 + + AddAttribAtOffset(out, cursor, fvf.Get_Tex_Offset(i), + kTexAttr[i], + static_cast(componentCount), + bgfx::AttribType::Float, false, + componentCount * sizeof(float)); + } + + // Pad up to the FVF stride if there is trailing space the bgfx + // layout has not accounted for. This keeps strides in lockstep so + // bgfx reads vertices at the same byte boundaries the engine writes + // them at. + if (totalSize > cursor) + { + out.skip(static_cast(totalSize - cursor)); + } + + out.end(); + return out.getStride() == totalSize; +} + +[[maybe_unused]] const auto kFvfLayoutAnchor = &BuildBgfxLayoutForFVF; + +// TheSuperHackers @refactor bobtista 11/04/2026 Set_Shader +// telemetry. The bgfx backend caches the picked program and state bits +// for the most recent ShaderClass it received, and tracks the unique +// shader bit patterns it has seen so we can log them once each. This +// is diagnostic only - no draw call uses these yet, but it confirms +// that the engine is calling Set_Shader on the bgfx backend and gives +// us real preset coverage data without touching rendering. +bgfx::ProgramHandle g_currentBgfxProgram = BGFX_INVALID_HANDLE; +uint64_t g_currentBgfxState = 0; + +const int kMaxLoggedPresets = 32; +unsigned int g_loggedPresetBits[kMaxLoggedPresets] = { 0 }; +int g_loggedPresetCount = 0; + +bool RecordPresetIfNew(unsigned int bits) +{ + for (int i = 0; i < g_loggedPresetCount; ++i) + { + if (g_loggedPresetBits[i] == bits) + { + return false; + } + } + if (g_loggedPresetCount < kMaxLoggedPresets) + { + g_loggedPresetBits[g_loggedPresetCount++] = bits; + return true; + } + return false; +} + +const char * ProgramDebugName(bgfx::ProgramHandle h) +{ + if (h.idx == g_passthroughProgram.idx) return "passthrough"; + if (h.idx == g_texturedLitProgram.idx) return "textured_lit"; + if (h.idx == g_texturedUnlitProgram.idx) return "textured_unlit"; + if (h.idx == g_texturedLitAtestProgram.idx) return "textured_lit_atest"; + if (h.idx == g_solidLitProgram.idx) return "solid_lit"; + return "?"; +} + +// Per-method "first call" flags so we can confirm the engine is routing +// each entry point through the bgfx backend at least once. Logged once, +// then suppressed to keep WWDebug.txt readable. +bool g_loggedFirstApplyRenderState = false; +bool g_loggedFirstSetTexture = false; +bool g_loggedFirstSetMaterial = false; +bool g_loggedFirstDrawTriangles = false; +bool g_loggedFirstSetVertexBuffer = false; +bool g_loggedFirstSetIndexBuffer = false; + // Create the bgfx debug popup window. Returns the HWND or nullptr on failure. HWND CreateBgfxDebugWindow() { @@ -268,6 +660,8 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) .add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true) .end(); + BuildStandardVertexLayouts(); + const bgfx::Memory * vsMem = bgfx::makeRef(vs_passthrough_dx11, sizeof(vs_passthrough_dx11)); const bgfx::Memory * fsMem = bgfx::makeRef(fs_passthrough_dx11, @@ -285,13 +679,92 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) WWDEBUG_SAY(("[BgfxBackend] passthrough shader createShader FAILED.")); } + // TheSuperHackers @refactor bobtista 11/04/2026 build the + // three preset shader programs. They share two vertex shaders and three + // fragment shaders. The lit-atest program reuses vs_textured_lit. Each + // createProgram(..., true) hands ownership of its shader handles to the + // program, so we must NOT manually destroy them later. + g_sTex0 = bgfx::createUniform("s_tex0", bgfx::UniformType::Sampler); + g_uAtestParams = bgfx::createUniform("u_atestParams", bgfx::UniformType::Vec4); + + const bgfx::Memory * vsLitMem = bgfx::makeRef(vs_textured_lit_dx11, + sizeof(vs_textured_lit_dx11)); + const bgfx::Memory * fsLitMem = bgfx::makeRef(fs_textured_lit_dx11, + sizeof(fs_textured_lit_dx11)); + const bgfx::Memory * vsUnlitMem = bgfx::makeRef(vs_textured_unlit_dx11, + sizeof(vs_textured_unlit_dx11)); + const bgfx::Memory * fsUnlitMem = bgfx::makeRef(fs_textured_unlit_dx11, + sizeof(fs_textured_unlit_dx11)); + const bgfx::Memory * fsAtestMem = bgfx::makeRef(fs_textured_lit_atest_dx11, + sizeof(fs_textured_lit_atest_dx11)); + + bgfx::ShaderHandle vsLit = bgfx::createShader(vsLitMem); + bgfx::ShaderHandle fsLit = bgfx::createShader(fsLitMem); + bgfx::ShaderHandle vsUnlit = bgfx::createShader(vsUnlitMem); + bgfx::ShaderHandle fsUnlit = bgfx::createShader(fsUnlitMem); + bgfx::ShaderHandle fsAtest = bgfx::createShader(fsAtestMem); + + if (bgfx::isValid(vsLit) && bgfx::isValid(fsLit)) + { + bgfx::setName(vsLit, "vs_textured_lit"); + bgfx::setName(fsLit, "fs_textured_lit"); + // vsLit is also used by the atest program; createProgram(..., true) + // would destroy it after the first call. Pass false here, then + // again for the atest program; we destroy vsLit ourselves below. + g_texturedLitProgram = bgfx::createProgram(vsLit, fsLit, false); + } + if (bgfx::isValid(vsUnlit) && bgfx::isValid(fsUnlit)) + { + bgfx::setName(vsUnlit, "vs_textured_unlit"); + bgfx::setName(fsUnlit, "fs_textured_unlit"); + g_texturedUnlitProgram = bgfx::createProgram(vsUnlit, fsUnlit, true); + } + if (bgfx::isValid(vsLit) && bgfx::isValid(fsAtest)) + { + bgfx::setName(fsAtest, "fs_textured_lit_atest"); + g_texturedLitAtestProgram = bgfx::createProgram(vsLit, fsAtest, false); + } + // Now safe to release standalone shader handles - the programs hold + // their own internal refs. + if (bgfx::isValid(vsLit)) + { + bgfx::destroy(vsLit); + } + if (bgfx::isValid(fsLit)) + { + bgfx::destroy(fsLit); + } + if (bgfx::isValid(fsAtest)) + { + bgfx::destroy(fsAtest); + } + + // Solid lit shader (bucket C) - vertex color only, no texture sample. + const bgfx::Memory * vsSolidMem = bgfx::makeRef(vs_solid_lit_dx11, + sizeof(vs_solid_lit_dx11)); + const bgfx::Memory * fsSolidMem = bgfx::makeRef(fs_solid_lit_dx11, + sizeof(fs_solid_lit_dx11)); + bgfx::ShaderHandle vsSolid = bgfx::createShader(vsSolidMem); + bgfx::ShaderHandle fsSolid = bgfx::createShader(fsSolidMem); + if (bgfx::isValid(vsSolid) && bgfx::isValid(fsSolid)) + { + bgfx::setName(vsSolid, "vs_solid_lit"); + bgfx::setName(fsSolid, "fs_solid_lit"); + g_solidLitProgram = bgfx::createProgram(vsSolid, fsSolid, true); + } + const bgfx::RendererType::Enum selected = bgfx::getRendererType(); const char * rendererName = bgfx::getRendererName(selected); WWDEBUG_SAY(("[BgfxBackend] bgfx::init OK on debug window " - "(renderer=%s, %dx%d, hwnd=%p, passthrough=%s).", + "(renderer=%s, %dx%d, hwnd=%p, passthrough=%s, " + "lit=%s, unlit=%s, lit_atest=%s, solid=%s).", rendererName, kBgfxWindowWidth, kBgfxWindowHeight, g_bgfxWindow, - bgfx::isValid(g_passthroughProgram) ? "ok" : "FAILED")); + bgfx::isValid(g_passthroughProgram) ? "ok" : "FAILED", + bgfx::isValid(g_texturedLitProgram) ? "ok" : "FAILED", + bgfx::isValid(g_texturedUnlitProgram) ? "ok" : "FAILED", + bgfx::isValid(g_texturedLitAtestProgram) ? "ok" : "FAILED", + bgfx::isValid(g_solidLitProgram) ? "ok" : "FAILED")); } void BgfxBackend::Shutdown() @@ -303,6 +776,36 @@ void BgfxBackend::Shutdown() bgfx::destroy(g_passthroughProgram); g_passthroughProgram = BGFX_INVALID_HANDLE; } + if (bgfx::isValid(g_texturedLitProgram)) + { + bgfx::destroy(g_texturedLitProgram); + g_texturedLitProgram = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(g_texturedUnlitProgram)) + { + bgfx::destroy(g_texturedUnlitProgram); + g_texturedUnlitProgram = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(g_texturedLitAtestProgram)) + { + bgfx::destroy(g_texturedLitAtestProgram); + g_texturedLitAtestProgram = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(g_solidLitProgram)) + { + bgfx::destroy(g_solidLitProgram); + g_solidLitProgram = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(g_sTex0)) + { + bgfx::destroy(g_sTex0); + g_sTex0 = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(g_uAtestParams)) + { + bgfx::destroy(g_uAtestParams); + g_uAtestParams = BGFX_INVALID_HANDLE; + } bgfx::shutdown(); g_bgfxInitialized = false; WWDEBUG_SAY(("[BgfxBackend] bgfx::shutdown complete.")); @@ -429,20 +932,70 @@ void BgfxBackend::Set_Viewport(const RenderBackendViewport & /*viewport*/) // -- Vertex / index buffers -------------------------------------------------- -void BgfxBackend::Set_Vertex_Buffer(const VertexBufferClass * /*vb*/, unsigned int /*stream*/) +void BgfxBackend::Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int /*stream*/) { + if (!g_loggedFirstSetVertexBuffer && vb != nullptr) + { + g_loggedFirstSetVertexBuffer = true; + const FVFInfoClass & fvf = vb->FVF_Info(); + WWDEBUG_SAY(("[BgfxBackend] Set_Vertex_Buffer first call (static VB path) " + "fvf=0x%08x size=%u verts=%u type=%u", + fvf.Get_FVF(), + fvf.Get_FVF_Size(), + vb->Get_Vertex_Count(), + vb->Type())); + + // Verification: build a bgfx layout from this FVF and + // log whether the resulting stride matches the source. If it does + // we can confidently start creating real bgfx vertex buffers from + // VertexBufferClass instances in a follow-up commit. + bgfx::VertexLayout layout; + const bool ok = BuildBgfxLayoutForFVF(fvf, layout); + const unsigned want = fvf.Get_FVF_Size(); + const unsigned got = layout.getStride(); + WWDEBUG_SAY(("[BgfxBackend] FVF -> bgfx layout %s " + "(stride want=%u got=%u)", + ok ? "OK" : "MISMATCH", want, got)); + } } -void BgfxBackend::Set_Vertex_Buffer(const DynamicVBAccessClass & /*vba*/) +void BgfxBackend::Set_Vertex_Buffer(const DynamicVBAccessClass & vba) { + if (!g_loggedFirstSetVertexBuffer) + { + g_loggedFirstSetVertexBuffer = true; + const FVFInfoClass & fvf = vba.FVF_Info(); + WWDEBUG_SAY(("[BgfxBackend] Set_Vertex_Buffer first call (dynamic VB path) " + "fvf=0x%08x size=%u verts=%u type=%u", + fvf.Get_FVF(), + fvf.Get_FVF_Size(), + vba.Get_Vertex_Count(), + vba.Get_Type())); + } } -void BgfxBackend::Set_Index_Buffer(const IndexBufferClass * /*ib*/, unsigned short /*index_base_offset*/) +void BgfxBackend::Set_Index_Buffer(const IndexBufferClass * ib, unsigned short /*index_base_offset*/) { + if (!g_loggedFirstSetIndexBuffer && ib != nullptr) + { + g_loggedFirstSetIndexBuffer = true; + WWDEBUG_SAY(("[BgfxBackend] Set_Index_Buffer first call (static IB path) " + "indices=%u type=%u", + ib->Get_Index_Count(), + ib->Type())); + } } -void BgfxBackend::Set_Index_Buffer(const DynamicIBAccessClass & /*iba*/, unsigned short /*index_base_offset*/) +void BgfxBackend::Set_Index_Buffer(const DynamicIBAccessClass & iba, unsigned short /*index_base_offset*/) { + if (!g_loggedFirstSetIndexBuffer) + { + g_loggedFirstSetIndexBuffer = true; + WWDEBUG_SAY(("[BgfxBackend] Set_Index_Buffer first call (dynamic IB path) " + "indices=%u type=%u", + iba.Get_Index_Count(), + iba.Get_Type())); + } } void BgfxBackend::Set_Index_Buffer_Index_Offset(unsigned int /*offset*/) @@ -451,8 +1004,26 @@ void BgfxBackend::Set_Index_Buffer_Index_Offset(unsigned int /*offset*/) // -- State: shaders, materials, textures ------------------------------------ -void BgfxBackend::Set_Shader(const ShaderClass & /*shader*/) +void BgfxBackend::Set_Shader(const ShaderClass & shader) { + // TheSuperHackers @refactor bobtista 11/04/2026 cache the + // picked program and state bits for the next draw call, and log any + // shader bit pattern we have not seen before (capped at kMaxLoggedPresets + // to keep WWDebug.txt readable). No draw call consumes the cached state + // yet - this is purely diagnostic so we can confirm the engine is + // routing Set_Shader through the bgfx backend and see which preset + // buckets the game actually uses. + g_currentBgfxProgram = PickProgramForShader(shader); + g_currentBgfxState = BuildBgfxStateForShader(shader); + + if (RecordPresetIfNew(shader.Get_Bits())) + { + WWDEBUG_SAY(("[BgfxBackend] Set_Shader new preset bits=0x%08x -> " + "program=%s state=0x%016llx", + shader.Get_Bits(), + ProgramDebugName(g_currentBgfxProgram), + static_cast(g_currentBgfxState))); + } } void BgfxBackend::Get_Shader(ShaderClass & /*shader*/) @@ -461,10 +1032,20 @@ void BgfxBackend::Get_Shader(ShaderClass & /*shader*/) void BgfxBackend::Set_Material(const VertexMaterialClass * /*material*/) { + if (!g_loggedFirstSetMaterial) + { + g_loggedFirstSetMaterial = true; + WWDEBUG_SAY(("[BgfxBackend] Set_Material first call")); + } } -void BgfxBackend::Set_Texture(unsigned int /*stage*/, TextureBaseClass * /*texture*/) +void BgfxBackend::Set_Texture(unsigned int stage, TextureBaseClass * /*texture*/) { + if (!g_loggedFirstSetTexture) + { + g_loggedFirstSetTexture = true; + WWDEBUG_SAY(("[BgfxBackend] Set_Texture first call (stage=%u)", stage)); + } } void BgfxBackend::Upload_Texture_Region( @@ -482,6 +1063,11 @@ void BgfxBackend::Upload_Texture_Region( void BgfxBackend::Apply_Render_State_Changes() { + if (!g_loggedFirstApplyRenderState) + { + g_loggedFirstApplyRenderState = true; + WWDEBUG_SAY(("[BgfxBackend] Apply_Render_State_Changes first call")); + } } void BgfxBackend::Apply_Default_State() @@ -626,18 +1212,30 @@ LightEnvironmentClass * BgfxBackend::Get_Light_Environment() const // -- Draw calls -------------------------------------------------------------- void BgfxBackend::Draw_Triangles(unsigned short /*start_index*/, - unsigned short /*polygon_count*/, + unsigned short polygon_count, unsigned short /*min_vertex_index*/, - unsigned short /*vertex_count*/) + unsigned short vertex_count) { + if (!g_loggedFirstDrawTriangles) + { + g_loggedFirstDrawTriangles = true; + WWDEBUG_SAY(("[BgfxBackend] Draw_Triangles first call (polys=%u verts=%u)", + polygon_count, vertex_count)); + } } void BgfxBackend::Draw_Triangles(unsigned int /*buffer_type*/, unsigned short /*start_index*/, - unsigned short /*polygon_count*/, + unsigned short polygon_count, unsigned short /*min_vertex_index*/, - unsigned short /*vertex_count*/) + unsigned short vertex_count) { + if (!g_loggedFirstDrawTriangles) + { + g_loggedFirstDrawTriangles = true; + WWDEBUG_SAY(("[BgfxBackend] Draw_Triangles first call typed (polys=%u verts=%u)", + polygon_count, vertex_count)); + } } void BgfxBackend::Draw_Strip(unsigned short /*start_index*/, diff --git a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt index dd74a30600a..89c85cd6080 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt @@ -258,6 +258,16 @@ if(GGC_RENDER_BACKEND STREQUAL "bgfx") # z_ww3d2 inherit the include directory and build order dependency. ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_passthrough.sc) ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_passthrough.sc) + # TheSuperHackers @refactor bobtista 11/04/2026 preset + # shader pairs - lit textured (bucket A), unlit textured (bucket B) + # and alpha-tested variant (bucket D). + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_textured_lit.sc) + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_textured_lit.sc) + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_textured_unlit.sc) + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_textured_unlit.sc) + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_textured_lit_atest.sc) + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_solid_lit.sc) + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_solid_lit.sc) target_link_libraries(corei_ww3d2 INTERFACE ggc_bgfx_shaders) endif() diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_solid_lit.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_solid_lit.sc new file mode 100644 index 00000000000..95d4d14d5df --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_solid_lit.sc @@ -0,0 +1,12 @@ +$input v_color0 + +// TheSuperHackers @refactor bobtista 11/04/2026 lit solid +// fragment shader. Outputs the interpolated vertex diffuse directly - +// equivalent to D3DTOP_SELECTARG1(D3DTA_DIFFUSE) with no texture stage. + +#include + +void main() +{ + gl_FragColor = v_color0; +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit.sc new file mode 100644 index 00000000000..b13a2fde3ec --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit.sc @@ -0,0 +1,17 @@ +$input v_color0, v_texcoord0 + +// TheSuperHackers @refactor bobtista 11/04/2026 lit textured +// fragment shader. Samples stage 0 and multiplies by the interpolated +// vertex diffuse. Equivalent to D3DTOP_MODULATE with +// (D3DTA_TEXTURE, D3DTA_DIFFUSE) which is the most common preset in the +// game. + +#include + +SAMPLER2D(s_tex0, 0); + +void main() +{ + vec4 texColor = texture2D(s_tex0, v_texcoord0); + gl_FragColor = texColor * v_color0; +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit_atest.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit_atest.sc new file mode 100644 index 00000000000..3aca275e252 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit_atest.sc @@ -0,0 +1,30 @@ +$input v_color0, v_texcoord0 + +// TheSuperHackers @refactor bobtista 11/04/2026 alpha-tested +// lit textured fragment shader. Same as fs_textured_lit but with a +// discard when the sampled alpha falls below u_atestParams.x. Used by +// the ATest* presets (trees, fences, see-through foliage). See +// +// u_atestParams: x = reference in [0,1]. The engine should write +// ShaderClass::Get_Alpha_Reference() normalized by 255 into .x. +// +// Note: deliberately NOT named u_alphaRef - bgfx_shader.sh has an +// internal u_alphaRef4 from its legacy state-based alpha test path, +// and shaderc rewrites uniforms ending in vec4 size, causing a +// "redefinition of u_alphaRef4" error in the generated HLSL. + +#include + +SAMPLER2D(s_tex0, 0); +uniform vec4 u_atestParams; + +void main() +{ + vec4 texColor = texture2D(s_tex0, v_texcoord0); + vec4 lit = texColor * v_color0; + if (lit.a < u_atestParams.x) + { + discard; + } + gl_FragColor = lit; +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_unlit.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_unlit.sc new file mode 100644 index 00000000000..8f38ad81bd4 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_unlit.sc @@ -0,0 +1,15 @@ +$input v_texcoord0 + +// TheSuperHackers @refactor bobtista 11/04/2026 unlit textured +// fragment shader. Samples stage 0 straight to output. Used by +// Opaque2D / OpaqueSprite and friends (GRADIENT_DISABLE + TEXTURING_ENABLE +// presets). + +#include + +SAMPLER2D(s_tex0, 0); + +void main() +{ + gl_FragColor = texture2D(s_tex0, v_texcoord0); +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_solid_lit.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_solid_lit.sc new file mode 100644 index 00000000000..de41b577745 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_solid_lit.sc @@ -0,0 +1,17 @@ +$input a_position, a_color0 +$output v_color0 + +// TheSuperHackers @refactor bobtista 11/04/2026 lit solid +// vertex shader. Used by the *Solid presets which have GRADIENT_MODULATE +// + TEXTURING_DISABLE - the engine writes the fixed-function lit color +// into the vertex diffuse and we just forward it. Identical to +// vs_passthrough but separated so the program lookup can stay 1:1 with +// the preset bucket. + +#include + +void main() +{ + gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0)); + v_color0 = a_color0; +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_textured_lit.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_textured_lit.sc new file mode 100644 index 00000000000..84585b861ae --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_textured_lit.sc @@ -0,0 +1,16 @@ +$input a_position, a_color0, a_texcoord0 +$output v_color0, v_texcoord0 + +// TheSuperHackers @refactor bobtista 11/04/2026 lit textured +// vertex shader. Transforms position by the bgfx model/view/projection +// matrix, forwards vertex diffuse (which carries the fixed-function lit +// color from the engine side) and texcoord 0. + +#include + +void main() +{ + gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0)); + v_color0 = a_color0; + v_texcoord0 = a_texcoord0; +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_textured_unlit.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_textured_unlit.sc new file mode 100644 index 00000000000..7973f6ac1bf --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_textured_unlit.sc @@ -0,0 +1,15 @@ +$input a_position, a_texcoord0 +$output v_texcoord0 + +// TheSuperHackers @refactor bobtista 11/04/2026 unlit textured +// vertex shader. Used by 2D/Sprite presets - no vertex lighting, just +// position transform and texcoord passthrough. +// shader bucket B. + +#include + +void main() +{ + gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0)); + v_texcoord0 = a_texcoord0; +} From 2b7524b6e52019e73e53d30ebc9648bd9c1ca7d4 Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 21 Apr 2026 14:02:46 -0400 Subject: [PATCH 045/523] feat(bgfx): capture engine VB/IB and submit real engine geometry through bgfx (cherry picked from commit f3e1b07deb20c14bd8c30497991ec16fe898f03d) --- .../Source/WWVegas/WW3D2/BgfxBackend.cpp | 611 +++++++++++++++++- .../Source/WWVegas/WW3D2/BgfxBackend.h | 7 + .../Source/WWVegas/WW3D2/IRenderBackend.h | 16 + .../Source/WWVegas/WW3D2/dx8indexbuffer.cpp | 14 + .../Source/WWVegas/WW3D2/dx8vertexbuffer.cpp | 23 + .../Libraries/Source/WWVegas/WW3D2/camera.cpp | 11 +- 6 files changed, 659 insertions(+), 23 deletions(-) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp index ff929c30efc..1b77d7de1ef 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -33,10 +33,18 @@ #include "dx8fvf.h" #include "dx8indexbuffer.h" #include "dx8vertexbuffer.h" +#include "dx8wrapper.h" +#include "matrix3d.h" +#include "matrix4.h" #include "shader.h" #include "vector3.h" #include "wwdebug.h" +#include +#include + +#include + // Including the bgfx header here is intentional: it forces a compile-time // dependency on the bgfx headers when GGC_RENDER_BACKEND=bgfx. If bgfx // isn't available the build fails here, which is the right place to @@ -120,6 +128,14 @@ bgfx::UniformHandle g_sTex0 = BGFX_INVALID_HANDLE; // u_alphaRef4 conflict. See fs_textured_lit_atest.sc for details. bgfx::UniformHandle g_uAtestParams = BGFX_INVALID_HANDLE; +// TheSuperHackers @refactor bobtista 11/04/2026 default 1x1 +// white texture. Real Set_Texture wiring is not in place yet, so the +// textured shaders need SOMETHING bound to s_tex0 or D3D11 returns +// undefined values. A 1x1 opaque white texture is a sensible default +// because the fragment shader does texColor * v_color0 - white * vc +// gives the vertex color through unmodified. +bgfx::TextureHandle g_defaultWhiteTexture = BGFX_INVALID_HANDLE; + // Vertex layout used by the test triangle. Position + packed RGBA color. // Initialized in Initialize since bgfx::VertexLayout::begin needs bgfx to be // up and running (it queries the active renderer for the pos type). @@ -503,6 +519,246 @@ bool BuildBgfxLayoutForFVF(const FVFInfoClass & fvf, bgfx::VertexLayout & out) [[maybe_unused]] const auto kFvfLayoutAnchor = &BuildBgfxLayoutForFVF; +// TheSuperHackers @refactor bobtista 11/04/2026 vertex/index +// buffer caches. The engine recycles VertexBufferClass / IndexBufferClass +// instances throughout its lifetime, so we cache the bgfx handle keyed +// by the source pointer. On first encounter we lock the source buffer +// for read, copy bytes via bgfx::copy(), and create a bgfx static buffer. +// Cache is destroyed wholesale in Shutdown - we don't try to react to +// VertexBufferClass destruction yet, which means handles may outlive +// their source until shutdown. That's fine for the prototype. +// +// Pointer reuse is theoretically possible but unlikely in a single +// session; if it bites us we'll add a generation counter or hook the +// destructor. + +std::unordered_map g_vbCache; +std::unordered_map g_ibCache; + +// The most recent buffers and offsets cached from Set_Vertex_Buffer / +// Set_Index_Buffer. Read by Draw_Triangles when it issues the bgfx +// submit. Cleared (made invalid) on Shutdown. +bgfx::VertexBufferHandle g_currentBgfxVB = BGFX_INVALID_HANDLE; +bgfx::IndexBufferHandle g_currentBgfxIB = BGFX_INVALID_HANDLE; +unsigned short g_currentIBOffset = 0; + +bool g_loggedFirstBgfxVbCreate = false; +bool g_loggedFirstBgfxIbCreate = false; +bool g_loggedFirstBgfxSubmit = false; + +// TheSuperHackers @refactor bobtista 11/04/2026 transform +// capture. The engine calls Set_Transform with world / view / projection +// matrices in W3D row-major form (Vector4 Row[4]). bgfx wants column- +// major float[16] for setViewTransform / setTransform. We convert with +// a transpose copy. Whether the resulting matrices produce visually +// correct geometry depends on multiplication order conventions; if +// things look mirrored or upside-down, the row-vector vs column-vector +// convention is the next thing to investigate. +// +// We capture all three matrices and apply them per-submit. View and +// projection are written via setViewTransform on view 1; world is set +// per-submit via setTransform. +float g_bgfxWorld[16]; +float g_bgfxView[16]; +float g_bgfxProj[16]; +bool g_bgfxViewProjDirty = true; +bool g_loggedFirstSetView = false; +bool g_loggedFirstSetProj = false; +bool g_loggedFirstSetWorld = false; + +// Engine geometry submits to its own view so it does not collide with +// the test triangle on view 0. View 0 keeps the test triangle for the +// "is bgfx alive" sentinel; view 1 is engine geometry under engine +// transforms. Both render to the popup back buffer. +const bgfx::ViewId kBgfxEngineView = 1; + +void IdentityMatrix(float * out) +{ + out[0] = 1.0f; out[1] = 0.0f; out[2] = 0.0f; out[3] = 0.0f; + out[4] = 0.0f; out[5] = 1.0f; out[6] = 0.0f; out[7] = 0.0f; + out[8] = 0.0f; out[9] = 0.0f; out[10] = 1.0f; out[11] = 0.0f; + out[12] = 0.0f; out[13] = 0.0f; out[14] = 0.0f; out[15] = 1.0f; +} + +// W3D Matrix4x4 stores Vector4 Row[4] in row-major order. bgfx wants +// column-major float[16]. Transpose-copy. +void W3DMatrix4ToBgfx(const Matrix4x4 & m, float * out) +{ + out[0] = m[0][0]; out[4] = m[0][1]; out[8] = m[0][2]; out[12] = m[0][3]; + out[1] = m[1][0]; out[5] = m[1][1]; out[9] = m[1][2]; out[13] = m[1][3]; + out[2] = m[2][0]; out[6] = m[2][1]; out[10] = m[2][2]; out[14] = m[2][3]; + out[3] = m[3][0]; out[7] = m[3][1]; out[11] = m[3][2]; out[15] = m[3][3]; +} + +// W3D Matrix3D stores Vector4 Row[3] - the bottom row is implicitly +// (0,0,0,1). Same transpose convention as Matrix4x4 but the missing +// row needs to be filled in. +void W3DMatrix3DToBgfx(const Matrix3D & m, float * out) +{ + out[0] = m[0][0]; out[4] = m[0][1]; out[8] = m[0][2]; out[12] = m[0][3]; + out[1] = m[1][0]; out[5] = m[1][1]; out[9] = m[1][2]; out[13] = m[1][3]; + out[2] = m[2][0]; out[6] = m[2][1]; out[10] = m[2][2]; out[14] = m[2][3]; + out[3] = 0.0f; out[7] = 0.0f; out[11] = 0.0f; out[15] = 1.0f; +} + +// TheSuperHackers @bugfix bobtista 11/04/2026 buffer copy +// kill switch. The Intel UHD Graphics driver corrupts the dx8 GPU +// copy of POOL_DEFAULT vertex buffers even when we lock with +// D3DLOCK_READONLY. The read-side path is fully disabled; bgfx VBs +// are now populated by the write-side capture hooks +// (Capture_Vertex_Data / Capture_Index_Data) called from the engine +// at write-lock time. See the write-side capture comments below. +constexpr bool kBgfxSkipBufferRead = true; + +bgfx::VertexBufferHandle EnsureBgfxVertexBuffer(const VertexBufferClass * vb) +{ + if (vb == nullptr) + { + return BGFX_INVALID_HANDLE; + } + auto it = g_vbCache.find(vb); + if (it != g_vbCache.end()) + { + return it->second; + } + + if (kBgfxSkipBufferRead) + { + g_vbCache[vb] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + if (vb->Type() != BUFFER_TYPE_DX8) + { + // Sorting / dynamic buffers handled by a different path; mark + // as cached-invalid so we do not retry every draw. + g_vbCache[vb] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + bgfx::VertexLayout layout; + if (!BuildBgfxLayoutForFVF(vb->FVF_Info(), layout)) + { + WWDEBUG_SAY(("[BgfxBackend] EnsureBgfxVertexBuffer: layout build failed " + "for fvf=0x%08x", vb->FVF_Info().Get_FVF())); + g_vbCache[vb] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + const unsigned vertexCount = vb->Get_Vertex_Count(); + const unsigned strideBytes = vb->FVF_Info().Get_FVF_Size(); + const unsigned totalBytes = vertexCount * strideBytes; + if (totalBytes == 0) + { + g_vbCache[vb] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + DX8VertexBufferClass * dxVb = + const_cast(static_cast(vb)); + IDirect3DVertexBuffer8 * d3dVb = dxVb->Get_DX8_Vertex_Buffer(); + if (d3dVb == nullptr) + { + g_vbCache[vb] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + BYTE * srcData = nullptr; + HRESULT hr = d3dVb->Lock(0, 0, &srcData, D3DLOCK_READONLY); + if (FAILED(hr) || srcData == nullptr) + { + WWDEBUG_SAY(("[BgfxBackend] EnsureBgfxVertexBuffer: d3d8 Lock failed hr=0x%08x", + static_cast(hr))); + g_vbCache[vb] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + const bgfx::Memory * mem = bgfx::copy(srcData, totalBytes); + d3dVb->Unlock(); + + bgfx::VertexBufferHandle h = bgfx::createVertexBuffer(mem, layout); + g_vbCache[vb] = h; + + if (!g_loggedFirstBgfxVbCreate) + { + g_loggedFirstBgfxVbCreate = true; + WWDEBUG_SAY(("[BgfxBackend] EnsureBgfxVertexBuffer first create " + "fvf=0x%08x stride=%u verts=%u bytes=%u handle=%s", + vb->FVF_Info().Get_FVF(), + strideBytes, vertexCount, totalBytes, + bgfx::isValid(h) ? "ok" : "FAILED")); + } + return h; +} + +bgfx::IndexBufferHandle EnsureBgfxIndexBuffer(const IndexBufferClass * ib) +{ + if (ib == nullptr) + { + return BGFX_INVALID_HANDLE; + } + auto it = g_ibCache.find(ib); + if (it != g_ibCache.end()) + { + return it->second; + } + + if (kBgfxSkipBufferRead) + { + g_ibCache[ib] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + if (ib->Type() != BUFFER_TYPE_DX8) + { + g_ibCache[ib] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + const unsigned indexCount = ib->Get_Index_Count(); + const unsigned totalBytes = indexCount * sizeof(uint16_t); + if (totalBytes == 0) + { + g_ibCache[ib] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + DX8IndexBufferClass * dxIb = + const_cast(static_cast(ib)); + IDirect3DIndexBuffer8 * d3dIb = dxIb->Get_DX8_Index_Buffer(); + if (d3dIb == nullptr) + { + g_ibCache[ib] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + BYTE * srcData = nullptr; + HRESULT hr = d3dIb->Lock(0, 0, &srcData, D3DLOCK_READONLY); + if (FAILED(hr) || srcData == nullptr) + { + WWDEBUG_SAY(("[BgfxBackend] EnsureBgfxIndexBuffer: d3d8 Lock failed hr=0x%08x", + static_cast(hr))); + g_ibCache[ib] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + const bgfx::Memory * mem = bgfx::copy(srcData, totalBytes); + d3dIb->Unlock(); + + bgfx::IndexBufferHandle h = bgfx::createIndexBuffer(mem); + g_ibCache[ib] = h; + + if (!g_loggedFirstBgfxIbCreate) + { + g_loggedFirstBgfxIbCreate = true; + WWDEBUG_SAY(("[BgfxBackend] EnsureBgfxIndexBuffer first create " + "indices=%u bytes=%u handle=%s", + indexCount, totalBytes, + bgfx::isValid(h) ? "ok" : "FAILED")); + } + return h; +} + // TheSuperHackers @refactor bobtista 11/04/2026 Set_Shader // telemetry. The bgfx backend caches the picked program and state bits // for the most recent ShaderClass it received, and tracks the unique @@ -640,7 +896,8 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) g_bgfxInitialized = true; // Configure view 0 to clear the debug window to a dark teal so it's - // visually obvious bgfx is running and alive. + // visually obvious bgfx is running and alive. View 0 holds the test + // triangle (bring-up sentinel). bgfx::setViewClear(0, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x1a3b5cff, // dark teal, 0xRRGGBBAA @@ -650,6 +907,28 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) static_cast(kBgfxWindowWidth), static_cast(kBgfxWindowHeight)); + // View 1 is the engine geometry view. Same render target, but its + // own clear/depth and (eventually) its own view+projection matrices + // captured from the engine's Set_Transform calls. Drawn after view 0 + // so engine geometry overlays the test triangle. + bgfx::setViewClear(kBgfxEngineView, + BGFX_CLEAR_DEPTH, + 0x00000000, + 1.0f, + 0); + bgfx::setViewRect(kBgfxEngineView, 0, 0, + static_cast(kBgfxWindowWidth), + static_cast(kBgfxWindowHeight)); + + // Default the cached transforms to identity until the engine writes + // real values via Set_Transform. This keeps the first few engine + // submits well-defined even if they fire before any matrices are + // captured. + IdentityMatrix(g_bgfxWorld); + IdentityMatrix(g_bgfxView); + IdentityMatrix(g_bgfxProj); + g_bgfxViewProjDirty = true; + // TheSuperHackers @refactor bobtista 11/04/2026 create the // passthrough shader program and vertex layout so End_Scene can submit // a test triangle. If shader creation fails the backend still runs but @@ -687,6 +966,14 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) g_sTex0 = bgfx::createUniform("s_tex0", bgfx::UniformType::Sampler); g_uAtestParams = bgfx::createUniform("u_atestParams", bgfx::UniformType::Vec4); + // default 1x1 white texture (opaque ABGR=0xffffffff). + static const uint8_t kWhitePixel[4] = { 0xff, 0xff, 0xff, 0xff }; + g_defaultWhiteTexture = bgfx::createTexture2D( + 1, 1, false, 1, + bgfx::TextureFormat::RGBA8, + BGFX_TEXTURE_NONE | BGFX_SAMPLER_POINT, + bgfx::copy(kWhitePixel, sizeof(kWhitePixel))); + const bgfx::Memory * vsLitMem = bgfx::makeRef(vs_textured_lit_dx11, sizeof(vs_textured_lit_dx11)); const bgfx::Memory * fsLitMem = bgfx::makeRef(fs_textured_lit_dx11, @@ -806,6 +1093,32 @@ void BgfxBackend::Shutdown() bgfx::destroy(g_uAtestParams); g_uAtestParams = BGFX_INVALID_HANDLE; } + if (bgfx::isValid(g_defaultWhiteTexture)) + { + bgfx::destroy(g_defaultWhiteTexture); + g_defaultWhiteTexture = BGFX_INVALID_HANDLE; + } + // cached engine buffers. Destroy before bgfx::shutdown + // so the handles outlive nothing. + for (auto & kv : g_vbCache) + { + if (bgfx::isValid(kv.second)) + { + bgfx::destroy(kv.second); + } + } + g_vbCache.clear(); + for (auto & kv : g_ibCache) + { + if (bgfx::isValid(kv.second)) + { + bgfx::destroy(kv.second); + } + } + g_ibCache.clear(); + g_currentBgfxVB = BGFX_INVALID_HANDLE; + g_currentBgfxIB = BGFX_INVALID_HANDLE; + g_currentIBOffset = 0; bgfx::shutdown(); g_bgfxInitialized = false; WWDEBUG_SAY(("[BgfxBackend] bgfx::shutdown complete.")); @@ -944,18 +1257,20 @@ void BgfxBackend::Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int / fvf.Get_FVF_Size(), vb->Get_Vertex_Count(), vb->Type())); - - // Verification: build a bgfx layout from this FVF and - // log whether the resulting stride matches the source. If it does - // we can confidently start creating real bgfx vertex buffers from - // VertexBufferClass instances in a follow-up commit. - bgfx::VertexLayout layout; - const bool ok = BuildBgfxLayoutForFVF(fvf, layout); - const unsigned want = fvf.Get_FVF_Size(); - const unsigned got = layout.getStride(); - WWDEBUG_SAY(("[BgfxBackend] FVF -> bgfx layout %s " - "(stride want=%u got=%u)", - ok ? "OK" : "MISMATCH", want, got)); + } + // The cache is populated by Capture_Vertex_Data on the engine's + // own write lock. Set_Vertex_Buffer just looks up whatever is already + // there. Engine VBs that have not been written via the WriteLockClass + // path yet (e.g. those filled by raw d3d8 calls) will miss the cache + // and the bgfx submit will be skipped. + auto it = g_vbCache.find(vb); + if (it != g_vbCache.end()) + { + g_currentBgfxVB = it->second; + } + else + { + g_currentBgfxVB = BGFX_INVALID_HANDLE; } } @@ -974,7 +1289,7 @@ void BgfxBackend::Set_Vertex_Buffer(const DynamicVBAccessClass & vba) } } -void BgfxBackend::Set_Index_Buffer(const IndexBufferClass * ib, unsigned short /*index_base_offset*/) +void BgfxBackend::Set_Index_Buffer(const IndexBufferClass * ib, unsigned short index_base_offset) { if (!g_loggedFirstSetIndexBuffer && ib != nullptr) { @@ -984,6 +1299,16 @@ void BgfxBackend::Set_Index_Buffer(const IndexBufferClass * ib, unsigned short / ib->Get_Index_Count(), ib->Type())); } + auto it = g_ibCache.find(ib); + if (it != g_ibCache.end()) + { + g_currentBgfxIB = it->second; + } + else + { + g_currentBgfxIB = BGFX_INVALID_HANDLE; + } + g_currentIBOffset = index_base_offset; } void BgfxBackend::Set_Index_Buffer(const DynamicIBAccessClass & iba, unsigned short /*index_base_offset*/) @@ -1002,6 +1327,92 @@ void BgfxBackend::Set_Index_Buffer_Index_Offset(unsigned int /*offset*/) { } +// -- write-side capture ------------------------------------------- +// +// Called from VertexBufferClass::WriteLockClass / IndexBufferClass::WriteLockClass +// destructors after the engine has finished writing data through the +// CPU-mapped lock pointer. The pointer is still valid (Unlock has not yet +// been called) so we can safely copy the bytes into bgfx-managed memory and +// stamp out a static bgfx VB/IB. Cached by source pointer; reused on every +// subsequent Set_Vertex_Buffer that references the same engine VB. +// +// Cleanly bypasses the Intel UHD POOL_DEFAULT lock corruption: we never lock +// the source d3d8 buffer ourselves. We piggyback on the engine's own write +// lock, which the engine has to do anyway and which the driver handles +// correctly because it is a real WRITE lock. + +void BgfxBackend::Capture_Vertex_Data(const VertexBufferClass * vb, + const void * data, + unsigned int size_bytes) +{ + if (!g_bgfxInitialized || vb == nullptr || data == nullptr || size_bytes == 0) + { + return; + } + + bgfx::VertexLayout layout; + if (!BuildBgfxLayoutForFVF(vb->FVF_Info(), layout)) + { + return; + } + + // Replace any prior cached handle for the same VB pointer. The engine + // can rewrite a VB's contents (e.g. dynamic terrain or animated meshes + // that go through the same VertexBufferClass instance over its lifetime), + // so the new write supersedes whatever bgfx had. + auto it = g_vbCache.find(vb); + if (it != g_vbCache.end() && bgfx::isValid(it->second)) + { + bgfx::destroy(it->second); + } + + const bgfx::Memory * mem = bgfx::copy(data, size_bytes); + bgfx::VertexBufferHandle h = bgfx::createVertexBuffer(mem, layout); + g_vbCache[vb] = h; + + if (!g_loggedFirstBgfxVbCreate) + { + g_loggedFirstBgfxVbCreate = true; + WWDEBUG_SAY(("[BgfxBackend] Capture_Vertex_Data first create " + "fvf=0x%08x stride=%u verts=%u bytes=%u handle=%s", + vb->FVF_Info().Get_FVF(), + vb->FVF_Info().Get_FVF_Size(), + vb->Get_Vertex_Count(), + size_bytes, + bgfx::isValid(h) ? "ok" : "FAILED")); + } +} + +void BgfxBackend::Capture_Index_Data(const IndexBufferClass * ib, + const void * data, + unsigned int size_bytes) +{ + if (!g_bgfxInitialized || ib == nullptr || data == nullptr || size_bytes == 0) + { + return; + } + + auto it = g_ibCache.find(ib); + if (it != g_ibCache.end() && bgfx::isValid(it->second)) + { + bgfx::destroy(it->second); + } + + const bgfx::Memory * mem = bgfx::copy(data, size_bytes); + bgfx::IndexBufferHandle h = bgfx::createIndexBuffer(mem); + g_ibCache[ib] = h; + + if (!g_loggedFirstBgfxIbCreate) + { + g_loggedFirstBgfxIbCreate = true; + WWDEBUG_SAY(("[BgfxBackend] Capture_Index_Data first create " + "indices=%u bytes=%u handle=%s", + ib->Get_Index_Count(), + size_bytes, + bgfx::isValid(h) ? "ok" : "FAILED")); + } +} + // -- State: shaders, materials, textures ------------------------------------ void BgfxBackend::Set_Shader(const ShaderClass & shader) @@ -1140,12 +1551,75 @@ void BgfxBackend::Set_Stencil_ZFail_Op(StencilOp /*op*/) // -- Transforms -------------------------------------------------------------- -void BgfxBackend::Set_Transform(TransformKind /*transform*/, const Matrix4x4 & /*m*/) +void BgfxBackend::Set_Transform(TransformKind transform, const Matrix4x4 & m) { + switch (transform) + { + case RB_TRANSFORM_WORLD: + W3DMatrix4ToBgfx(m, g_bgfxWorld); + if (!g_loggedFirstSetWorld) + { + g_loggedFirstSetWorld = true; + WWDEBUG_SAY(("[BgfxBackend] Set_Transform first WORLD (Matrix4x4) " + "row0=%.3f,%.3f,%.3f,%.3f", + m[0][0], m[0][1], m[0][2], m[0][3])); + } + break; + case RB_TRANSFORM_VIEW: + W3DMatrix4ToBgfx(m, g_bgfxView); + g_bgfxViewProjDirty = true; + if (!g_loggedFirstSetView) + { + g_loggedFirstSetView = true; + WWDEBUG_SAY(("[BgfxBackend] Set_Transform first VIEW (Matrix4x4) " + "row0=%.3f,%.3f,%.3f,%.3f", + m[0][0], m[0][1], m[0][2], m[0][3])); + } + break; + case RB_TRANSFORM_PROJECTION: + W3DMatrix4ToBgfx(m, g_bgfxProj); + g_bgfxViewProjDirty = true; + if (!g_loggedFirstSetProj) + { + g_loggedFirstSetProj = true; + WWDEBUG_SAY(("[BgfxBackend] Set_Transform first PROJ (Matrix4x4) " + "row0=%.3f,%.3f,%.3f,%.3f", + m[0][0], m[0][1], m[0][2], m[0][3])); + } + break; + default: + break; + } } -void BgfxBackend::Set_Transform(TransformKind /*transform*/, const Matrix3D & /*m*/) +void BgfxBackend::Set_Transform(TransformKind transform, const Matrix3D & m) { + switch (transform) + { + case RB_TRANSFORM_WORLD: + W3DMatrix3DToBgfx(m, g_bgfxWorld); + if (!g_loggedFirstSetWorld) + { + g_loggedFirstSetWorld = true; + WWDEBUG_SAY(("[BgfxBackend] Set_Transform first WORLD (Matrix3D) " + "row0=%.3f,%.3f,%.3f,%.3f", + m[0][0], m[0][1], m[0][2], m[0][3])); + } + break; + case RB_TRANSFORM_VIEW: + W3DMatrix3DToBgfx(m, g_bgfxView); + g_bgfxViewProjDirty = true; + if (!g_loggedFirstSetView) + { + g_loggedFirstSetView = true; + WWDEBUG_SAY(("[BgfxBackend] Set_Transform first VIEW (Matrix3D) " + "row0=%.3f,%.3f,%.3f,%.3f", + m[0][0], m[0][1], m[0][2], m[0][3])); + } + break; + default: + break; + } } void BgfxBackend::Get_Transform(TransformKind /*transform*/, Matrix4x4 & /*m*/) @@ -1154,10 +1628,13 @@ void BgfxBackend::Get_Transform(TransformKind /*transform*/, Matrix4x4 & /*m*/) void BgfxBackend::Set_World_Identity() { + IdentityMatrix(g_bgfxWorld); } void BgfxBackend::Set_View_Identity() { + IdentityMatrix(g_bgfxView); + g_bgfxViewProjDirty = true; } bool BgfxBackend::Is_World_Identity() @@ -1170,9 +1647,18 @@ bool BgfxBackend::Is_View_Identity() return true; } -void BgfxBackend::Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & /*matrix*/, +void BgfxBackend::Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & matrix, float /*znear*/, float /*zfar*/) { + W3DMatrix4ToBgfx(matrix, g_bgfxProj); + g_bgfxViewProjDirty = true; + if (!g_loggedFirstSetProj) + { + g_loggedFirstSetProj = true; + WWDEBUG_SAY(("[BgfxBackend] Set_Projection_Transform_With_Z_Bias first call " + "row0=%.3f,%.3f,%.3f,%.3f", + matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3])); + } } // -- Lighting and fog -------------------------------------------------------- @@ -1211,9 +1697,90 @@ LightEnvironmentClass * BgfxBackend::Get_Light_Environment() const // -- Draw calls -------------------------------------------------------------- -void BgfxBackend::Draw_Triangles(unsigned short /*start_index*/, +namespace +{ +// TheSuperHackers @refactor bobtista 11/04/2026 actual bgfx +// submit. Called from both Draw_Triangles overloads when we have a +// valid cached VB + IB + program. State and program were cached by +// Set_Shader; the buffers were cached by Set_Vertex_Buffer / +// Set_Index_Buffer. Submits to view 0, the same view the test +// triangle uses, so the popup will show both overlapping until we +// move engine geometry to its own view. +// +// View transforms remain identity. Engine geometry is in world +// coordinates which will be far off-screen under an identity +// projection - so this draws "nothing visible" except as a way to +// prove the submit pipeline works without crashing. Real +// view/projection wiring lands in the next session. +void SubmitEngineDraw(unsigned short start_index, + unsigned short polygon_count, + unsigned short min_vertex_index, + unsigned short vertex_count) +{ + if (!g_bgfxInitialized) + { + return; + } + if (!bgfx::isValid(g_currentBgfxVB) || + !bgfx::isValid(g_currentBgfxIB) || + !bgfx::isValid(g_currentBgfxProgram)) + { + return; + } + + // Push the engine view+projection when they change. setViewTransform + // applies until the next change so we do not need to call it per + // submit, only when the engine has updated either matrix. + if (g_bgfxViewProjDirty) + { + bgfx::setViewTransform(kBgfxEngineView, g_bgfxView, g_bgfxProj); + g_bgfxViewProjDirty = false; + } + + // World matrix is per-submit. bgfx consumes the value at submit time + // and resets the per-draw transform after each submit. + bgfx::setTransform(g_bgfxWorld); + + bgfx::setVertexBuffer(0, g_currentBgfxVB, min_vertex_index, vertex_count); + bgfx::setIndexBuffer(g_currentBgfxIB, + static_cast(g_currentIBOffset) + start_index, + static_cast(polygon_count) * 3); + + // Bind the default 1x1 white texture to stage 0. Real Set_Texture + // wiring is not in place yet, so this is a placeholder so the + // textured shaders have a valid sampler. The shader does + // texColor * v_color0 so white-on-vertex-color renders the vertex + // color through unmodified. + if (bgfx::isValid(g_defaultWhiteTexture) && bgfx::isValid(g_sTex0)) + { + bgfx::setTexture(0, g_sTex0, g_defaultWhiteTexture); + } + + // Use the cached state if it was built; otherwise fall back to a + // sane default that at least lets the draw run. + const uint64_t state = (g_currentBgfxState != 0) + ? g_currentBgfxState + : (BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); + bgfx::setState(state); + + bgfx::submit(kBgfxEngineView, g_currentBgfxProgram); + + if (!g_loggedFirstBgfxSubmit) + { + g_loggedFirstBgfxSubmit = true; + WWDEBUG_SAY(("[BgfxBackend] First engine bgfx::submit " + "(view=%u start=%u polys=%u min_v=%u count=%u state=0x%016llx)", + static_cast(kBgfxEngineView), + start_index, polygon_count, + min_vertex_index, vertex_count, + static_cast(state))); + } +} +} + +void BgfxBackend::Draw_Triangles(unsigned short start_index, unsigned short polygon_count, - unsigned short /*min_vertex_index*/, + unsigned short min_vertex_index, unsigned short vertex_count) { if (!g_loggedFirstDrawTriangles) @@ -1222,12 +1789,13 @@ void BgfxBackend::Draw_Triangles(unsigned short /*start_index*/, WWDEBUG_SAY(("[BgfxBackend] Draw_Triangles first call (polys=%u verts=%u)", polygon_count, vertex_count)); } + SubmitEngineDraw(start_index, polygon_count, min_vertex_index, vertex_count); } void BgfxBackend::Draw_Triangles(unsigned int /*buffer_type*/, - unsigned short /*start_index*/, + unsigned short start_index, unsigned short polygon_count, - unsigned short /*min_vertex_index*/, + unsigned short min_vertex_index, unsigned short vertex_count) { if (!g_loggedFirstDrawTriangles) @@ -1236,6 +1804,7 @@ void BgfxBackend::Draw_Triangles(unsigned int /*buffer_type*/, WWDEBUG_SAY(("[BgfxBackend] Draw_Triangles first call typed (polys=%u verts=%u)", polygon_count, vertex_count)); } + SubmitEngineDraw(start_index, polygon_count, min_vertex_index, vertex_count); } void BgfxBackend::Draw_Strip(unsigned short /*start_index*/, diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h index 13bdbaf9396..90d1687ef5d 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h @@ -68,6 +68,13 @@ class BgfxBackend : public IRenderBackend virtual void Set_Index_Buffer(const DynamicIBAccessClass & iba, unsigned short index_base_offset); virtual void Set_Index_Buffer_Index_Offset(unsigned int offset); + virtual void Capture_Vertex_Data(const VertexBufferClass * vb, + const void * data, + unsigned int size_bytes); + virtual void Capture_Index_Data(const IndexBufferClass * ib, + const void * data, + unsigned int size_bytes); + // -- State: shaders, materials, textures --------------------------------- virtual void Set_Shader(const ShaderClass & shader); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h index e463b891af8..f9db4b28578 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h @@ -222,6 +222,22 @@ class IRenderBackend virtual void Set_Index_Buffer(const DynamicIBAccessClass & iba, unsigned short index_base_offset) = 0; virtual void Set_Index_Buffer_Index_Offset(unsigned int offset) = 0; + // TheSuperHackers @refactor bobtista 11/04/2026 write-side + // capture hooks. The W3D engine writes vertex/index data through + // VertexBufferClass::WriteLockClass / IndexBufferClass::WriteLockClass + // (and the various Copy() helpers). At unlock time the data is sitting + // in a CPU-mapped pointer that the engine just wrote into - that is + // the safe moment for the bgfx backend to grab a copy and create its + // own GPU buffer. The DX8 backend ignores these calls; only BgfxBackend + // uses them. Default empty implementations so existing call sites that + // do not need them are not forced to override. + virtual void Capture_Vertex_Data(const VertexBufferClass * /*vb*/, + const void * /*data*/, + unsigned int /*size_bytes*/) {} + virtual void Capture_Index_Data(const IndexBufferClass * /*ib*/, + const void * /*data*/, + unsigned int /*size_bytes*/) {} + // ------------------------------------------------------------------------- // State: shaders, materials, textures // ------------------------------------------------------------------------- diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.cpp index 578c6b82aec..204bd7464d8 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.cpp @@ -45,6 +45,12 @@ #include "WWLib/thread.h" #include "WWDebug/wwmemlog.h" +// TheSuperHackers @refactor bobtista 11/04/2026 capture index +// data into the active render backend at write-lock time. See the +// matching comment in dx8vertexbuffer.cpp. +#include "RenderBackend.h" +#include "IRenderBackend.h" + #define DEFAULT_IB_SIZE 5000 static bool _DynamicSortingIndexArrayInUse=false; @@ -214,6 +220,14 @@ IndexBufferClass::WriteLockClass::WriteLockClass(IndexBufferClass* index_buffer_ IndexBufferClass::WriteLockClass::~WriteLockClass() { DX8_THREAD_ASSERT(); + // TheSuperHackers @refactor bobtista 11/04/2026 capture + // index data into the active render backend BEFORE unlocking. After + // Unlock the source pointer is invalid. The bgfx backend snapshots + // the bytes; the dx8 backend ignores the call. + if (g_renderBackend != NULL && indices != NULL && index_buffer->Type() == BUFFER_TYPE_DX8) { + const unsigned int total_bytes = index_buffer->Get_Index_Count() * sizeof(unsigned short); + g_renderBackend->Capture_Index_Data(index_buffer, indices, total_bytes); + } switch (index_buffer->Type()) { case BUFFER_TYPE_DX8: DX8_Assert(); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp index a6551553239..09499c29e90 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp @@ -47,6 +47,14 @@ #include "WWDebug/wwmemlog.h" #include +// TheSuperHackers @refactor bobtista 11/04/2026 capture vertex +// data into the active render backend at write-lock time. The bgfx backend +// uses this to populate its own bgfx vertex buffer cache without having +// to lock the source d3d8 buffer (which corrupts POOL_DEFAULT VBs on +// some Intel UHD drivers). DX8 backend ignores the call. +#include "RenderBackend.h" +#include "IRenderBackend.h" + #define DEFAULT_VB_SIZE 5000 static bool _DynamicSortingVertexArrayInUse=false; @@ -194,6 +202,15 @@ VertexBufferClass::WriteLockClass::WriteLockClass(VertexBufferClass* VertexBuffe VertexBufferClass::WriteLockClass::~WriteLockClass() { DX8_THREAD_ASSERT(); + // TheSuperHackers @refactor bobtista 11/04/2026 capture + // vertex data into the active render backend BEFORE unlocking. After + // Unlock the source pointer becomes invalid, so this is the last + // safe moment. The bgfx backend snapshots the bytes; the dx8 backend + // ignores the call. + if (g_renderBackend != NULL && Vertices != NULL && VertexBuffer->Type() == BUFFER_TYPE_DX8) { + const unsigned int total_bytes = VertexBuffer->Get_Vertex_Count() * VertexBuffer->FVF_Info().Get_FVF_Size(); + g_renderBackend->Capture_Vertex_Data(VertexBuffer, Vertices, total_bytes); + } switch (VertexBuffer->Type()) { case BUFFER_TYPE_DX8: #ifdef VERTEX_BUFFER_LOG @@ -260,6 +277,12 @@ VertexBufferClass::AppendLockClass::AppendLockClass(VertexBufferClass* VertexBuf VertexBufferClass::AppendLockClass::~AppendLockClass() { DX8_THREAD_ASSERT(); + // TheSuperHackers @refactor bobtista 11/04/2026 append locks + // are intentionally NOT captured for the bgfx backend. The Vertices + // pointer here is offset to a sub-range, not the start of the VB, so we + // cannot do a "snapshot the whole buffer" copy from it. Append locks + // are rare in the static rendering path; if a buffer is exclusively + // updated via append locks the bgfx backend will simply skip drawing it. switch (VertexBuffer->Type()) { case BUFFER_TYPE_DX8: DX8_Assert(); diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp index a42e7e4fe43..891c8a9acc8 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp @@ -76,6 +76,13 @@ #include "WWMath/matrix4.h" #include "WW3D2/dx8wrapper.h" +// TheSuperHackers @refactor bobtista 11/04/2026 Route the +// camera's view + projection matrices through the active render backend +// instead of straight to DX8Wrapper, so the bgfx backend can capture +// them. The dx8 backend forwards to DX8Wrapper unchanged. +#include "RenderBackend.h" +#include "IRenderBackend.h" + /*********************************************************************************************** * CameraClass::CameraClass -- constructor * @@ -749,8 +756,8 @@ void CameraClass::Apply() Matrix4x4 d3dprojection; Get_D3D_Projection_Matrix(&d3dprojection); - DX8Wrapper::Set_Projection_Transform_With_Z_Bias(d3dprojection,ZNear,ZFar); - DX8Wrapper::Set_Transform(D3DTS_VIEW,CameraInvTransform); + g_renderBackend->Set_Projection_Transform_With_Z_Bias(d3dprojection,ZNear,ZFar); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW,CameraInvTransform); } void CameraClass::Set_Clip_Planes(float znear,float zfar) From d1e84b9ebf8d4c685819042e91d9426951c54a6a Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 21 Apr 2026 14:02:46 -0400 Subject: [PATCH 046/523] feat(bgfx): texture cache with multi-stage binding and W3DShaderManager terrain routing (cherry picked from commit 5b049c9999cc60e7ec47b935b393bd7904c285d8) --- .../Source/WWVegas/WW3D2/BgfxBackend.cpp | 803 ++++++++++++------ .../Source/WWVegas/WW3D2/BgfxBackend.h | 138 ++- .../Source/WWVegas/WW3D2/IRenderBackend.h | 14 + .../Source/WWVegas/WW3D2/dx8indexbuffer.cpp | 7 + .../Source/WWVegas/WW3D2/dx8vertexbuffer.cpp | 9 + .../WWVegas/WW3D2/shaders/fs_textured_lit.sc | 19 +- .../WW3D2/shaders/fs_textured_lit_atest.sc | 12 +- .../WW3D2/shaders/fs_textured_unlit.sc | 15 +- 8 files changed, 644 insertions(+), 373 deletions(-) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp index 1b77d7de1ef..bd79e75d7da 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -37,7 +37,9 @@ #include "matrix3d.h" #include "matrix4.h" #include "shader.h" +#include "texture.h" #include "vector3.h" +#include "ww3dformat.h" #include "wwdebug.h" #include @@ -88,10 +90,6 @@ namespace // is misconfigured. [[maybe_unused]] const auto kBgfxLinkAnchor = &bgfx::getCaps; -// Shared static vector returned from Get_Ambient(). Later work will replace -// this with real per-frame ambient tracking. -const Vector3 kZeroVec3(0.0f, 0.0f, 0.0f); - // TheSuperHackers @refactor bobtista 11/04/2026 Tracks // whether bgfx::init has been called successfully, so Begin_Scene / // End_Scene / Shutdown can skip bgfx calls if init was never reached. @@ -103,8 +101,12 @@ bool g_bgfxInitialized = false; HWND g_bgfxWindow = nullptr; const wchar_t * const kBgfxWindowClass = L"GGC_BgfxDebugWindow"; -const int kBgfxWindowWidth = 512; -const int kBgfxWindowHeight = 384; +// TheSuperHackers @refactor bobtista 11/04/2026 popup +// dimensions chosen to match the game's 1.6 aspect ratio (1280x800) +// so engine geometry rendered with the game's projection matrix +// does not look horizontally squished. +const int kBgfxWindowWidth = 800; +const int kBgfxWindowHeight = 500; // TheSuperHackers @refactor bobtista 11/04/2026 program handle // for the passthrough shader pair. Created once in Initialize after bgfx::init @@ -122,6 +124,9 @@ bgfx::ProgramHandle g_solidLitProgram = BGFX_INVALID_HANDLE; // Sampler uniform shared by all textured fragment shaders. Bound to stage 0. bgfx::UniformHandle g_sTex0 = BGFX_INVALID_HANDLE; +bgfx::UniformHandle g_sTex1 = BGFX_INVALID_HANDLE; +bgfx::UniformHandle g_sTex2 = BGFX_INVALID_HANDLE; +bgfx::UniformHandle g_sTex3 = BGFX_INVALID_HANDLE; // Alpha-test parameters consumed by fs_textured_lit_atest. .x is the // reference threshold in [0, 1]; engine writes ShaderClass alpha-ref / 255. // Named u_atestParams (not u_alphaRef) to avoid bgfx_shader.sh's internal @@ -534,6 +539,15 @@ bool BuildBgfxLayoutForFVF(const FVFInfoClass & fvf, bgfx::VertexLayout & out) std::unordered_map g_vbCache; std::unordered_map g_ibCache; +std::unordered_map g_textureCache; + +// The bgfx texture currently bound to stage 0 by Set_Texture. Used by +// SubmitEngineDraw - falls back to g_defaultWhiteTexture if invalid. +bgfx::TextureHandle g_currentBgfxTexture0 = BGFX_INVALID_HANDLE; +bgfx::TextureHandle g_currentBgfxTexture1 = BGFX_INVALID_HANDLE; +bgfx::TextureHandle g_currentBgfxTexture2 = BGFX_INVALID_HANDLE; +bgfx::TextureHandle g_currentBgfxTexture3 = BGFX_INVALID_HANDLE; +bool g_loggedFirstBgfxTextureCreate = false; // The most recent buffers and offsets cached from Set_Vertex_Buffer / // Set_Index_Buffer. Read by Draw_Triangles when it issues the bgfx @@ -542,9 +556,40 @@ bgfx::VertexBufferHandle g_currentBgfxVB = BGFX_INVALID_HANDLE; bgfx::IndexBufferHandle g_currentBgfxIB = BGFX_INVALID_HANDLE; unsigned short g_currentIBOffset = 0; +// TheSuperHackers @refactor bobtista 11/04/2026 transient +// (dynamic) buffer state. Capture_Dynamic_Vertex_Data allocs a bgfx +// transient VB and records the owning DynamicVBAccessClass pointer so +// the matching Set_Vertex_Buffer(DynamicVBAccessClass&) call can claim +// it. The transient buffers are auto-freed at bgfx::frame time; we +// only track validity within the current frame. +struct PendingTransientVB +{ + bool valid; + const DynamicVBAccessClass * owner; + bgfx::TransientVertexBuffer tvb; +}; +struct PendingTransientIB +{ + bool valid; + const DynamicIBAccessClass * owner; + bgfx::TransientIndexBuffer tib; +}; +PendingTransientVB g_pendingDynVB = { false, nullptr, {} }; +PendingTransientIB g_pendingDynIB = { false, nullptr, {} }; + +// Current draw call uses transient buffers if these are set. They +// shadow the static VB/IB handles above - SubmitEngineDraw picks the +// transient path when these are true. +bool g_currentUseTransientVB = false; +bgfx::TransientVertexBuffer g_currentTransientVB = {}; +bool g_currentUseTransientIB = false; +bgfx::TransientIndexBuffer g_currentTransientIB = {}; + bool g_loggedFirstBgfxVbCreate = false; bool g_loggedFirstBgfxIbCreate = false; bool g_loggedFirstBgfxSubmit = false; +bool g_loggedFirstDynVbCapture = false; +bool g_loggedFirstDynIbCapture = false; // TheSuperHackers @refactor bobtista 11/04/2026 transform // capture. The engine calls Set_Transform with world / view / projection @@ -601,6 +646,43 @@ void W3DMatrix3DToBgfx(const Matrix3D & m, float * out) out[3] = 0.0f; out[7] = 0.0f; out[11] = 0.0f; out[15] = 1.0f; } +// TheSuperHackers @refactor bobtista 11/04/2026 aspect ratio +// correction. The engine computes the projection matrix for the main +// game window's aspect ratio. The bgfx popup has a fixed 800x500 +// framebuffer (aspect 1.6), so applying the engine's projection +// directly stretches geometry horizontally when the game runs at a +// different aspect (e.g. 1024x768 = 1.33, or 1280x720 = 1.78). Rescale +// column 0 by (gameAspect / popupAspect) so the popup shows the same +// field of view the engine is rendering to the main window. +// +// The game aspect is extracted from the projection itself: for a +// standard perspective matrix proj[0][0] = cot(fov/2)/aspect and +// proj[1][1] = cot(fov/2), so gameAspect = proj[1][1] / proj[0][0]. +// Avoids touching DX8Wrapper's protected resolution accessors and +// works even when the engine later reshapes the backbuffer. +void ApplyPopupAspectCorrection(float * proj) +{ + // Column-major: proj[0] is (col 0, row 0), proj[5] is (col 1, row 1). + const float p00 = proj[0]; + const float p11 = proj[5]; + if (p00 <= 0.0f || p11 <= 0.0f) + { + return; + } + const float gameAspect = p11 / p00; + const float popupAspect = static_cast(kBgfxWindowWidth) / + static_cast(kBgfxWindowHeight); + if (popupAspect <= 0.0f) + { + return; + } + const float scale = gameAspect / popupAspect; + proj[0] *= scale; + proj[1] *= scale; + proj[2] *= scale; + proj[3] *= scale; +} + // TheSuperHackers @bugfix bobtista 11/04/2026 buffer copy // kill switch. The Intel UHD Graphics driver corrupts the dx8 GPU // copy of POOL_DEFAULT vertex buffers even when we lock with @@ -759,6 +841,142 @@ bgfx::IndexBufferHandle EnsureBgfxIndexBuffer(const IndexBufferClass * ib) return h; } +// TheSuperHackers @refactor bobtista 11/04/2026 texture +// capture. Unlike vertex buffers, W3D textures default to POOL_MANAGED, +// which is safe to lock with D3DLOCK_READONLY on the Intel UHD driver. +// We can read the source d3d8 texture data on demand from inside +// Set_Texture without an engine-side write hook. POOL_DEFAULT textures +// (render targets, dynamic textures) are skipped to avoid the same +// corruption that hit vertex buffers. + +bgfx::TextureFormat::Enum TranslateWW3DFormat(WW3DFormat fmt) +{ + switch (fmt) + { + case WW3D_FORMAT_A8R8G8B8: + case WW3D_FORMAT_X8R8G8B8: + // D3D8 stores ARGB as 0xAARRGGBB which on little-endian + // memory is BB GG RR AA - matches bgfx BGRA8 byte order. + return bgfx::TextureFormat::BGRA8; + case WW3D_FORMAT_R5G6B5: return bgfx::TextureFormat::R5G6B5; + case WW3D_FORMAT_A1R5G5B5: return bgfx::TextureFormat::BGR5A1; + case WW3D_FORMAT_A4R4G4B4: return bgfx::TextureFormat::BGRA4; + case WW3D_FORMAT_A8: return bgfx::TextureFormat::A8; + case WW3D_FORMAT_L8: return bgfx::TextureFormat::R8; + case WW3D_FORMAT_DXT1: return bgfx::TextureFormat::BC1; + case WW3D_FORMAT_DXT2: + case WW3D_FORMAT_DXT3: return bgfx::TextureFormat::BC2; + case WW3D_FORMAT_DXT4: + case WW3D_FORMAT_DXT5: return bgfx::TextureFormat::BC3; + default: return bgfx::TextureFormat::Unknown; + } +} + +bgfx::TextureHandle EnsureBgfxTexture(TextureBaseClass * tex) +{ + if (tex == nullptr) + { + return BGFX_INVALID_HANDLE; + } + auto it = g_textureCache.find(tex); + if (it != g_textureCache.end()) + { + return it->second; + } + + // Only handle TextureClass (regular 2D) for now. Cube and volume + // textures take a different path and would need their own helpers. + TextureClass * tex2d = tex->As_TextureClass(); + if (tex2d == nullptr) + { + g_textureCache[tex] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + if (tex->Get_Pool() == TextureBaseClass::POOL_DEFAULT) + { + // POOL_DEFAULT textures (render targets, dynamic) need a + // different code path. Skip for now. + g_textureCache[tex] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + const bgfx::TextureFormat::Enum bgfxFmt = TranslateWW3DFormat(tex2d->Get_Texture_Format()); + if (bgfxFmt == bgfx::TextureFormat::Unknown) + { + g_textureCache[tex] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + IDirect3DBaseTexture8 * baseTex = tex->Peek_D3D_Base_Texture(); + if (baseTex == nullptr) + { + // Texture not yet uploaded to D3D - the background loader thread + // may not have finished. DO NOT cache the failure: we want to + // retry on the next Set_Texture call so the bgfx handle gets + // populated as soon as the engine finishes loading. Otherwise a + // texture that was simply slow to load would stay white forever. + return BGFX_INVALID_HANDLE; + } + + IDirect3DTexture8 * d3dTex = static_cast(baseTex); + + D3DSURFACE_DESC desc; + if (FAILED(d3dTex->GetLevelDesc(0, &desc))) + { + g_textureCache[tex] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + D3DLOCKED_RECT locked = { 0 }; + HRESULT hr = d3dTex->LockRect(0, &locked, NULL, D3DLOCK_READONLY); + if (FAILED(hr) || locked.pBits == NULL) + { + WWDEBUG_SAY(("[BgfxBackend] EnsureBgfxTexture LockRect failed hr=0x%08x", static_cast(hr))); + g_textureCache[tex] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + // Compute the byte size of the surface. For uncompressed formats it + // is height * pitch. For DXT it is height/4 * pitch (block-compressed). + const bool isCompressed = + bgfxFmt == bgfx::TextureFormat::BC1 || + bgfxFmt == bgfx::TextureFormat::BC2 || + bgfxFmt == bgfx::TextureFormat::BC3; + + unsigned heightBlocks = desc.Height; + if (isCompressed) + { + heightBlocks = (desc.Height + 3) / 4; + } + const unsigned totalBytes = heightBlocks * static_cast(locked.Pitch); + + const bgfx::Memory * mem = bgfx::copy(locked.pBits, totalBytes); + d3dTex->UnlockRect(0); + + bgfx::TextureHandle h = bgfx::createTexture2D( + static_cast(desc.Width), + static_cast(desc.Height), + false, 1, + bgfxFmt, + BGFX_TEXTURE_NONE | BGFX_SAMPLER_MIN_ANISOTROPIC | BGFX_SAMPLER_MAG_ANISOTROPIC, + mem); + + g_textureCache[tex] = h; + + if (!g_loggedFirstBgfxTextureCreate) + { + g_loggedFirstBgfxTextureCreate = true; + WWDEBUG_SAY(("[BgfxBackend] EnsureBgfxTexture first create " + "%dx%d ww3dfmt=%u bytes=%u handle=%s", + desc.Width, desc.Height, + static_cast(tex2d->Get_Texture_Format()), + totalBytes, + bgfx::isValid(h) ? "ok" : "FAILED")); + } + return h; +} + // TheSuperHackers @refactor bobtista 11/04/2026 Set_Shader // telemetry. The bgfx backend caches the picked program and state bits // for the most recent ShaderClass it received, and tracks the unique @@ -803,9 +1021,7 @@ const char * ProgramDebugName(bgfx::ProgramHandle h) // Per-method "first call" flags so we can confirm the engine is routing // each entry point through the bgfx backend at least once. Logged once, // then suppressed to keep WWDebug.txt readable. -bool g_loggedFirstApplyRenderState = false; bool g_loggedFirstSetTexture = false; -bool g_loggedFirstSetMaterial = false; bool g_loggedFirstDrawTriangles = false; bool g_loggedFirstSetVertexBuffer = false; bool g_loggedFirstSetIndexBuffer = false; @@ -964,6 +1180,9 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) // createProgram(..., true) hands ownership of its shader handles to the // program, so we must NOT manually destroy them later. g_sTex0 = bgfx::createUniform("s_tex0", bgfx::UniformType::Sampler); + g_sTex1 = bgfx::createUniform("s_tex1", bgfx::UniformType::Sampler); + g_sTex2 = bgfx::createUniform("s_tex2", bgfx::UniformType::Sampler); + g_sTex3 = bgfx::createUniform("s_tex3", bgfx::UniformType::Sampler); g_uAtestParams = bgfx::createUniform("u_atestParams", bgfx::UniformType::Vec4); // default 1x1 white texture (opaque ABGR=0xffffffff). @@ -1088,6 +1307,21 @@ void BgfxBackend::Shutdown() bgfx::destroy(g_sTex0); g_sTex0 = BGFX_INVALID_HANDLE; } + if (bgfx::isValid(g_sTex1)) + { + bgfx::destroy(g_sTex1); + g_sTex1 = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(g_sTex2)) + { + bgfx::destroy(g_sTex2); + g_sTex2 = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(g_sTex3)) + { + bgfx::destroy(g_sTex3); + g_sTex3 = BGFX_INVALID_HANDLE; + } if (bgfx::isValid(g_uAtestParams)) { bgfx::destroy(g_uAtestParams); @@ -1116,9 +1350,25 @@ void BgfxBackend::Shutdown() } } g_ibCache.clear(); - g_currentBgfxVB = BGFX_INVALID_HANDLE; - g_currentBgfxIB = BGFX_INVALID_HANDLE; - g_currentIBOffset = 0; + for (auto & kv : g_textureCache) + { + if (bgfx::isValid(kv.second)) + { + bgfx::destroy(kv.second); + } + } + g_textureCache.clear(); + g_currentBgfxVB = BGFX_INVALID_HANDLE; + g_currentBgfxIB = BGFX_INVALID_HANDLE; + g_currentBgfxTexture0 = BGFX_INVALID_HANDLE; + g_currentBgfxTexture1 = BGFX_INVALID_HANDLE; + g_currentBgfxTexture2 = BGFX_INVALID_HANDLE; + g_currentBgfxTexture3 = BGFX_INVALID_HANDLE; + g_currentIBOffset = 0; + g_currentUseTransientVB = false; + g_currentUseTransientIB = false; + g_pendingDynVB.valid = false; + g_pendingDynIB.valid = false; bgfx::shutdown(); g_bgfxInitialized = false; WWDEBUG_SAY(("[BgfxBackend] bgfx::shutdown complete.")); @@ -1131,32 +1381,8 @@ void BgfxBackend::Shutdown() } } -// -- Device state queries ---------------------------------------------------- - -bool BgfxBackend::Is_Device_Lost() const -{ - return false; -} - -bool BgfxBackend::Has_Stencil() -{ - return false; -} - -WW3DFormat BgfxBackend::Get_Back_Buffer_Format() -{ - return WW3D_FORMAT_UNKNOWN; -} - -SurfaceClass * BgfxBackend::Get_Back_Buffer(unsigned int /*num*/) -{ - return nullptr; -} - -void BgfxBackend::Set_Gamma(float /*gamma*/, float /*bright*/, float /*contrast*/, - bool /*calibrate*/, bool /*uselimit*/) -{ -} +// Device state queries (Is_Device_Lost / Has_Stencil / Get_Back_Buffer_Format +// / Get_Back_Buffer / Set_Gamma) are inherited from DX8Backend. // -- Frame lifecycle --------------------------------------------------------- // @@ -1227,26 +1453,24 @@ void BgfxBackend::End_Scene(bool /*flip_frame*/) } SubmitTestTriangle(); bgfx::frame(); -} - -void BgfxBackend::Flip_To_Primary() -{ -} -void BgfxBackend::Clear(bool /*clear_color*/, bool /*clear_z_stencil*/, - const Vector3 & /*color*/, - float /*dest_alpha*/, float /*z*/, unsigned int /*stencil*/) -{ + // Transient buffers are freed at bgfx::frame time. Invalidate the + // pending and current slots so nothing next frame tries to reuse + // a dead handle. + g_pendingDynVB.valid = false; + g_pendingDynIB.valid = false; + g_currentUseTransientVB = false; + g_currentUseTransientIB = false; } -void BgfxBackend::Set_Viewport(const RenderBackendViewport & /*viewport*/) -{ -} +// Flip_To_Primary, Clear, Set_Viewport are inherited from DX8Backend. // -- Vertex / index buffers -------------------------------------------------- -void BgfxBackend::Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int /*stream*/) +void BgfxBackend::Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int stream) { + DX8Backend::Set_Vertex_Buffer(vb, stream); + if (!g_loggedFirstSetVertexBuffer && vb != nullptr) { g_loggedFirstSetVertexBuffer = true; @@ -1263,6 +1487,7 @@ void BgfxBackend::Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int / // there. Engine VBs that have not been written via the WriteLockClass // path yet (e.g. those filled by raw d3d8 calls) will miss the cache // and the bgfx submit will be skipped. + g_currentUseTransientVB = false; auto it = g_vbCache.find(vb); if (it != g_vbCache.end()) { @@ -1276,21 +1501,28 @@ void BgfxBackend::Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int / void BgfxBackend::Set_Vertex_Buffer(const DynamicVBAccessClass & vba) { - if (!g_loggedFirstSetVertexBuffer) + DX8Backend::Set_Vertex_Buffer(vba); + + // If the matching Capture_Dynamic_Vertex_Data already + // allocated a transient VB for this access class, claim it for the + // next draw. Otherwise miss the cache and skip the bgfx submit. + if (g_pendingDynVB.valid && g_pendingDynVB.owner == &vba) { - g_loggedFirstSetVertexBuffer = true; - const FVFInfoClass & fvf = vba.FVF_Info(); - WWDEBUG_SAY(("[BgfxBackend] Set_Vertex_Buffer first call (dynamic VB path) " - "fvf=0x%08x size=%u verts=%u type=%u", - fvf.Get_FVF(), - fvf.Get_FVF_Size(), - vba.Get_Vertex_Count(), - vba.Get_Type())); + g_currentUseTransientVB = true; + g_currentTransientVB = g_pendingDynVB.tvb; + g_pendingDynVB.valid = false; + } + else + { + g_currentUseTransientVB = false; + g_currentBgfxVB = BGFX_INVALID_HANDLE; } } void BgfxBackend::Set_Index_Buffer(const IndexBufferClass * ib, unsigned short index_base_offset) { + DX8Backend::Set_Index_Buffer(ib, index_base_offset); + if (!g_loggedFirstSetIndexBuffer && ib != nullptr) { g_loggedFirstSetIndexBuffer = true; @@ -1299,6 +1531,7 @@ void BgfxBackend::Set_Index_Buffer(const IndexBufferClass * ib, unsigned short i ib->Get_Index_Count(), ib->Type())); } + g_currentUseTransientIB = false; auto it = g_ibCache.find(ib); if (it != g_ibCache.end()) { @@ -1311,21 +1544,26 @@ void BgfxBackend::Set_Index_Buffer(const IndexBufferClass * ib, unsigned short i g_currentIBOffset = index_base_offset; } -void BgfxBackend::Set_Index_Buffer(const DynamicIBAccessClass & iba, unsigned short /*index_base_offset*/) +void BgfxBackend::Set_Index_Buffer(const DynamicIBAccessClass & iba, unsigned short index_base_offset) { - if (!g_loggedFirstSetIndexBuffer) + DX8Backend::Set_Index_Buffer(iba, index_base_offset); + + if (g_pendingDynIB.valid && g_pendingDynIB.owner == &iba) { - g_loggedFirstSetIndexBuffer = true; - WWDEBUG_SAY(("[BgfxBackend] Set_Index_Buffer first call (dynamic IB path) " - "indices=%u type=%u", - iba.Get_Index_Count(), - iba.Get_Type())); + g_currentUseTransientIB = true; + g_currentTransientIB = g_pendingDynIB.tib; + g_pendingDynIB.valid = false; } + else + { + g_currentUseTransientIB = false; + g_currentBgfxIB = BGFX_INVALID_HANDLE; + } + g_currentIBOffset = index_base_offset; } -void BgfxBackend::Set_Index_Buffer_Index_Offset(unsigned int /*offset*/) -{ -} +// Set_Index_Buffer_Index_Offset is inherited from DX8Backend so the dx8 +// device sees the binding. // -- write-side capture ------------------------------------------- // @@ -1413,10 +1651,107 @@ void BgfxBackend::Capture_Index_Data(const IndexBufferClass * ib, } } +// TheSuperHackers @refactor bobtista 11/04/2026 dynamic +// capture. DynamicVBAccessClass / DynamicIBAccessClass are CPU-side +// views onto a ring buffer that changes every frame (particles, sprites, +// skinned meshes, HUD). Creating a bgfx VB per frame would churn the +// GPU allocator, so the bgfx side uses transient buffers which are +// auto-freed at the next bgfx::frame. +// +// Flow: engine's WriteLockClass destructor calls this with the locked +// sub-range. We alloc a transient buffer of exactly that size, memcpy +// the data in, and stash the handle keyed by the access class pointer. +// The matching Set_Vertex_Buffer(DynamicVBAccessClass&) later sees its +// own pointer in g_pendingDynVB and claims the transient for the draw. + +void BgfxBackend::Capture_Dynamic_Vertex_Data(const DynamicVBAccessClass * vba, + const void * data, + unsigned int size_bytes) +{ + if (!g_bgfxInitialized || vba == nullptr || data == nullptr || size_bytes == 0) + { + return; + } + + bgfx::VertexLayout layout; + if (!BuildBgfxLayoutForFVF(vba->FVF_Info(), layout)) + { + return; + } + + const uint32_t num_verts = static_cast(vba->Get_Vertex_Count()); + if (num_verts == 0) + { + return; + } + if (bgfx::getAvailTransientVertexBuffer(num_verts, layout) < num_verts) + { + g_pendingDynVB.valid = false; + return; + } + + bgfx::allocTransientVertexBuffer(&g_pendingDynVB.tvb, num_verts, layout); + const uint32_t copy_bytes = num_verts * layout.getStride(); + const uint32_t bytes = (size_bytes < copy_bytes) ? size_bytes : copy_bytes; + std::memcpy(g_pendingDynVB.tvb.data, data, bytes); + g_pendingDynVB.owner = vba; + g_pendingDynVB.valid = true; + + if (!g_loggedFirstDynVbCapture) + { + g_loggedFirstDynVbCapture = true; + WWDEBUG_SAY(("[BgfxBackend] Capture_Dynamic_Vertex_Data first " + "fvf=0x%08x stride=%u verts=%u bytes=%u", + vba->FVF_Info().Get_FVF(), + layout.getStride(), + num_verts, + bytes)); + } +} + +void BgfxBackend::Capture_Dynamic_Index_Data(const DynamicIBAccessClass * iba, + const void * data, + unsigned int size_bytes) +{ + if (!g_bgfxInitialized || iba == nullptr || data == nullptr || size_bytes == 0) + { + return; + } + + const uint32_t num_indices = static_cast(iba->Get_Index_Count()); + if (num_indices == 0) + { + return; + } + if (bgfx::getAvailTransientIndexBuffer(num_indices) < num_indices) + { + g_pendingDynIB.valid = false; + return; + } + + bgfx::allocTransientIndexBuffer(&g_pendingDynIB.tib, num_indices); + const uint32_t copy_bytes = num_indices * sizeof(uint16_t); + const uint32_t bytes = (size_bytes < copy_bytes) ? size_bytes : copy_bytes; + std::memcpy(g_pendingDynIB.tib.data, data, bytes); + g_pendingDynIB.owner = iba; + g_pendingDynIB.valid = true; + + if (!g_loggedFirstDynIbCapture) + { + g_loggedFirstDynIbCapture = true; + WWDEBUG_SAY(("[BgfxBackend] Capture_Dynamic_Index_Data first " + "indices=%u bytes=%u", + num_indices, + bytes)); + } +} + // -- State: shaders, materials, textures ------------------------------------ void BgfxBackend::Set_Shader(const ShaderClass & shader) { + DX8Backend::Set_Shader(shader); + // TheSuperHackers @refactor bobtista 11/04/2026 cache the // picked program and state bits for the next draw call, and log any // shader bit pattern we have not seen before (capped at kMaxLoggedPresets @@ -1437,122 +1772,41 @@ void BgfxBackend::Set_Shader(const ShaderClass & shader) } } -void BgfxBackend::Get_Shader(ShaderClass & /*shader*/) +void BgfxBackend::Set_Texture(unsigned int stage, TextureBaseClass * texture) { -} + DX8Backend::Set_Texture(stage, texture); -void BgfxBackend::Set_Material(const VertexMaterialClass * /*material*/) -{ - if (!g_loggedFirstSetMaterial) - { - g_loggedFirstSetMaterial = true; - WWDEBUG_SAY(("[BgfxBackend] Set_Material first call")); - } -} - -void BgfxBackend::Set_Texture(unsigned int stage, TextureBaseClass * /*texture*/) -{ if (!g_loggedFirstSetTexture) { g_loggedFirstSetTexture = true; WWDEBUG_SAY(("[BgfxBackend] Set_Texture first call (stage=%u)", stage)); } -} - -void BgfxBackend::Upload_Texture_Region( - TextureClass * /*dst_texture*/, - unsigned int /*dst_level*/, - unsigned int /*dst_x*/, unsigned int /*dst_y*/, - const void * /*src_data*/, - unsigned int /*src_pitch*/, - unsigned int /*region_width*/, unsigned int /*region_height*/, - WW3DFormat /*format*/) -{ - // Stub on render-backend-interface. The bgfx texture mirror lands in - // the bgfx-features topic; until then bgfx is not the active backend. -} - -void BgfxBackend::Apply_Render_State_Changes() -{ - if (!g_loggedFirstApplyRenderState) + // stages 0-3 wired. Covers terrain base + detail + // + cloud + noise, the standard 4-stage layout used by the + // FlatHeightMap pixel shader family. Stages above 3 still fall + // through unmigrated. + switch (stage) { - g_loggedFirstApplyRenderState = true; - WWDEBUG_SAY(("[BgfxBackend] Apply_Render_State_Changes first call")); + case 0: g_currentBgfxTexture0 = EnsureBgfxTexture(texture); break; + case 1: g_currentBgfxTexture1 = EnsureBgfxTexture(texture); break; + case 2: g_currentBgfxTexture2 = EnsureBgfxTexture(texture); break; + case 3: g_currentBgfxTexture3 = EnsureBgfxTexture(texture); break; + default: break; } } -void BgfxBackend::Apply_Default_State() -{ -} - -void BgfxBackend::Invalidate_Cached_Render_States() -{ -} - -void BgfxBackend::Set_Blend_Op(BlendOp /*op*/) -{ -} - -void BgfxBackend::Set_Blend_Factors(BlendFactor /*src*/, BlendFactor /*dest*/) -{ -} - -void BgfxBackend::Set_Color_Write_Enable(bool /*red*/, bool /*green*/, bool /*blue*/, bool /*alpha*/) -{ -} - -void BgfxBackend::Set_Alpha_Blend_Enable(bool /*enable*/) -{ -} - -void BgfxBackend::Show_Hardware_Cursor(bool /*show*/) -{ -} - -void BgfxBackend::Set_Hardware_Cursor_Image(int /*hotspot_x*/, int /*hotspot_y*/, SurfaceClass * /*surface*/) -{ -} - -void BgfxBackend::Set_Hardware_Cursor_Position(int /*x*/, int /*y*/) -{ -} - -void BgfxBackend::Set_Stencil_Enable(bool /*enable*/) -{ -} - -void BgfxBackend::Set_Stencil_Func(CompareFunc /*func*/) -{ -} - -void BgfxBackend::Set_Stencil_Ref(unsigned int /*ref*/) -{ -} - -void BgfxBackend::Set_Stencil_Mask(unsigned int /*mask*/) -{ -} - -void BgfxBackend::Set_Stencil_Write_Mask(unsigned int /*mask*/) -{ -} - -void BgfxBackend::Set_Stencil_Pass_Op(StencilOp /*op*/) -{ -} - -void BgfxBackend::Set_Stencil_Fail_Op(StencilOp /*op*/) -{ -} - -void BgfxBackend::Set_Stencil_ZFail_Op(StencilOp /*op*/) -{ -} +// Get_Shader, Set_Material, Apply_Render_State_Changes, Apply_Default_State, +// Invalidate_Cached_Render_States, Set_Blend_Op, Set_Blend_Factors, +// Set_Color_Write_Enable, Set_Alpha_Blend_Enable, hardware cursor, and +// Set_Stencil_* are all inherited from DX8Backend and forward unchanged +// to DX8Wrapper. // -- Transforms -------------------------------------------------------------- void BgfxBackend::Set_Transform(TransformKind transform, const Matrix4x4 & m) { + DX8Backend::Set_Transform(transform, m); + switch (transform) { case RB_TRANSFORM_WORLD: @@ -1578,6 +1832,7 @@ void BgfxBackend::Set_Transform(TransformKind transform, const Matrix4x4 & m) break; case RB_TRANSFORM_PROJECTION: W3DMatrix4ToBgfx(m, g_bgfxProj); + ApplyPopupAspectCorrection(g_bgfxProj); g_bgfxViewProjDirty = true; if (!g_loggedFirstSetProj) { @@ -1594,6 +1849,8 @@ void BgfxBackend::Set_Transform(TransformKind transform, const Matrix4x4 & m) void BgfxBackend::Set_Transform(TransformKind transform, const Matrix3D & m) { + DX8Backend::Set_Transform(transform, m); + switch (transform) { case RB_TRANSFORM_WORLD: @@ -1622,35 +1879,29 @@ void BgfxBackend::Set_Transform(TransformKind transform, const Matrix3D & m) } } -void BgfxBackend::Get_Transform(TransformKind /*transform*/, Matrix4x4 & /*m*/) -{ -} +// Get_Transform / Is_World_Identity / Is_View_Identity are inherited +// from DX8Backend. void BgfxBackend::Set_World_Identity() { + DX8Backend::Set_World_Identity(); IdentityMatrix(g_bgfxWorld); } void BgfxBackend::Set_View_Identity() { + DX8Backend::Set_View_Identity(); IdentityMatrix(g_bgfxView); g_bgfxViewProjDirty = true; } -bool BgfxBackend::Is_World_Identity() -{ - return true; -} - -bool BgfxBackend::Is_View_Identity() -{ - return true; -} - void BgfxBackend::Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & matrix, - float /*znear*/, float /*zfar*/) + float znear, float zfar) { + DX8Backend::Set_Projection_Transform_With_Z_Bias(matrix, znear, zfar); + W3DMatrix4ToBgfx(matrix, g_bgfxProj); + ApplyPopupAspectCorrection(g_bgfxProj); g_bgfxViewProjDirty = true; if (!g_loggedFirstSetProj) { @@ -1661,39 +1912,9 @@ void BgfxBackend::Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & matrix, } } -// -- Lighting and fog -------------------------------------------------------- - -void BgfxBackend::Set_Light(unsigned int /*index*/, const LightClass & /*light*/) -{ -} - -void BgfxBackend::Set_Ambient(const Vector3 & /*color*/) -{ -} - -const Vector3 & BgfxBackend::Get_Ambient() const -{ - return kZeroVec3; -} - -void BgfxBackend::Set_Fog(bool /*enable*/, const Vector3 & /*color*/, - float /*start*/, float /*end*/) -{ -} - -bool BgfxBackend::Get_Fog_Enable() const -{ - return false; -} - -void BgfxBackend::Set_Light_Environment(LightEnvironmentClass * /*light_env*/) -{ -} - -LightEnvironmentClass * BgfxBackend::Get_Light_Environment() const -{ - return nullptr; -} +// Lighting and fog (Set_Light, Set_Ambient, Get_Ambient, Set_Fog, +// Get_Fog_Enable, Set_Light_Environment, Get_Light_Environment) are +// inherited from DX8Backend. // -- Draw calls -------------------------------------------------------------- @@ -1721,9 +1942,13 @@ void SubmitEngineDraw(unsigned short start_index, { return; } - if (!bgfx::isValid(g_currentBgfxVB) || - !bgfx::isValid(g_currentBgfxIB) || - !bgfx::isValid(g_currentBgfxProgram)) + if (!bgfx::isValid(g_currentBgfxProgram)) + { + return; + } + const bool have_vb = g_currentUseTransientVB || bgfx::isValid(g_currentBgfxVB); + const bool have_ib = g_currentUseTransientIB || bgfx::isValid(g_currentBgfxIB); + if (!have_vb || !have_ib) { return; } @@ -1741,19 +1966,78 @@ void SubmitEngineDraw(unsigned short start_index, // and resets the per-draw transform after each submit. bgfx::setTransform(g_bgfxWorld); - bgfx::setVertexBuffer(0, g_currentBgfxVB, min_vertex_index, vertex_count); - bgfx::setIndexBuffer(g_currentBgfxIB, - static_cast(g_currentIBOffset) + start_index, - static_cast(polygon_count) * 3); + if (g_currentUseTransientVB) + { + bgfx::setVertexBuffer(0, &g_currentTransientVB, min_vertex_index, vertex_count); + } + else + { + bgfx::setVertexBuffer(0, g_currentBgfxVB, min_vertex_index, vertex_count); + } - // Bind the default 1x1 white texture to stage 0. Real Set_Texture - // wiring is not in place yet, so this is a placeholder so the - // textured shaders have a valid sampler. The shader does - // texColor * v_color0 so white-on-vertex-color renders the vertex - // color through unmodified. - if (bgfx::isValid(g_defaultWhiteTexture) && bgfx::isValid(g_sTex0)) + if (g_currentUseTransientIB) + { + bgfx::setIndexBuffer(&g_currentTransientIB, + start_index, + static_cast(polygon_count) * 3); + } + else + { + bgfx::setIndexBuffer(g_currentBgfxIB, + static_cast(g_currentIBOffset) + start_index, + static_cast(polygon_count) * 3); + } + + // bind engine textures on stages 0 and 1 by + // Set_Texture, falling back to the 1x1 white default if no real + // texture is cached. The default white * vertex color = vertex color, + // which keeps untextured draws visible until every texture path + // is migrated. For stage 1, white is also the multiplicative + // identity, so single-texture draws (which don't bind stage 1) are + // unaffected by the second sample. + if (bgfx::isValid(g_sTex0)) + { + const bgfx::TextureHandle bound = + bgfx::isValid(g_currentBgfxTexture0) + ? g_currentBgfxTexture0 + : g_defaultWhiteTexture; + if (bgfx::isValid(bound)) + { + bgfx::setTexture(0, g_sTex0, bound); + } + } + if (bgfx::isValid(g_sTex1)) + { + const bgfx::TextureHandle bound = + bgfx::isValid(g_currentBgfxTexture1) + ? g_currentBgfxTexture1 + : g_defaultWhiteTexture; + if (bgfx::isValid(bound)) + { + bgfx::setTexture(1, g_sTex1, bound); + } + } + if (bgfx::isValid(g_sTex2)) { - bgfx::setTexture(0, g_sTex0, g_defaultWhiteTexture); + const bgfx::TextureHandle bound = + bgfx::isValid(g_currentBgfxTexture2) + ? g_currentBgfxTexture2 + : g_defaultWhiteTexture; + if (bgfx::isValid(bound)) + { + bgfx::setTexture(2, g_sTex2, bound); + } + } + if (bgfx::isValid(g_sTex3)) + { + const bgfx::TextureHandle bound = + bgfx::isValid(g_currentBgfxTexture3) + ? g_currentBgfxTexture3 + : g_defaultWhiteTexture; + if (bgfx::isValid(bound)) + { + bgfx::setTexture(3, g_sTex3, bound); + } } // Use the cached state if it was built; otherwise fall back to a @@ -1783,6 +2067,8 @@ void BgfxBackend::Draw_Triangles(unsigned short start_index, unsigned short min_vertex_index, unsigned short vertex_count) { + DX8Backend::Draw_Triangles(start_index, polygon_count, min_vertex_index, vertex_count); + if (!g_loggedFirstDrawTriangles) { g_loggedFirstDrawTriangles = true; @@ -1792,12 +2078,14 @@ void BgfxBackend::Draw_Triangles(unsigned short start_index, SubmitEngineDraw(start_index, polygon_count, min_vertex_index, vertex_count); } -void BgfxBackend::Draw_Triangles(unsigned int /*buffer_type*/, +void BgfxBackend::Draw_Triangles(unsigned int buffer_type, unsigned short start_index, unsigned short polygon_count, unsigned short min_vertex_index, unsigned short vertex_count) { + DX8Backend::Draw_Triangles(buffer_type, start_index, polygon_count, min_vertex_index, vertex_count); + if (!g_loggedFirstDrawTriangles) { g_loggedFirstDrawTriangles = true; @@ -1807,52 +2095,7 @@ void BgfxBackend::Draw_Triangles(unsigned int /*buffer_type*/, SubmitEngineDraw(start_index, polygon_count, min_vertex_index, vertex_count); } -void BgfxBackend::Draw_Strip(unsigned short /*start_index*/, - unsigned short /*index_count*/, - unsigned short /*min_vertex_index*/, - unsigned short /*vertex_count*/) -{ -} - -// -- Programmable pipeline --------------------------------------------------- - -void BgfxBackend::Set_Vertex_Shader(unsigned long /*vertex_shader*/) -{ -} - -void BgfxBackend::Set_Pixel_Shader(unsigned long /*pixel_shader*/) -{ -} - -void BgfxBackend::Set_Vertex_Shader_Constant(int /*reg*/, const void * /*data*/, int /*count*/) -{ -} - -void BgfxBackend::Set_Pixel_Shader_Constant(int /*reg*/, const void * /*data*/, int /*count*/) -{ -} - -// -- Render targets ---------------------------------------------------------- - -TextureClass * BgfxBackend::Create_Render_Target(int /*width*/, int /*height*/, WW3DFormat /*format*/) -{ - return nullptr; -} - -void BgfxBackend::Set_Render_Target_With_Z(TextureClass * /*texture*/, ZTextureClass * /*ztexture*/) -{ -} - -bool BgfxBackend::Is_Render_To_Texture() -{ - return false; -} - -void BgfxBackend::Set_Shadow_Map(int /*idx*/, ZTextureClass * /*ztex*/) -{ -} - -ZTextureClass * BgfxBackend::Get_Shadow_Map(int /*idx*/) -{ - return nullptr; -} +// Draw_Strip, the programmable pipeline (Set_Vertex_Shader, Set_Pixel_Shader, +// Set_Vertex_Shader_Constant, Set_Pixel_Shader_Constant), and render targets +// (Create_Render_Target, Set_Render_Target_With_Z, Is_Render_To_Texture, +// Set_Shadow_Map, Get_Shadow_Map) are inherited from DX8Backend. diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h index 90d1687ef5d..0a4d3ee45a4 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h @@ -16,12 +16,17 @@ ** along with this program. If not, see . */ -// TheSuperHackers @refactor bobtista 10/04/2026 BgfxBackend is the -// IRenderBackend implementation that targets bgfx (DX11 on Windows, -// Vulkan on Linux, Metal on macOS). This provides a stub shell that -// compiles and links against bgfx but implements every virtual method -// as a no-op. Later work fills in real implementations subsystem by -// subsystem as the engine is migrated off DX8Wrapper statics. +// TheSuperHackers @refactor bobtista 11/04/2026 BgfxBackend inherits from +// DX8Backend so the dx8 device stays correctly programmed for as long as +// the bgfx and dx8 backends are co-resident. The base class implementation +// of every method forwards to DX8Wrapper. BgfxBackend overrides the +// methods that need bgfx-specific extras (capture matrices, build a +// vertex/index buffer cache, pick a bgfx program from a ShaderClass, +// etc.) and calls the base class first to keep the dx8 device functional. +// +// After the cutover the dx8 device goes away and the base class +// forwards become no-ops. Until then, this dual path is what keeps the +// dx8 main game window rendering correctly in the bgfx build. // // This header MUST NOT be included from any VC6 translation unit. The // VC6 build always uses DX8Backend; the bgfx backend requires MSVC 2022+ @@ -29,44 +34,46 @@ #pragma once -#include "IRenderBackend.h" +#include "DX8Backend.h" -class BgfxBackend : public IRenderBackend +class BgfxBackend : public DX8Backend { public: BgfxBackend(); virtual ~BgfxBackend(); // -- Backend lifecycle ---------------------------------------------------- + // + // Initialize creates the bgfx popup window and calls bgfx::init. + // Shutdown tears down all bgfx resources before bgfx::shutdown. + // Both override DX8Backend's empty stubs. virtual void Initialize(void * hwnd, int width, int height); virtual void Shutdown(); - // -- Device state queries ------------------------------------------------- - - virtual bool Is_Device_Lost() const; - virtual bool Has_Stencil(); - virtual WW3DFormat Get_Back_Buffer_Format(); - virtual SurfaceClass * Get_Back_Buffer(unsigned int num); - virtual void Set_Gamma(float gamma, float bright, float contrast, bool calibrate, bool uselimit); - // -- Frame lifecycle ------------------------------------------------------ + // + // Begin_Scene touches the bgfx views; End_Scene calls bgfx::frame to advance the swap chain. virtual void Begin_Scene(); virtual void End_Scene(bool flip_frame); - virtual void Flip_To_Primary(); - virtual void Clear(bool clear_color, bool clear_z_stencil, - const Vector3 & color, - float dest_alpha, float z, unsigned int stencil); - virtual void Set_Viewport(const RenderBackendViewport & viewport); // -- Vertex / index buffers ----------------------------------------------- + // + // Override only the static-VB / IB setters to record the bgfx cache + // hit (or miss) for the current draw. The base DX8Backend forwards + // are still called so the d3d8 device sees the binding too. virtual void Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int stream); virtual void Set_Vertex_Buffer(const DynamicVBAccessClass & vba); virtual void Set_Index_Buffer(const IndexBufferClass * ib, unsigned short index_base_offset); virtual void Set_Index_Buffer(const DynamicIBAccessClass & iba, unsigned short index_base_offset); - virtual void Set_Index_Buffer_Index_Offset(unsigned int offset); + + // write-side capture hooks. DX8Backend inherits the + // empty default from IRenderBackend; BgfxBackend captures the data + // into the cache for use by Set_Vertex_Buffer / Set_Index_Buffer. + // Dynamic variants for DynamicVBAccessClass / + // DynamicIBAccessClass which get copied into bgfx transient buffers. virtual void Capture_Vertex_Data(const VertexBufferClass * vb, const void * data, @@ -74,62 +81,39 @@ class BgfxBackend : public IRenderBackend virtual void Capture_Index_Data(const IndexBufferClass * ib, const void * data, unsigned int size_bytes); + virtual void Capture_Dynamic_Vertex_Data(const DynamicVBAccessClass * vba, + const void * data, + unsigned int size_bytes); + virtual void Capture_Dynamic_Index_Data(const DynamicIBAccessClass * iba, + const void * data, + unsigned int size_bytes); // -- State: shaders, materials, textures --------------------------------- + // + // Set_Shader picks a bgfx program and state mask from the preset bits. + // Set_Texture caches the texture handle. Both call the base DX8Backend + // forward so the dx8 device also sees the change. virtual void Set_Shader(const ShaderClass & shader); - virtual void Get_Shader(ShaderClass & shader); - virtual void Set_Material(const VertexMaterialClass * material); virtual void Set_Texture(unsigned int stage, TextureBaseClass * texture); - virtual void Upload_Texture_Region( - TextureClass * dst_texture, - unsigned int dst_level, - unsigned int dst_x, unsigned int dst_y, - const void * src_data, - unsigned int src_pitch, - unsigned int region_width, unsigned int region_height, - WW3DFormat format); - virtual void Apply_Render_State_Changes(); - virtual void Apply_Default_State(); - virtual void Invalidate_Cached_Render_States(); - virtual void Set_Blend_Op(BlendOp op); - virtual void Set_Blend_Factors(BlendFactor src, BlendFactor dest); - virtual void Set_Color_Write_Enable(bool red, bool green, bool blue, bool alpha); - virtual void Set_Alpha_Blend_Enable(bool enable); - virtual void Show_Hardware_Cursor(bool show); - virtual void Set_Hardware_Cursor_Image(int hotspot_x, int hotspot_y, SurfaceClass * surface); - virtual void Set_Hardware_Cursor_Position(int x, int y); - virtual void Set_Stencil_Enable(bool enable); - virtual void Set_Stencil_Func(CompareFunc func); - virtual void Set_Stencil_Ref(unsigned int ref); - virtual void Set_Stencil_Mask(unsigned int mask); - virtual void Set_Stencil_Write_Mask(unsigned int mask); - virtual void Set_Stencil_Pass_Op(StencilOp op); - virtual void Set_Stencil_Fail_Op(StencilOp op); - virtual void Set_Stencil_ZFail_Op(StencilOp op); // -- Transforms ----------------------------------------------------------- + // + // Captures the engine's view / projection / world matrices into bgfx + // column-major form, then forwards to the base DX8Backend so the dx8 + // device also gets the matrices set. virtual void Set_Transform(TransformKind transform, const Matrix4x4 & m); virtual void Set_Transform(TransformKind transform, const Matrix3D & m); - virtual void Get_Transform(TransformKind transform, Matrix4x4 & m); virtual void Set_World_Identity(); virtual void Set_View_Identity(); - virtual bool Is_World_Identity(); - virtual bool Is_View_Identity(); virtual void Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & matrix, float znear, float zfar); - // -- Lighting and fog ----------------------------------------------------- - - virtual void Set_Light(unsigned int index, const LightClass & light); - virtual void Set_Ambient(const Vector3 & color); - virtual const Vector3 & Get_Ambient() const; - virtual void Set_Fog(bool enable, const Vector3 & color, float start, float end); - virtual bool Get_Fog_Enable() const; - virtual void Set_Light_Environment(LightEnvironmentClass * light_env); - virtual LightEnvironmentClass * Get_Light_Environment() const; - // -- Draw calls ----------------------------------------------------------- + // + // Issues a real bgfx::submit on view 1 if the cache lookup found a + // valid VB+IB+program for the current state, then forwards to the + // base DX8Backend so the dx8 device also draws the geometry. virtual void Draw_Triangles(unsigned short start_index, unsigned short polygon_count, @@ -140,23 +124,15 @@ class BgfxBackend : public IRenderBackend unsigned short polygon_count, unsigned short min_vertex_index, unsigned short vertex_count); - virtual void Draw_Strip(unsigned short start_index, - unsigned short index_count, - unsigned short min_vertex_index, - unsigned short vertex_count); - - // -- Programmable pipeline ------------------------------------------------ - - virtual void Set_Vertex_Shader(unsigned long vertex_shader); - virtual void Set_Pixel_Shader(unsigned long pixel_shader); - virtual void Set_Vertex_Shader_Constant(int reg, const void * data, int count); - virtual void Set_Pixel_Shader_Constant(int reg, const void * data, int count); - - // -- Render targets ------------------------------------------------------- - virtual TextureClass * Create_Render_Target(int width, int height, WW3DFormat format); - virtual void Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass * ztexture); - virtual bool Is_Render_To_Texture(); - virtual void Set_Shadow_Map(int idx, ZTextureClass * ztex); - virtual ZTextureClass * Get_Shadow_Map(int idx); + // Everything else (Is_Device_Lost, Has_Stencil, Get_Back_Buffer*, + // Set_Gamma, Flip_To_Primary, Clear, Set_Viewport, Set_Index_Buffer_Index_Offset, + // Get_Shader, Set_Material, Apply_Render_State_Changes, + // Apply_Default_State, Invalidate_Cached_Render_States, Set_Blend_*, + // Set_Color_Write_Enable, Set_Alpha_Blend_Enable, Show/Set_Hardware_Cursor*, + // Set_Stencil_*, Get_Transform, Set_Light*, Set/Get_Ambient, + // Set/Get_Fog*, Set/Get_Light_Environment, Draw_Strip, Set_Vertex_Shader, + // Set_Pixel_Shader, *_Constant, Create_Render_Target, + // Set_Render_Target_With_Z, Is_Render_To_Texture, Set/Get_Shadow_Map) + // is inherited from DX8Backend and forwards to DX8Wrapper unchanged. }; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h index f9db4b28578..c7cca86685b 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h @@ -238,6 +238,20 @@ class IRenderBackend const void * /*data*/, unsigned int /*size_bytes*/) {} + // TheSuperHackers @refactor bobtista 11/04/2026 dynamic + // capture hooks. Same pattern as above but for DynamicVBAccessClass / + // DynamicIBAccessClass. The data pointer and size describe just the + // sub-range the caller locked - not the entire dynamic ring buffer. + // BgfxBackend copies the sub-range into a per-frame transient buffer + // keyed by the access class pointer, so the next Set_Vertex_Buffer / + // Set_Index_Buffer call on the same access class instance picks it up. + virtual void Capture_Dynamic_Vertex_Data(const DynamicVBAccessClass * /*vba*/, + const void * /*data*/, + unsigned int /*size_bytes*/) {} + virtual void Capture_Dynamic_Index_Data(const DynamicIBAccessClass * /*iba*/, + const void * /*data*/, + unsigned int /*size_bytes*/) {} + // ------------------------------------------------------------------------- // State: shaders, materials, textures // ------------------------------------------------------------------------- diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.cpp index 204bd7464d8..f4f23c2eea4 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.cpp @@ -466,6 +466,13 @@ DynamicIBAccessClass::WriteLockClass::~WriteLockClass() DX8_THREAD_ASSERT(); switch (DynamicIBAccess->Get_Type()) { case BUFFER_TYPE_DYNAMIC_DX8: + // TheSuperHackers @refactor bobtista 11/04/2026 + // write-side capture for bgfx backend. Copy locked sub-range + // into a bgfx transient IB before Unlock. + if (g_renderBackend != NULL && Indices != NULL) { + const unsigned int total_bytes = DynamicIBAccess->Get_Index_Count() * sizeof(unsigned short); + g_renderBackend->Capture_Dynamic_Index_Data(DynamicIBAccess, Indices, total_bytes); + } DX8_Assert(); DX8_ErrorCode(static_cast(DynamicIBAccess->IndexBuffer)->Get_DX8_Index_Buffer()->Unlock()); break; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp index 09499c29e90..3ab6a730a3b 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp @@ -901,6 +901,15 @@ DynamicVBAccessClass::WriteLockClass::~WriteLockClass() WWASSERT(!dx8_lock); WWDEBUG_SAY(("DynamicVertexBuffer->Unlock()")); #endif + // TheSuperHackers @refactor bobtista 11/04/2026 + // write-side capture for bgfx backend. Copy the locked sub-range + // into a bgfx transient VB before we Unlock. DX8Backend inherits + // an empty default so this is a no-op in the dx8 build. + if (g_renderBackend != NULL && Vertices != NULL) { + const unsigned int total_bytes = DynamicVBAccess->Get_Vertex_Count() * + DynamicVBAccess->VertexBuffer->FVF_Info().Get_FVF_Size(); + g_renderBackend->Capture_Dynamic_Vertex_Data(DynamicVBAccess, Vertices, total_bytes); + } DX8_Assert(); DX8_ErrorCode(static_cast(DynamicVBAccess->VertexBuffer)->Get_DX8_Vertex_Buffer()->Unlock()); break; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit.sc index b13a2fde3ec..089d676720c 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit.sc @@ -1,17 +1,24 @@ $input v_color0, v_texcoord0 // TheSuperHackers @refactor bobtista 11/04/2026 lit textured -// fragment shader. Samples stage 0 and multiplies by the interpolated -// vertex diffuse. Equivalent to D3DTOP_MODULATE with -// (D3DTA_TEXTURE, D3DTA_DIFFUSE) which is the most common preset in the -// game. +// fragment shader. Samples stages 0-3 and multiplies by the interpolated +// vertex diffuse. Stages 1-3 were added later so terrain and +// multi-textured surfaces show their detail/blend/noise layers. All +// stages default to a 1x1 white texture when unbound, so single-texture +// draws are unaffected (white * color = color). #include SAMPLER2D(s_tex0, 0); +SAMPLER2D(s_tex1, 1); +SAMPLER2D(s_tex2, 2); +SAMPLER2D(s_tex3, 3); void main() { - vec4 texColor = texture2D(s_tex0, v_texcoord0); - gl_FragColor = texColor * v_color0; + vec4 tex0 = texture2D(s_tex0, v_texcoord0); + vec4 tex1 = texture2D(s_tex1, v_texcoord0); + vec4 tex2 = texture2D(s_tex2, v_texcoord0); + vec4 tex3 = texture2D(s_tex3, v_texcoord0); + gl_FragColor = tex0 * tex1 * tex2 * tex3 * v_color0; } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit_atest.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit_atest.sc index 3aca275e252..f9bb42184e3 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit_atest.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit_atest.sc @@ -4,6 +4,8 @@ $input v_color0, v_texcoord0 // lit textured fragment shader. Same as fs_textured_lit but with a // discard when the sampled alpha falls below u_atestParams.x. Used by // the ATest* presets (trees, fences, see-through foliage). See +// Stages +// 1-3 to match the non-atest variants. // // u_atestParams: x = reference in [0,1]. The engine should write // ShaderClass::Get_Alpha_Reference() normalized by 255 into .x. @@ -16,12 +18,18 @@ $input v_color0, v_texcoord0 #include SAMPLER2D(s_tex0, 0); +SAMPLER2D(s_tex1, 1); +SAMPLER2D(s_tex2, 2); +SAMPLER2D(s_tex3, 3); uniform vec4 u_atestParams; void main() { - vec4 texColor = texture2D(s_tex0, v_texcoord0); - vec4 lit = texColor * v_color0; + vec4 tex0 = texture2D(s_tex0, v_texcoord0); + vec4 tex1 = texture2D(s_tex1, v_texcoord0); + vec4 tex2 = texture2D(s_tex2, v_texcoord0); + vec4 tex3 = texture2D(s_tex3, v_texcoord0); + vec4 lit = tex0 * tex1 * tex2 * tex3 * v_color0; if (lit.a < u_atestParams.x) { discard; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_unlit.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_unlit.sc index 8f38ad81bd4..43250a3c06c 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_unlit.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_unlit.sc @@ -1,15 +1,22 @@ $input v_texcoord0 // TheSuperHackers @refactor bobtista 11/04/2026 unlit textured -// fragment shader. Samples stage 0 straight to output. Used by -// Opaque2D / OpaqueSprite and friends (GRADIENT_DISABLE + TEXTURING_ENABLE -// presets). +// fragment shader. Samples stages 0-3 and multiplies them. Stages +// 4G.4 added stages 1-3 for multi-textured UI / sprites. Unused stages +// default to a 1x1 white texture so single-texture draws are unaffected. #include SAMPLER2D(s_tex0, 0); +SAMPLER2D(s_tex1, 1); +SAMPLER2D(s_tex2, 2); +SAMPLER2D(s_tex3, 3); void main() { - gl_FragColor = texture2D(s_tex0, v_texcoord0); + vec4 tex0 = texture2D(s_tex0, v_texcoord0); + vec4 tex1 = texture2D(s_tex1, v_texcoord0); + vec4 tex2 = texture2D(s_tex2, v_texcoord0); + vec4 tex3 = texture2D(s_tex3, v_texcoord0); + gl_FragColor = tex0 * tex1 * tex2 * tex3; } From 4290bcc5cc3224bd865f93ad7634ec7979f8c430 Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 21 Apr 2026 14:02:46 -0400 Subject: [PATCH 047/523] feat(bgfx): route mesh/sort/render2d/particles/segline draw pipeline through g_renderBackend (cherry picked from commit f39027dc11f9a4b31bbc1f09fdeb33bddc599388) --- .../Source/WWVegas/WW3D2/BgfxBackend.cpp | 933 +++++++++++++----- .../Source/WWVegas/WW3D2/BgfxBackend.h | 20 +- .../Source/WWVegas/WW3D2/IRenderBackend.h | 46 + .../Source/WWVegas/WW3D2/dx8indexbuffer.cpp | 20 +- .../Source/WWVegas/WW3D2/dx8indexbuffer.h | 5 + .../Source/WWVegas/WW3D2/dx8polygonrenderer.h | 8 +- .../Source/WWVegas/WW3D2/dx8renderer.cpp | 58 +- .../Source/WWVegas/WW3D2/dx8vertexbuffer.cpp | 28 +- .../Source/WWVegas/WW3D2/dx8vertexbuffer.h | 6 + .../Source/WWVegas/WW3D2/dx8wrapper.cpp | 44 +- .../Source/WWVegas/WW3D2/dynamesh.cpp | 36 +- .../Libraries/Source/WWVegas/WW3D2/line3d.cpp | 16 +- .../Source/WWVegas/WW3D2/matpass.cpp | 8 +- .../Source/WWVegas/WW3D2/pointgr.cpp | 38 +- .../Source/WWVegas/WW3D2/ringobj.cpp | 18 +- .../Source/WWVegas/WW3D2/seglinerenderer.cpp | 20 +- .../WWVegas/WW3D2/shaders/fs_solid_lit.sc | 10 +- .../WWVegas/WW3D2/shaders/fs_textured_lit.sc | 29 +- .../WW3D2/shaders/fs_textured_lit_atest.sc | 27 +- .../WW3D2/shaders/fs_textured_unlit.sc | 11 +- .../WWVegas/WW3D2/shaders/varying.def.sc | 2 + .../WWVegas/WW3D2/shaders/vs_solid_lit.sc | 5 +- .../WWVegas/WW3D2/shaders/vs_textured_lit.sc | 32 +- .../Source/WWVegas/WW3D2/sortingrenderer.cpp | 80 +- .../Source/WWVegas/WW3D2/sphereobj.cpp | 22 +- .../Source/WWVegas/WW3D2/streakRender.cpp | 20 +- .../Source/WWVegas/WW3D2/boxrobj.cpp | 9 +- .../Libraries/Source/WWVegas/WW3D2/dazzle.cpp | 16 +- .../Source/WWVegas/WW3D2/decalmsh.cpp | 14 +- .../Source/WWVegas/WW3D2/linegrp.cpp | 8 +- .../Libraries/Source/WWVegas/WW3D2/mesh.cpp | 16 +- 31 files changed, 1132 insertions(+), 473 deletions(-) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp index bd79e75d7da..2c7199850d8 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -127,6 +127,13 @@ bgfx::UniformHandle g_sTex0 = BGFX_INVALID_HANDLE; bgfx::UniformHandle g_sTex1 = BGFX_INVALID_HANDLE; bgfx::UniformHandle g_sTex2 = BGFX_INVALID_HANDLE; bgfx::UniformHandle g_sTex3 = BGFX_INVALID_HANDLE; +// TheSuperHackers @refactor bobtista 11/04/2026 material +// diffuse uniform. Carries the VertexMaterialClass::Get_Diffuse color +// + opacity from Set_Material into the fragment shader so team colors +// (which the W3D engine writes into the material diffuse channel) and +// alpha fades modulate the output. +bgfx::UniformHandle g_uMatDiffuse = BGFX_INVALID_HANDLE; +float g_currentMatDiffuse[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; // Alpha-test parameters consumed by fs_textured_lit_atest. .x is the // reference threshold in [0, 1]; engine writes ShaderClass alpha-ref / 255. // Named u_atestParams (not u_alphaRef) to avoid bgfx_shader.sh's internal @@ -537,9 +544,16 @@ bool BuildBgfxLayoutForFVF(const FVFInfoClass & fvf, bgfx::VertexLayout & out) // session; if it bites us we'll add a generation counter or hook the // destructor. -std::unordered_map g_vbCache; -std::unordered_map g_ibCache; -std::unordered_map g_textureCache; +// TheSuperHackers @refactor bobtista 11/04/2026 switched from +// static bgfx VB/IB handles to dynamic ones. Rigid mesh category containers +// fill their shared VB / IB one sub-range at a time via AppendLockClass, +// which requires in-place sub-range updates that only dynamic bgfx buffers +// support. Full-buffer writes (WriteLockClass) also go through the same +// dynamic path - created once, updated with bgfx::update as the engine +// rewrites the buffer. +std::unordered_map g_vbCache; +std::unordered_map g_ibCache; +std::unordered_map g_textureCache; // The bgfx texture currently bound to stage 0 by Set_Texture. Used by // SubmitEngineDraw - falls back to g_defaultWhiteTexture if invalid. @@ -552,9 +566,9 @@ bool g_loggedFirstBgfxTextureCreate = false; // The most recent buffers and offsets cached from Set_Vertex_Buffer / // Set_Index_Buffer. Read by Draw_Triangles when it issues the bgfx // submit. Cleared (made invalid) on Shutdown. -bgfx::VertexBufferHandle g_currentBgfxVB = BGFX_INVALID_HANDLE; -bgfx::IndexBufferHandle g_currentBgfxIB = BGFX_INVALID_HANDLE; -unsigned short g_currentIBOffset = 0; +bgfx::DynamicVertexBufferHandle g_currentBgfxVB = BGFX_INVALID_HANDLE; +bgfx::DynamicIndexBufferHandle g_currentBgfxIB = BGFX_INVALID_HANDLE; +unsigned short g_currentIBOffset = 0; // TheSuperHackers @refactor bobtista 11/04/2026 transient // (dynamic) buffer state. Capture_Dynamic_Vertex_Data allocs a bgfx @@ -617,6 +631,43 @@ bool g_loggedFirstSetWorld = false; // transforms. Both render to the popup back buffer. const bgfx::ViewId kBgfxEngineView = 1; +// TheSuperHackers @refactor bobtista 11/04/2026 dedicated +// view id for sorted draws. View 2's view matrix is permanently +// identity and its projection tracks view 1's. Per-batch sort +// transforms get pre-multiplied into g_bgfxSortWorld so view 2 never +// needs setViewTransform updates per batch - which is critical, +// because bgfx::setViewTransform is per-view-for-the-whole-frame and +// would otherwise stomp view 1's camera view if shared. +const bgfx::ViewId kBgfxEngineSortView = 2; + +// True between Begin_Sorted_Batch_Pass and End_Sorted_Batch_Pass; +// SubmitEngineDraw routes to kBgfxEngineSortView and uses +// g_bgfxSortWorld while this is set. +bool g_inSortFlush = false; + +// Per-batch effective world for sorted draws: the pre-multiplied +// sortView * sortWorld (in bgfx column-major form) captured from the +// engine's render_state by Capture_Sorted_Batch_Transforms. +float g_bgfxSortWorld[16]; + +// TheSuperHackers @refactor bobtista 11/04/2026 Set by +// Submit_Sorted_Draw after it emits the bgfx submit for a sorting VB +// direct draw. The outer BgfxBackend::Draw_Triangles consumes this +// flag to skip its SubmitEngineDraw - the draw was already issued +// with correctly remapped args against the inner dynamic buffers, +// so falling through would emit a second, incorrect submit. +bool g_skipNextSubmitEngineDraw = false; + + +// Snapshot of g_bgfxProj at the time the sort flush runs. The engine +// calls Set_Projection_Transform_With_Z_Bias multiple times per frame +// (camera, water reflections, shadows). The LAST call may use a tiny +// near-field frustum that clips all sort geometry. We capture the +// projection at sort-flush time (when it's still the camera projection) +// and re-apply it to view 2 at End_Scene time. +float g_bgfxSortProj[16]; +bool g_bgfxSortProjCaptured = false; + void IdentityMatrix(float * out) { out[0] = 1.0f; out[1] = 0.0f; out[2] = 0.0f; out[3] = 0.0f; @@ -692,155 +743,6 @@ void ApplyPopupAspectCorrection(float * proj) // at write-lock time. See the write-side capture comments below. constexpr bool kBgfxSkipBufferRead = true; -bgfx::VertexBufferHandle EnsureBgfxVertexBuffer(const VertexBufferClass * vb) -{ - if (vb == nullptr) - { - return BGFX_INVALID_HANDLE; - } - auto it = g_vbCache.find(vb); - if (it != g_vbCache.end()) - { - return it->second; - } - - if (kBgfxSkipBufferRead) - { - g_vbCache[vb] = BGFX_INVALID_HANDLE; - return BGFX_INVALID_HANDLE; - } - - if (vb->Type() != BUFFER_TYPE_DX8) - { - // Sorting / dynamic buffers handled by a different path; mark - // as cached-invalid so we do not retry every draw. - g_vbCache[vb] = BGFX_INVALID_HANDLE; - return BGFX_INVALID_HANDLE; - } - - bgfx::VertexLayout layout; - if (!BuildBgfxLayoutForFVF(vb->FVF_Info(), layout)) - { - WWDEBUG_SAY(("[BgfxBackend] EnsureBgfxVertexBuffer: layout build failed " - "for fvf=0x%08x", vb->FVF_Info().Get_FVF())); - g_vbCache[vb] = BGFX_INVALID_HANDLE; - return BGFX_INVALID_HANDLE; - } - - const unsigned vertexCount = vb->Get_Vertex_Count(); - const unsigned strideBytes = vb->FVF_Info().Get_FVF_Size(); - const unsigned totalBytes = vertexCount * strideBytes; - if (totalBytes == 0) - { - g_vbCache[vb] = BGFX_INVALID_HANDLE; - return BGFX_INVALID_HANDLE; - } - - DX8VertexBufferClass * dxVb = - const_cast(static_cast(vb)); - IDirect3DVertexBuffer8 * d3dVb = dxVb->Get_DX8_Vertex_Buffer(); - if (d3dVb == nullptr) - { - g_vbCache[vb] = BGFX_INVALID_HANDLE; - return BGFX_INVALID_HANDLE; - } - - BYTE * srcData = nullptr; - HRESULT hr = d3dVb->Lock(0, 0, &srcData, D3DLOCK_READONLY); - if (FAILED(hr) || srcData == nullptr) - { - WWDEBUG_SAY(("[BgfxBackend] EnsureBgfxVertexBuffer: d3d8 Lock failed hr=0x%08x", - static_cast(hr))); - g_vbCache[vb] = BGFX_INVALID_HANDLE; - return BGFX_INVALID_HANDLE; - } - - const bgfx::Memory * mem = bgfx::copy(srcData, totalBytes); - d3dVb->Unlock(); - - bgfx::VertexBufferHandle h = bgfx::createVertexBuffer(mem, layout); - g_vbCache[vb] = h; - - if (!g_loggedFirstBgfxVbCreate) - { - g_loggedFirstBgfxVbCreate = true; - WWDEBUG_SAY(("[BgfxBackend] EnsureBgfxVertexBuffer first create " - "fvf=0x%08x stride=%u verts=%u bytes=%u handle=%s", - vb->FVF_Info().Get_FVF(), - strideBytes, vertexCount, totalBytes, - bgfx::isValid(h) ? "ok" : "FAILED")); - } - return h; -} - -bgfx::IndexBufferHandle EnsureBgfxIndexBuffer(const IndexBufferClass * ib) -{ - if (ib == nullptr) - { - return BGFX_INVALID_HANDLE; - } - auto it = g_ibCache.find(ib); - if (it != g_ibCache.end()) - { - return it->second; - } - - if (kBgfxSkipBufferRead) - { - g_ibCache[ib] = BGFX_INVALID_HANDLE; - return BGFX_INVALID_HANDLE; - } - - if (ib->Type() != BUFFER_TYPE_DX8) - { - g_ibCache[ib] = BGFX_INVALID_HANDLE; - return BGFX_INVALID_HANDLE; - } - - const unsigned indexCount = ib->Get_Index_Count(); - const unsigned totalBytes = indexCount * sizeof(uint16_t); - if (totalBytes == 0) - { - g_ibCache[ib] = BGFX_INVALID_HANDLE; - return BGFX_INVALID_HANDLE; - } - - DX8IndexBufferClass * dxIb = - const_cast(static_cast(ib)); - IDirect3DIndexBuffer8 * d3dIb = dxIb->Get_DX8_Index_Buffer(); - if (d3dIb == nullptr) - { - g_ibCache[ib] = BGFX_INVALID_HANDLE; - return BGFX_INVALID_HANDLE; - } - - BYTE * srcData = nullptr; - HRESULT hr = d3dIb->Lock(0, 0, &srcData, D3DLOCK_READONLY); - if (FAILED(hr) || srcData == nullptr) - { - WWDEBUG_SAY(("[BgfxBackend] EnsureBgfxIndexBuffer: d3d8 Lock failed hr=0x%08x", - static_cast(hr))); - g_ibCache[ib] = BGFX_INVALID_HANDLE; - return BGFX_INVALID_HANDLE; - } - - const bgfx::Memory * mem = bgfx::copy(srcData, totalBytes); - d3dIb->Unlock(); - - bgfx::IndexBufferHandle h = bgfx::createIndexBuffer(mem); - g_ibCache[ib] = h; - - if (!g_loggedFirstBgfxIbCreate) - { - g_loggedFirstBgfxIbCreate = true; - WWDEBUG_SAY(("[BgfxBackend] EnsureBgfxIndexBuffer first create " - "indices=%u bytes=%u handle=%s", - indexCount, totalBytes, - bgfx::isValid(h) ? "ok" : "FAILED")); - } - return h; -} - // TheSuperHackers @refactor bobtista 11/04/2026 texture // capture. Unlike vertex buffers, W3D textures default to POOL_MANAGED, // which is safe to lock with D3DLOCK_READONLY on the Intel UHD driver. @@ -1097,7 +999,10 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) initArgs.type = bgfx::RendererType::Count; // auto-select (D3D11 on Windows) initArgs.resolution.width = static_cast(kBgfxWindowWidth); initArgs.resolution.height = static_cast(kBgfxWindowHeight); - initArgs.resolution.reset = BGFX_RESET_NONE; + // enable 4x MSAA on the bgfx framebuffer. Without this, + // polygon boundaries between terrain tiles, rock shorelines, and + // unit silhouettes alias hard at the popup's lower resolution. + initArgs.resolution.reset = BGFX_RESET_MSAA_X4; initArgs.platformData = pd; if (!bgfx::init(initArgs)) @@ -1111,6 +1016,15 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) g_bgfxInitialized = true; + // TheSuperHackers @refactor bobtista 11/04/2026 force + // a bgfx::reset() after init so the MSAA flag actually takes effect. + // On D3D11 bgfx needs an explicit reset call to rebuild the swapchain + // with a multi-sampled back buffer; setting resolution.reset in the + // Init struct alone isn't enough in every bgfx build. + bgfx::reset(static_cast(kBgfxWindowWidth), + static_cast(kBgfxWindowHeight), + BGFX_RESET_MSAA_X4); + // Configure view 0 to clear the debug window to a dark teal so it's // visually obvious bgfx is running and alive. View 0 holds the test // triangle (bring-up sentinel). @@ -1136,6 +1050,20 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) static_cast(kBgfxWindowWidth), static_cast(kBgfxWindowHeight)); + // TheSuperHackers @refactor bobtista 11/04/2026 sorted + // draws view. No clear (reuses view 1's color + depth so sorted + // particles z-test correctly against opaque geometry), same rect. + // View matrix is permanently identity; projection tracks view 1's + // via Set_Projection_Transform_With_Z_Bias. + bgfx::setViewClear(kBgfxEngineSortView, + BGFX_CLEAR_NONE, + 0x00000000, + 1.0f, + 0); + bgfx::setViewRect(kBgfxEngineSortView, 0, 0, + static_cast(kBgfxWindowWidth), + static_cast(kBgfxWindowHeight)); + // Default the cached transforms to identity until the engine writes // real values via Set_Transform. This keeps the first few engine // submits well-defined even if they fire before any matrices are @@ -1143,8 +1071,18 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) IdentityMatrix(g_bgfxWorld); IdentityMatrix(g_bgfxView); IdentityMatrix(g_bgfxProj); + IdentityMatrix(g_bgfxSortWorld); g_bgfxViewProjDirty = true; + // Sort view gets identity view + current projection. setViewTransform + // persists for the life of the bgfx view; we re-apply the projection + // in Set_Projection_Transform_With_Z_Bias whenever it changes. + { + float identityView[16]; + IdentityMatrix(identityView); + bgfx::setViewTransform(kBgfxEngineSortView, identityView, g_bgfxProj); + } + // TheSuperHackers @refactor bobtista 11/04/2026 create the // passthrough shader program and vertex layout so End_Scene can submit // a test triangle. If shader creation fails the backend still runs but @@ -1183,6 +1121,7 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) g_sTex1 = bgfx::createUniform("s_tex1", bgfx::UniformType::Sampler); g_sTex2 = bgfx::createUniform("s_tex2", bgfx::UniformType::Sampler); g_sTex3 = bgfx::createUniform("s_tex3", bgfx::UniformType::Sampler); + g_uMatDiffuse = bgfx::createUniform("u_matDiffuse", bgfx::UniformType::Vec4); g_uAtestParams = bgfx::createUniform("u_atestParams", bgfx::UniformType::Vec4); // default 1x1 white texture (opaque ABGR=0xffffffff). @@ -1322,6 +1261,11 @@ void BgfxBackend::Shutdown() bgfx::destroy(g_sTex3); g_sTex3 = BGFX_INVALID_HANDLE; } + if (bgfx::isValid(g_uMatDiffuse)) + { + bgfx::destroy(g_uMatDiffuse); + g_uMatDiffuse = BGFX_INVALID_HANDLE; + } if (bgfx::isValid(g_uAtestParams)) { bgfx::destroy(g_uAtestParams); @@ -1385,56 +1329,6 @@ void BgfxBackend::Shutdown() // / Get_Back_Buffer / Set_Gamma) are inherited from DX8Backend. // -- Frame lifecycle --------------------------------------------------------- -// -// TheSuperHackers @refactor bobtista 11/04/2026 Begin_Scene -// submits a rotating view setup for view 0. End_Scene submits a test -// triangle via the passthrough shader then calls bgfx::frame() to present. -// This is the first real bgfx draw call. Future work -// replace the test triangle with actual scene geometry submitted through -// the IRenderBackend draw methods. - -namespace -{ -void SubmitTestTriangle() -{ - if (!bgfx::isValid(g_passthroughProgram)) - { - return; - } - - if (bgfx::getAvailTransientVertexBuffer(3, g_triangleLayout) < 3) - { - return; - } - - // Explicitly set view 0's transforms to identity each frame. Without - // this, u_modelViewProj in the vertex shader is whatever bgfx happens - // to have from a previous view transform call (undefined on first use). - static const float identity[16] = { - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; - bgfx::setViewTransform(0, identity, identity); - - bgfx::TransientVertexBuffer tvb; - bgfx::allocTransientVertexBuffer(&tvb, 3, g_triangleLayout); - TriangleVertex * verts = reinterpret_cast(tvb.data); - - // Triangle in clip space. Z = 0.5 puts it safely inside D3D11's [0,1] - // depth range away from both near and far clip planes. Format of the - // color field is ABGR packed (bgfx convention): 0xAABBGGRR as a u32. - verts[0].x = 0.0f; verts[0].y = 0.5f; verts[0].z = 0.5f; verts[0].abgr = 0xff0000ff; // red top - verts[1].x = 0.5f; verts[1].y = -0.5f; verts[1].z = 0.5f; verts[1].abgr = 0xff00ff00; // green right - verts[2].x = -0.5f; verts[2].y = -0.5f; verts[2].z = 0.5f; verts[2].abgr = 0xffff0000; // blue left - - bgfx::setVertexBuffer(0, &tvb); - // No culling — eliminate winding-order confusion while diagnosing. - bgfx::setState(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); - bgfx::submit(0, g_passthroughProgram); -} -} void BgfxBackend::Begin_Scene() { @@ -1443,6 +1337,8 @@ void BgfxBackend::Begin_Scene() return; } bgfx::touch(0); + bgfx::touch(kBgfxEngineView); + bgfx::touch(kBgfxEngineSortView); } void BgfxBackend::End_Scene(bool /*flip_frame*/) @@ -1451,7 +1347,20 @@ void BgfxBackend::End_Scene(bool /*flip_frame*/) { return; } - SubmitTestTriangle(); + // Re-apply the camera projection to the sort view. The engine calls + // Set_Projection_Transform_With_Z_Bias multiple times per frame + // (camera, water reflections, shadows). The last call often uses a + // tiny near-field frustum that clips all sort geometry. Since bgfx's + // setViewTransform is retroactive for the whole frame, we re-apply + // the projection that was active at sort-flush time. + if (g_bgfxSortProjCaptured) + { + float identityView[16]; + IdentityMatrix(identityView); + bgfx::setViewTransform(kBgfxEngineSortView, identityView, g_bgfxSortProj); + g_bgfxSortProjCaptured = false; + } + bgfx::frame(); // Transient buffers are freed at bgfx::frame time. Invalidate the @@ -1471,17 +1380,6 @@ void BgfxBackend::Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int s { DX8Backend::Set_Vertex_Buffer(vb, stream); - if (!g_loggedFirstSetVertexBuffer && vb != nullptr) - { - g_loggedFirstSetVertexBuffer = true; - const FVFInfoClass & fvf = vb->FVF_Info(); - WWDEBUG_SAY(("[BgfxBackend] Set_Vertex_Buffer first call (static VB path) " - "fvf=0x%08x size=%u verts=%u type=%u", - fvf.Get_FVF(), - fvf.Get_FVF_Size(), - vb->Get_Vertex_Count(), - vb->Type())); - } // The cache is populated by Capture_Vertex_Data on the engine's // own write lock. Set_Vertex_Buffer just looks up whatever is already // there. Engine VBs that have not been written via the WriteLockClass @@ -1496,6 +1394,37 @@ void BgfxBackend::Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int s else { g_currentBgfxVB = BGFX_INVALID_HANDLE; + // On-demand capture for static VBs that were never captured + // via WriteLockClass (e.g. rotor meshes loaded at startup). + // Lock the D3D VB, copy the data into a bgfx dynamic VB, and + // cache it for future use. + if (vb != nullptr && g_bgfxInitialized + && (vb->Type() == BUFFER_TYPE_DX8)) + { + DX8VertexBufferClass * dx8vb = + static_cast( + const_cast(vb)); + IDirect3DVertexBuffer8 * d3dvb = dx8vb->Get_DX8_Vertex_Buffer(); + if (d3dvb != nullptr) + { + const unsigned int stride = vb->FVF_Info().Get_FVF_Size(); + const unsigned int num_verts = vb->Get_Vertex_Count(); + const unsigned int bytes = num_verts * stride; + BYTE * data = nullptr; + HRESULT hr = d3dvb->Lock(0, bytes, &data, D3DLOCK_READONLY); + if (SUCCEEDED(hr) && data != nullptr) + { + Capture_Vertex_Data(vb, data, bytes); + d3dvb->Unlock(); + // Re-lookup after capture + auto it2 = g_vbCache.find(vb); + if (it2 != g_vbCache.end()) + { + g_currentBgfxVB = it2->second; + } + } + } + } } } @@ -1540,6 +1469,32 @@ void BgfxBackend::Set_Index_Buffer(const IndexBufferClass * ib, unsigned short i else { g_currentBgfxIB = BGFX_INVALID_HANDLE; + // On-demand capture for static IBs not yet in cache. + if (ib != nullptr && g_bgfxInitialized + && (ib->Type() == BUFFER_TYPE_DX8)) + { + DX8IndexBufferClass * dx8ib = + static_cast( + const_cast(ib)); + IDirect3DIndexBuffer8 * d3dib = dx8ib->Get_DX8_Index_Buffer(); + if (d3dib != nullptr) + { + const unsigned int num_indices = ib->Get_Index_Count(); + const unsigned int bytes = num_indices * sizeof(unsigned short); + BYTE * data = nullptr; + HRESULT hr = d3dib->Lock(0, bytes, &data, D3DLOCK_READONLY); + if (SUCCEEDED(hr) && data != nullptr) + { + Capture_Index_Data(ib, data, bytes); + d3dib->Unlock(); + auto it2 = g_ibCache.find(ib); + if (it2 != g_ibCache.end()) + { + g_currentBgfxIB = it2->second; + } + } + } + } } g_currentIBOffset = index_base_offset; } @@ -1562,8 +1517,20 @@ void BgfxBackend::Set_Index_Buffer(const DynamicIBAccessClass & iba, unsigned sh g_currentIBOffset = index_base_offset; } -// Set_Index_Buffer_Index_Offset is inherited from DX8Backend so the dx8 -// device sees the binding. +// TheSuperHackers @refactor bobtista 11/04/2026 override +// Set_Index_Buffer_Index_Offset so we capture the per-mesh base vertex +// offset. DX8PolygonRendererClass::Render calls this once per mesh +// before Draw_Triangles to shift which vertex slot in the shared +// category VB each index resolves to. Without this override the call +// forwards to DX8Backend -> DX8Wrapper and the bgfx path keeps using +// the stale offset from Set_Index_Buffer, so every mesh inside the +// same rigid FVF category would draw using the first mesh's vertex +// slots. Must call the base so the dx8 device still gets the update. +void BgfxBackend::Set_Index_Buffer_Index_Offset(unsigned int offset) +{ + DX8Backend::Set_Index_Buffer_Index_Offset(offset); + g_currentIBOffset = static_cast(offset); +} // -- write-side capture ------------------------------------------- // @@ -1579,45 +1546,79 @@ void BgfxBackend::Set_Index_Buffer(const DynamicIBAccessClass & iba, unsigned sh // lock, which the engine has to do anyway and which the driver handles // correctly because it is a real WRITE lock. -void BgfxBackend::Capture_Vertex_Data(const VertexBufferClass * vb, - const void * data, - unsigned int size_bytes) +namespace { - if (!g_bgfxInitialized || vb == nullptr || data == nullptr || size_bytes == 0) +// TheSuperHackers @refactor bobtista 11/04/2026 dynamic buffer +// ensure helpers. Return the cached dynamic VB / IB handle for the given +// engine buffer, creating it sized to the full capacity on first sight. +// Returned handle is guaranteed valid on success; invalid handle on +// failure. Used by both the full-buffer (WriteLockClass) and sub-range +// (AppendLockClass) capture paths. +bgfx::DynamicVertexBufferHandle EnsureDynamicVertexBuffer(const VertexBufferClass * vb) +{ + auto it = g_vbCache.find(vb); + if (it != g_vbCache.end() && bgfx::isValid(it->second)) { - return; + return it->second; } - bgfx::VertexLayout layout; if (!BuildBgfxLayoutForFVF(vb->FVF_Info(), layout)) { - return; + return BGFX_INVALID_HANDLE; + } + const uint32_t num_verts = static_cast(vb->Get_Vertex_Count()); + if (num_verts == 0) + { + return BGFX_INVALID_HANDLE; } + bgfx::DynamicVertexBufferHandle h = bgfx::createDynamicVertexBuffer(num_verts, layout); + g_vbCache[vb] = h; + return h; +} - // Replace any prior cached handle for the same VB pointer. The engine - // can rewrite a VB's contents (e.g. dynamic terrain or animated meshes - // that go through the same VertexBufferClass instance over its lifetime), - // so the new write supersedes whatever bgfx had. - auto it = g_vbCache.find(vb); - if (it != g_vbCache.end() && bgfx::isValid(it->second)) +bgfx::DynamicIndexBufferHandle EnsureDynamicIndexBuffer(const IndexBufferClass * ib) +{ + auto it = g_ibCache.find(ib); + if (it != g_ibCache.end() && bgfx::isValid(it->second)) { - bgfx::destroy(it->second); + return it->second; } + const uint32_t num_indices = static_cast(ib->Get_Index_Count()); + if (num_indices == 0) + { + return BGFX_INVALID_HANDLE; + } + bgfx::DynamicIndexBufferHandle h = bgfx::createDynamicIndexBuffer(num_indices); + g_ibCache[ib] = h; + return h; +} +} +void BgfxBackend::Capture_Vertex_Data(const VertexBufferClass * vb, + const void * data, + unsigned int size_bytes) +{ + if (!g_bgfxInitialized || vb == nullptr || data == nullptr || size_bytes == 0) + { + return; + } + bgfx::DynamicVertexBufferHandle h = EnsureDynamicVertexBuffer(vb); + if (!bgfx::isValid(h)) + { + return; + } const bgfx::Memory * mem = bgfx::copy(data, size_bytes); - bgfx::VertexBufferHandle h = bgfx::createVertexBuffer(mem, layout); - g_vbCache[vb] = h; + bgfx::update(h, 0, mem); if (!g_loggedFirstBgfxVbCreate) { g_loggedFirstBgfxVbCreate = true; - WWDEBUG_SAY(("[BgfxBackend] Capture_Vertex_Data first create " - "fvf=0x%08x stride=%u verts=%u bytes=%u handle=%s", + WWDEBUG_SAY(("[BgfxBackend] Capture_Vertex_Data first update " + "fvf=0x%08x stride=%u verts=%u bytes=%u", vb->FVF_Info().Get_FVF(), vb->FVF_Info().Get_FVF_Size(), vb->Get_Vertex_Count(), - size_bytes, - bgfx::isValid(h) ? "ok" : "FAILED")); + size_bytes)); } } @@ -1629,28 +1630,286 @@ void BgfxBackend::Capture_Index_Data(const IndexBufferClass * ib, { return; } - - auto it = g_ibCache.find(ib); - if (it != g_ibCache.end() && bgfx::isValid(it->second)) + bgfx::DynamicIndexBufferHandle h = EnsureDynamicIndexBuffer(ib); + if (!bgfx::isValid(h)) { - bgfx::destroy(it->second); + return; } - const bgfx::Memory * mem = bgfx::copy(data, size_bytes); - bgfx::IndexBufferHandle h = bgfx::createIndexBuffer(mem); - g_ibCache[ib] = h; + bgfx::update(h, 0, mem); if (!g_loggedFirstBgfxIbCreate) { g_loggedFirstBgfxIbCreate = true; - WWDEBUG_SAY(("[BgfxBackend] Capture_Index_Data first create " - "indices=%u bytes=%u handle=%s", + WWDEBUG_SAY(("[BgfxBackend] Capture_Index_Data first update " + "indices=%u bytes=%u", ib->Get_Index_Count(), - size_bytes, - bgfx::isValid(h) ? "ok" : "FAILED")); + size_bytes)); } } +void BgfxBackend::Capture_Vertex_Sub_Range(const VertexBufferClass * vb, + const void * data, + unsigned int start_vertex, + unsigned int size_bytes) +{ + if (!g_bgfxInitialized || vb == nullptr || data == nullptr || size_bytes == 0) + { + return; + } + bgfx::DynamicVertexBufferHandle h = EnsureDynamicVertexBuffer(vb); + if (!bgfx::isValid(h)) + { + return; + } + const bgfx::Memory * mem = bgfx::copy(data, size_bytes); + bgfx::update(h, start_vertex, mem); + +} + +void BgfxBackend::Capture_Index_Sub_Range(const IndexBufferClass * ib, + const void * data, + unsigned int start_index, + unsigned int size_bytes) +{ + if (!g_bgfxInitialized || ib == nullptr || data == nullptr || size_bytes == 0) + { + return; + } + bgfx::DynamicIndexBufferHandle h = EnsureDynamicIndexBuffer(ib); + if (!bgfx::isValid(h)) + { + return; + } + const bgfx::Memory * mem = bgfx::copy(data, size_bytes); + bgfx::update(h, start_index, mem); + +} + +// TheSuperHackers @refactor bobtista 11/04/2026 sorted +// draw pass routing. The sort flush calls Begin / Capture / End +// around its per-batch draw loop. Begin flips the routing flag so +// SubmitEngineDraw pushes submits into kBgfxEngineSortView using +// g_bgfxSortWorld; End flips it back. Capture computes the per-batch +// effective world matrix and stores it in bgfx column-major form. +// +// Matrix math: W3D Matrix4x4 is row-major with row vectors, so the +// engine pipeline is v' = v * sortWorld * sortView. In column-vector +// form that is (sortView^T * sortWorld^T) * v, i.e. bgfx's world +// matrix must be sortView^T * sortWorld^T. We compute the combined +// row-major product sortWorld * sortView directly into bgfx +// column-major layout (the transpose is baked into the index +// pattern), avoiding an intermediate copy. +static bool g_loggedFirstSortBegin = false; +static bool g_loggedFirstSortCapture = false; + +void BgfxBackend::Begin_Sorted_Batch_Pass() +{ + g_inSortFlush = true; + + // Capture the projection NOW (while it's still the camera projection). + // Later Set_Projection calls (water, shadows) may overwrite g_bgfxProj + // with a tiny frustum. We re-apply this saved projection at End_Scene. + if (!g_bgfxSortProjCaptured) + { + std::memcpy(g_bgfxSortProj, g_bgfxProj, sizeof(g_bgfxSortProj)); + g_bgfxSortProjCaptured = true; + } + + if (!g_loggedFirstSortBegin) + { + g_loggedFirstSortBegin = true; + WWDEBUG_SAY(("[BgfxBackend] Begin_Sorted_Batch_Pass first fire " + "transVB=%d transIB=%d", + int(g_currentUseTransientVB), + int(g_currentUseTransientIB))); + } +} + +void BgfxBackend::End_Sorted_Batch_Pass() +{ + g_inSortFlush = false; +} + +void BgfxBackend::Capture_Sorted_Batch_Transforms(const Matrix4x4 & sortWorld, + const Matrix4x4 & sortView) +{ + // Compute the D3D row-major product sortWorld * sortView, then store + // it as row-major float[16] (r*4+c). bgfx on D3D11 interprets the + // raw bytes as column-major HLSL float4x4, which makes mul(M, v) use + // the ROWS of our stored matrix — matching D3D's row-vector convention. + // The previous [c*4+r] storage put the translation at the W component + // of each column (indices 3,7,11) instead of row 3 (indices 12,13,14), + // which works for identity transforms (particles) but breaks for mesh + // transforms with translation (helicopter rotors). + for (int r = 0; r < 4; ++r) + { + for (int c = 0; c < 4; ++c) + { + float s = 0.0f; + for (int k = 0; k < 4; ++k) + { + s += sortWorld[r][k] * sortView[k][c]; + } + g_bgfxSortWorld[r * 4 + c] = s; + } + } + + if (!g_loggedFirstSortCapture) + { + g_loggedFirstSortCapture = true; + WWDEBUG_SAY(("[BgfxBackend] Capture_Sorted_Batch_Transforms first " + "sortWorld row0=%.3f,%.3f,%.3f,%.3f sortView row0=%.3f,%.3f,%.3f,%.3f", + sortWorld[0][0], sortWorld[0][1], sortWorld[0][2], sortWorld[0][3], + sortView[0][0], sortView[0][1], sortView[0][2], sortView[0][3])); + WWDEBUG_SAY(("[BgfxBackend] combined (column-major) col0=%.3f,%.3f,%.3f,%.3f col3=%.3f,%.3f,%.3f,%.3f", + g_bgfxSortWorld[0], g_bgfxSortWorld[1], g_bgfxSortWorld[2], g_bgfxSortWorld[3], + g_bgfxSortWorld[12], g_bgfxSortWorld[13], g_bgfxSortWorld[14], g_bgfxSortWorld[15])); + } +} + +// TheSuperHackers @refactor bobtista 11/04/2026 sorted VB +// direct-draw submit. Called from DX8Wrapper::Draw_Sorting_IB_VB after +// it populates an internal dynamic VB and dynamic IB by copying a slice +// of the sorting VB/IB with the correct vba_offset / iba_offset / +// index_base_offset / min_vertex_index arithmetic. The engine's lock +// destructors already fired Capture_Dynamic_* for those inner buffers, +// so g_pendingDynVB / g_pendingDynIB hold their transients keyed by +// the exact access-class pointers we were just handed. +// +// We pull those transients into local draw state, emit a single bgfx +// submit to the sorted view with the inner buffers and remapped args +// (start_index=0, min_vertex_index=0, count relative to the inner +// buffers), and set g_skipNextSubmitEngineDraw so the outer Draw_Triangles +// does not emit a second, incorrect submit using the old sorting-VB +// args. +static bool g_loggedFirstSortedDirectSubmit = false; + +void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, + const DynamicIBAccessClass & dyn_ib, + unsigned short polygon_count, + unsigned short vertex_count) +{ + if (!g_bgfxInitialized) + { + return; + } + + // The inner dynamic buffers' WriteLockClass dtors already ran, so + // Capture_Dynamic_Vertex_Data / Capture_Dynamic_Index_Data should + // have stashed their transients keyed by &dyn_vb / &dyn_ib. + if (!g_pendingDynVB.valid || g_pendingDynVB.owner != &dyn_vb) + { + if (!g_loggedFirstSortedDirectSubmit) + { + g_loggedFirstSortedDirectSubmit = true; + WWDEBUG_SAY(("[BgfxBackend] Submit_Sorted_Draw SKIP: pendingDynVB not " + "claimable (valid=%d ownerMatch=%d)", + int(g_pendingDynVB.valid), + int(g_pendingDynVB.owner == &dyn_vb))); + } + return; + } + if (!g_pendingDynIB.valid || g_pendingDynIB.owner != &dyn_ib) + { + if (!g_loggedFirstSortedDirectSubmit) + { + g_loggedFirstSortedDirectSubmit = true; + WWDEBUG_SAY(("[BgfxBackend] Submit_Sorted_Draw SKIP: pendingDynIB not " + "claimable (valid=%d ownerMatch=%d)", + int(g_pendingDynIB.valid), + int(g_pendingDynIB.owner == &dyn_ib))); + } + return; + } + + const bgfx::TransientVertexBuffer vb = g_pendingDynVB.tvb; + const bgfx::TransientIndexBuffer ib = g_pendingDynIB.tib; + g_pendingDynVB.valid = false; + g_pendingDynIB.valid = false; + + if (!bgfx::isValid(g_currentBgfxProgram)) + { + g_skipNextSubmitEngineDraw = true; + return; + } + + // Sort view's view+proj were set up at init (identity view, + // projection tracks opaque view via Set_Projection_Transform_With_Z_Bias). + // World is the current g_bgfxSortWorld if we are inside a sort batch, + // otherwise the regular g_bgfxWorld (rigid FVF category with sorting=true + // has no batch-wrapped Apply_Render_State - it uses the per-mesh world + // set by the caller via g_renderBackend->Set_Transform). + const float * worldMtx = g_inSortFlush ? g_bgfxSortWorld : g_bgfxWorld; + bgfx::setTransform(worldMtx); + + bgfx::setVertexBuffer(0, &vb, 0, vertex_count); + bgfx::setIndexBuffer(&ib, 0, static_cast(polygon_count) * 3); + + const uint32_t kSamplerFlags = 0; + if (bgfx::isValid(g_sTex0)) + { + const bgfx::TextureHandle bound = + bgfx::isValid(g_currentBgfxTexture0) ? g_currentBgfxTexture0 : g_defaultWhiteTexture; + if (bgfx::isValid(bound)) + { + bgfx::setTexture(0, g_sTex0, bound, kSamplerFlags); + } + } + if (bgfx::isValid(g_sTex1)) + { + const bgfx::TextureHandle bound = + bgfx::isValid(g_currentBgfxTexture1) ? g_currentBgfxTexture1 : g_defaultWhiteTexture; + if (bgfx::isValid(bound)) + { + bgfx::setTexture(1, g_sTex1, bound, kSamplerFlags); + } + } + if (bgfx::isValid(g_sTex2)) + { + const bgfx::TextureHandle bound = + bgfx::isValid(g_currentBgfxTexture2) ? g_currentBgfxTexture2 : g_defaultWhiteTexture; + if (bgfx::isValid(bound)) + { + bgfx::setTexture(2, g_sTex2, bound, kSamplerFlags); + } + } + if (bgfx::isValid(g_sTex3)) + { + const bgfx::TextureHandle bound = + bgfx::isValid(g_currentBgfxTexture3) ? g_currentBgfxTexture3 : g_defaultWhiteTexture; + if (bgfx::isValid(bound)) + { + bgfx::setTexture(3, g_sTex3, bound, kSamplerFlags); + } + } + + if (bgfx::isValid(g_uMatDiffuse)) + { + bgfx::setUniform(g_uMatDiffuse, g_currentMatDiffuse); + } + + uint64_t state = (g_currentBgfxState != 0) + ? g_currentBgfxState + : (BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); + + bgfx::setState(state); + + bgfx::submit(kBgfxEngineSortView, g_currentBgfxProgram); + + if (!g_loggedFirstSortedDirectSubmit) + { + g_loggedFirstSortedDirectSubmit = true; + WWDEBUG_SAY(("[BgfxBackend] Submit_Sorted_Draw first fire " + "(polys=%u verts=%u worldCol3=%.2f,%.2f,%.2f state=0x%016llx)", + polygon_count, vertex_count, + worldMtx[12], worldMtx[13], worldMtx[14], + static_cast(state))); + } + + g_skipNextSubmitEngineDraw = true; +} + // TheSuperHackers @refactor bobtista 11/04/2026 dynamic // capture. DynamicVBAccessClass / DynamicIBAccessClass are CPU-side // views onto a ring buffer that changes every frame (particles, sprites, @@ -1707,6 +1966,7 @@ void BgfxBackend::Capture_Dynamic_Vertex_Data(const DynamicVBAccessClass * vba, num_verts, bytes)); } + } void BgfxBackend::Capture_Dynamic_Index_Data(const DynamicIBAccessClass * iba, @@ -1744,6 +2004,7 @@ void BgfxBackend::Capture_Dynamic_Index_Data(const DynamicIBAccessClass * iba, num_indices, bytes)); } + } // -- State: shaders, materials, textures ------------------------------------ @@ -1772,6 +2033,34 @@ void BgfxBackend::Set_Shader(const ShaderClass & shader) } } +void BgfxBackend::Set_Material(const VertexMaterialClass * material) +{ + DX8Backend::Set_Material(material); + + // TheSuperHackers @refactor bobtista 11/04/2026 capture + // material diffuse + opacity so the fragment shader can tint output + // with team colors. Generals writes the player color into the + // VertexMaterialClass diffuse channel; without this override bgfx + // meshes render with stale or default colors and units are either + // washed-out white or tinted by whatever the previous draw used. + if (material != nullptr) + { + Vector3 diffuse(1.0f, 1.0f, 1.0f); + material->Get_Diffuse(&diffuse); + g_currentMatDiffuse[0] = diffuse.X; + g_currentMatDiffuse[1] = diffuse.Y; + g_currentMatDiffuse[2] = diffuse.Z; + g_currentMatDiffuse[3] = material->Get_Opacity(); + } + else + { + g_currentMatDiffuse[0] = 1.0f; + g_currentMatDiffuse[1] = 1.0f; + g_currentMatDiffuse[2] = 1.0f; + g_currentMatDiffuse[3] = 1.0f; + } +} + void BgfxBackend::Set_Texture(unsigned int stage, TextureBaseClass * texture) { DX8Backend::Set_Texture(stage, texture); @@ -1795,7 +2084,7 @@ void BgfxBackend::Set_Texture(unsigned int stage, TextureBaseClass * texture) } } -// Get_Shader, Set_Material, Apply_Render_State_Changes, Apply_Default_State, +// Get_Shader, Apply_Render_State_Changes, Apply_Default_State, // Invalidate_Cached_Render_States, Set_Blend_Op, Set_Blend_Factors, // Set_Color_Write_Enable, Set_Alpha_Blend_Enable, hardware cursor, and // Set_Stencil_* are all inherited from DX8Backend and forward unchanged @@ -1903,12 +2192,39 @@ void BgfxBackend::Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & matrix, W3DMatrix4ToBgfx(matrix, g_bgfxProj); ApplyPopupAspectCorrection(g_bgfxProj); g_bgfxViewProjDirty = true; + + // View 2's projection is now set at End_Scene time from the saved + // sort-flush projection (g_bgfxSortProj). This avoids the issue + // where later Set_Projection calls (water, shadows) overwrite view + // 2's projection with a tiny near-field frustum. if (!g_loggedFirstSetProj) { g_loggedFirstSetProj = true; WWDEBUG_SAY(("[BgfxBackend] Set_Projection_Transform_With_Z_Bias first call " "row0=%.3f,%.3f,%.3f,%.3f", matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3])); + // Dump full bgfx column-major projection for Z debugging + WWDEBUG_SAY(("[BgfxBackend] PROJ bgfx col-major: " + "col0=%.4f,%.4f,%.4f,%.4f " + "col1=%.4f,%.4f,%.4f,%.4f " + "col2=%.4f,%.4f,%.4f,%.4f " + "col3=%.4f,%.4f,%.4f,%.4f", + g_bgfxProj[0], g_bgfxProj[1], g_bgfxProj[2], g_bgfxProj[3], + g_bgfxProj[4], g_bgfxProj[5], g_bgfxProj[6], g_bgfxProj[7], + g_bgfxProj[8], g_bgfxProj[9], g_bgfxProj[10], g_bgfxProj[11], + g_bgfxProj[12], g_bgfxProj[13], g_bgfxProj[14], g_bgfxProj[15])); + // Manual clip-space computation for a known sort vertex + // v = (-170.29, -5.09, -499.62, 1.0) + float vx=-170.29f, vy=-5.09f, vz=-499.62f, vw=1.0f; + float cx = g_bgfxProj[0]*vx + g_bgfxProj[4]*vy + g_bgfxProj[8]*vz + g_bgfxProj[12]*vw; + float cy = g_bgfxProj[1]*vx + g_bgfxProj[5]*vy + g_bgfxProj[9]*vz + g_bgfxProj[13]*vw; + float cz = g_bgfxProj[2]*vx + g_bgfxProj[6]*vy + g_bgfxProj[10]*vz + g_bgfxProj[14]*vw; + float cw = g_bgfxProj[3]*vx + g_bgfxProj[7]*vy + g_bgfxProj[11]*vz + g_bgfxProj[15]*vw; + WWDEBUG_SAY(("[BgfxBackend] PROJ TEST: clip=%.2f,%.2f,%.2f,%.2f ndc=%.4f,%.4f,%.4f", + cx, cy, cz, cw, + cw != 0.0f ? cx/cw : 0.0f, + cw != 0.0f ? cy/cw : 0.0f, + cw != 0.0f ? cz/cw : 0.0f)); } } @@ -1953,10 +2269,19 @@ void SubmitEngineDraw(unsigned short start_index, return; } + // Route to the dedicated sorted view when the sort + // flush has activated it. The sort view's view+proj were set at + // init and are refreshed by Set_Projection_Transform_With_Z_Bias, + // so it never needs a per-submit setViewTransform - only view 1 + // (the opaque view) uses the dirty flag. + const bgfx::ViewId submitView = g_inSortFlush ? kBgfxEngineSortView : kBgfxEngineView; + const float * worldMtx = g_inSortFlush ? g_bgfxSortWorld : g_bgfxWorld; + // Push the engine view+projection when they change. setViewTransform // applies until the next change so we do not need to call it per - // submit, only when the engine has updated either matrix. - if (g_bgfxViewProjDirty) + // submit, only when the engine has updated either matrix. Sort view + // draws never touch g_bgfxView so the opaque view is never stomped. + if (!g_inSortFlush && g_bgfxViewProjDirty) { bgfx::setViewTransform(kBgfxEngineView, g_bgfxView, g_bgfxProj); g_bgfxViewProjDirty = false; @@ -1964,15 +2289,44 @@ void SubmitEngineDraw(unsigned short start_index, // World matrix is per-submit. bgfx consumes the value at submit time // and resets the per-draw transform after each submit. - bgfx::setTransform(g_bgfxWorld); - + bgfx::setTransform(worldMtx); + + // TheSuperHackers @refactor bobtista 11/04/2026 offset + // semantics. DX8Wrapper::Set_Index_Buffer_Index_Offset and + // Set_Vertex_Buffer(DynamicVBAccessClass) set a d3d8 BaseVertexIndex + // (passed to SetIndices). d3d8 implicitly adds that value to every + // index during the draw, i.e. effective_vertex = VB[IB[start+i] + + // base]. The bgfx equivalent is setVertexBuffer's _startVertex + // parameter, NOT an IB offset. Previously SubmitEngineDraw was + // incorrectly adding g_currentIBOffset to the IB start - that + // shifted which indices were read instead of which vertex they + // resolved to, so meshes with a non-zero base_vertex_offset drew + // garbled geometry from other meshes' vertex data. + const uint32_t base_vertex = static_cast(g_currentIBOffset); if (g_currentUseTransientVB) { - bgfx::setVertexBuffer(0, &g_currentTransientVB, min_vertex_index, vertex_count); + if (g_inSortFlush) + { + // Sort flush: use 2-arg overload (binds entire buffer). + // Sort indices are absolute offsets into the full transient, + // so no base vertex offset is needed. The 4-arg overload + // would restrict the vertex range and clip high indices. + bgfx::setVertexBuffer(0, &g_currentTransientVB); + } + else + { + // Skin/dynamic draws: apply base_vertex as startVertex. + // Each mesh part within the shared transient VB has a + // different base offset (from Set_Index_Buffer_Index_Offset). + // Without this, all mesh parts read from vertex 0 and + // infantry/vehicles are invisible or garbled. + bgfx::setVertexBuffer(0, &g_currentTransientVB, + base_vertex, vertex_count); + } } else { - bgfx::setVertexBuffer(0, g_currentBgfxVB, min_vertex_index, vertex_count); + bgfx::setVertexBuffer(0, g_currentBgfxVB, base_vertex, vertex_count); } if (g_currentUseTransientIB) @@ -1984,7 +2338,7 @@ void SubmitEngineDraw(unsigned short start_index, else { bgfx::setIndexBuffer(g_currentBgfxIB, - static_cast(g_currentIBOffset) + start_index, + start_index, static_cast(polygon_count) * 3); } @@ -1995,6 +2349,21 @@ void SubmitEngineDraw(unsigned short start_index, // is migrated. For stage 1, white is also the multiplicative // identity, so single-texture draws (which don't bind stage 1) are // unaffected by the second sample. + // + // force trilinear filtering on all sampler stages. + // DX8Wrapper normally sets per-stage MIN/MAG/MIP filters via + // Set_DX8_Texture_Stage_State which bypasses g_renderBackend, so + // the bgfx samplers use their creation-time default (often point). + // Jamming BGFX_SAMPLER_MIN_ANISOTROPIC | MAG_ANISOTROPIC | MIP_LINEAR + // at bind time makes diagonal terrain edges, rocks, and unit + // textures stop looking blocky. + // Pass 0 = use the sampler's creation-time defaults, which is + // linear min/mag + mip-linear (trilinear) if the texture has + // mipmaps. Passing an explicit MIP_POINT here was forcing nearest- + // neighbor mip selection, breaking mipmap smoothing and making + // distant terrain patches look blocky. Let bgfx pick the best + // filter it can for each texture. + const uint32_t kSamplerFlags = 0; if (bgfx::isValid(g_sTex0)) { const bgfx::TextureHandle bound = @@ -2003,7 +2372,7 @@ void SubmitEngineDraw(unsigned short start_index, : g_defaultWhiteTexture; if (bgfx::isValid(bound)) { - bgfx::setTexture(0, g_sTex0, bound); + bgfx::setTexture(0, g_sTex0, bound, kSamplerFlags); } } if (bgfx::isValid(g_sTex1)) @@ -2014,7 +2383,7 @@ void SubmitEngineDraw(unsigned short start_index, : g_defaultWhiteTexture; if (bgfx::isValid(bound)) { - bgfx::setTexture(1, g_sTex1, bound); + bgfx::setTexture(1, g_sTex1, bound, kSamplerFlags); } } if (bgfx::isValid(g_sTex2)) @@ -2025,7 +2394,7 @@ void SubmitEngineDraw(unsigned short start_index, : g_defaultWhiteTexture; if (bgfx::isValid(bound)) { - bgfx::setTexture(2, g_sTex2, bound); + bgfx::setTexture(2, g_sTex2, bound, kSamplerFlags); } } if (bgfx::isValid(g_sTex3)) @@ -2036,25 +2405,43 @@ void SubmitEngineDraw(unsigned short start_index, : g_defaultWhiteTexture; if (bgfx::isValid(bound)) { - bgfx::setTexture(3, g_sTex3, bound); + bgfx::setTexture(3, g_sTex3, bound, kSamplerFlags); } } + // push the material diffuse (team color + opacity) so + // the fragment shader can tint output. Updated by Set_Material. + if (bgfx::isValid(g_uMatDiffuse)) + { + bgfx::setUniform(g_uMatDiffuse, g_currentMatDiffuse); + } + // Use the cached state if it was built; otherwise fall back to a // sane default that at least lets the draw run. - const uint64_t state = (g_currentBgfxState != 0) + uint64_t state = (g_currentBgfxState != 0) ? g_currentBgfxState : (BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); + + // Disable depth test for sort view draws. Sort + // particles are pre-transformed to view space with identity + // world/view, so their Z values don't match the opaque view's + // depth buffer (which used the real camera view transform). + if (submitView == kBgfxEngineSortView) + { + state &= ~BGFX_STATE_DEPTH_TEST_MASK; + state |= BGFX_STATE_DEPTH_TEST_ALWAYS; + } + bgfx::setState(state); - bgfx::submit(kBgfxEngineView, g_currentBgfxProgram); + bgfx::submit(submitView, g_currentBgfxProgram); if (!g_loggedFirstBgfxSubmit) { g_loggedFirstBgfxSubmit = true; WWDEBUG_SAY(("[BgfxBackend] First engine bgfx::submit " "(view=%u start=%u polys=%u min_v=%u count=%u state=0x%016llx)", - static_cast(kBgfxEngineView), + static_cast(submitView), start_index, polygon_count, min_vertex_index, vertex_count, static_cast(state))); @@ -2075,6 +2462,15 @@ void BgfxBackend::Draw_Triangles(unsigned short start_index, WWDEBUG_SAY(("[BgfxBackend] Draw_Triangles first call (polys=%u verts=%u)", polygon_count, vertex_count)); } + + // If DX8Wrapper::Draw_Sorting_IB_VB already submitted + // the draw with correctly remapped args against its internal dynamic + // buffers, skip the outer submit. + if (g_skipNextSubmitEngineDraw) + { + g_skipNextSubmitEngineDraw = false; + return; + } SubmitEngineDraw(start_index, polygon_count, min_vertex_index, vertex_count); } @@ -2092,6 +2488,11 @@ void BgfxBackend::Draw_Triangles(unsigned int buffer_type, WWDEBUG_SAY(("[BgfxBackend] Draw_Triangles first call typed (polys=%u verts=%u)", polygon_count, vertex_count)); } + if (g_skipNextSubmitEngineDraw) + { + g_skipNextSubmitEngineDraw = false; + return; + } SubmitEngineDraw(start_index, polygon_count, min_vertex_index, vertex_count); } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h index 0a4d3ee45a4..c6ef15ddf86 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h @@ -68,6 +68,7 @@ class BgfxBackend : public DX8Backend virtual void Set_Vertex_Buffer(const DynamicVBAccessClass & vba); virtual void Set_Index_Buffer(const IndexBufferClass * ib, unsigned short index_base_offset); virtual void Set_Index_Buffer(const DynamicIBAccessClass & iba, unsigned short index_base_offset); + virtual void Set_Index_Buffer_Index_Offset(unsigned int offset); // write-side capture hooks. DX8Backend inherits the // empty default from IRenderBackend; BgfxBackend captures the data @@ -87,6 +88,22 @@ class BgfxBackend : public DX8Backend virtual void Capture_Dynamic_Index_Data(const DynamicIBAccessClass * iba, const void * data, unsigned int size_bytes); + virtual void Capture_Vertex_Sub_Range(const VertexBufferClass * vb, + const void * data, + unsigned int start_vertex, + unsigned int size_bytes); + virtual void Capture_Index_Sub_Range(const IndexBufferClass * ib, + const void * data, + unsigned int start_index, + unsigned int size_bytes); + virtual void Begin_Sorted_Batch_Pass(); + virtual void End_Sorted_Batch_Pass(); + virtual void Capture_Sorted_Batch_Transforms(const Matrix4x4 & world, + const Matrix4x4 & view); + virtual void Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, + const DynamicIBAccessClass & dyn_ib, + unsigned short polygon_count, + unsigned short vertex_count); // -- State: shaders, materials, textures --------------------------------- // @@ -95,6 +112,7 @@ class BgfxBackend : public DX8Backend // forward so the dx8 device also sees the change. virtual void Set_Shader(const ShaderClass & shader); + virtual void Set_Material(const VertexMaterialClass * material); virtual void Set_Texture(unsigned int stage, TextureBaseClass * texture); // -- Transforms ----------------------------------------------------------- @@ -126,7 +144,7 @@ class BgfxBackend : public DX8Backend unsigned short vertex_count); // Everything else (Is_Device_Lost, Has_Stencil, Get_Back_Buffer*, - // Set_Gamma, Flip_To_Primary, Clear, Set_Viewport, Set_Index_Buffer_Index_Offset, + // Set_Gamma, Flip_To_Primary, Clear, Set_Viewport, // Get_Shader, Set_Material, Apply_Render_State_Changes, // Apply_Default_State, Invalidate_Cached_Render_States, Set_Blend_*, // Set_Color_Write_Enable, Set_Alpha_Blend_Enable, Show/Set_Hardware_Cursor*, diff --git a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h index c7cca86685b..ec759263f4a 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h @@ -252,6 +252,52 @@ class IRenderBackend const void * /*data*/, unsigned int /*size_bytes*/) {} + // TheSuperHackers @refactor bobtista 11/04/2026 sub-range + // capture. Rigid mesh category containers fill their shared VB / IB + // via AppendLockClass one sub-range at a time. BgfxBackend creates a + // bgfx dynamic buffer the first time it sees a VB / IB and updates + // the sub-range in place. start_vertex / start_index is in elements + // (verts or shorts), size_bytes is in bytes. + virtual void Capture_Vertex_Sub_Range(const VertexBufferClass * /*vb*/, + const void * /*data*/, + unsigned int /*start_vertex*/, + unsigned int /*size_bytes*/) {} + virtual void Capture_Index_Sub_Range(const IndexBufferClass * /*ib*/, + const void * /*data*/, + unsigned int /*start_index*/, + unsigned int /*size_bytes*/) {} + + // TheSuperHackers @refactor bobtista 11/04/2026 sorted + // draw pass routing. SortingRendererClass::Flush_Sorting_Pool wraps + // its per-batch draw loop in Begin/End_Sorted_Batch_Pass and calls + // Capture_Sorted_Batch_Transforms once per batch inside the loop. + // BgfxBackend uses this to route the sorted submits to a dedicated + // bgfx view id so per-batch matrices cannot stomp the opaque view. + // Empty defaults = no-op on DX8Backend. + virtual void Begin_Sorted_Batch_Pass() {} + virtual void End_Sorted_Batch_Pass() {} + virtual void Capture_Sorted_Batch_Transforms(const Matrix4x4 & /*world*/, + const Matrix4x4 & /*view*/) {} + + // TheSuperHackers @refactor bobtista 11/04/2026 sorted + // direct-draw path hook. DX8Wrapper::Draw_Sorting_IB_VB handles + // draws whose currently-bound VB/IB are sorting-type (CPU arrays) + // by creating an internal dynamic VB/IB, copying a slice out of + // the sorting buffers at (vba_offset+index_base_offset+min_vertex_index), + // and issuing the dx8 draw against those inner buffers. The bgfx + // backend never sees the sorting-VB path otherwise because the + // outer Set_Vertex_Buffer captured the full sorting VB. This hook + // lets Draw_Sorting_IB_VB hand the freshly populated inner dynamic + // VB/IB access classes to the render backend so BgfxBackend can + // claim their pending transient buffers and submit a correctly + // remapped draw (start_index=0, min_vertex_index=0). Sets an + // internal skip flag so the outer Draw_Triangles does not also + // emit a stale submit. Empty default = no-op on DX8Backend. + virtual void Submit_Sorted_Draw(const DynamicVBAccessClass & /*dyn_vb*/, + const DynamicIBAccessClass & /*dyn_ib*/, + unsigned short /*polygon_count*/, + unsigned short /*vertex_count*/) {} + // ------------------------------------------------------------------------- // State: shaders, materials, textures // ------------------------------------------------------------------------- diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.cpp index f4f23c2eea4..0dccbdb0c12 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.cpp @@ -220,11 +220,10 @@ IndexBufferClass::WriteLockClass::WriteLockClass(IndexBufferClass* index_buffer_ IndexBufferClass::WriteLockClass::~WriteLockClass() { DX8_THREAD_ASSERT(); - // TheSuperHackers @refactor bobtista 11/04/2026 capture - // index data into the active render backend BEFORE unlocking. After - // Unlock the source pointer is invalid. The bgfx backend snapshots - // the bytes; the dx8 backend ignores the call. - if (g_renderBackend != NULL && indices != NULL && index_buffer->Type() == BUFFER_TYPE_DX8) { + // TheSuperHackers @refactor bobtista 11/04/2026 Capture index data (including + // BUFFER_TYPE_SORTING) into the render backend before Unlock invalidates the pointer. + if (g_renderBackend != NULL && indices != NULL && + (index_buffer->Type() == BUFFER_TYPE_DX8 || index_buffer->Type() == BUFFER_TYPE_SORTING)) { const unsigned int total_bytes = index_buffer->Get_Index_Count() * sizeof(unsigned short); g_renderBackend->Capture_Index_Data(index_buffer, indices, total_bytes); } @@ -246,7 +245,9 @@ IndexBufferClass::WriteLockClass::~WriteLockClass() IndexBufferClass::AppendLockClass::AppendLockClass(IndexBufferClass* index_buffer_,unsigned start_index, unsigned index_range) : - index_buffer(index_buffer_) + index_buffer(index_buffer_), + AppendStartIndex(start_index), + AppendIndexRange(index_range) { DX8_THREAD_ASSERT(); WWASSERT(start_index+index_range<=index_buffer->Get_Index_Count()); @@ -276,6 +277,13 @@ IndexBufferClass::AppendLockClass::AppendLockClass(IndexBufferClass* index_buffe IndexBufferClass::AppendLockClass::~AppendLockClass() { DX8_THREAD_ASSERT(); + // TheSuperHackers @refactor bobtista 11/04/2026 Capture rigid and sorting IB sub-range + // writes for the bgfx backend; without this hook mesh indices never reach bgfx. + if (g_renderBackend != NULL && indices != NULL && + (index_buffer->Type() == BUFFER_TYPE_DX8 || index_buffer->Type() == BUFFER_TYPE_SORTING)) { + const unsigned int size_bytes = AppendIndexRange * sizeof(unsigned short); + g_renderBackend->Capture_Index_Sub_Range(index_buffer, indices, AppendStartIndex, size_bytes); + } switch (index_buffer->Type()) { case BUFFER_TYPE_DX8: DX8_Assert(); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.h index 3ae18aabe10..72bf68fabb5 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.h @@ -83,6 +83,11 @@ class IndexBufferClass : public RefCountClass { IndexBufferClass* index_buffer; unsigned short* indices; + // TheSuperHackers @refactor bobtista 11/04/2026 + // stored so the destructor can report the locked sub-range to + // the bgfx write-side capture hook. Not used by the dx8 path. + unsigned AppendStartIndex; + unsigned AppendIndexRange; public: AppendLockClass(IndexBufferClass* index_buffer,unsigned start_index, unsigned index_range); ~AppendLockClass(); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8polygonrenderer.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8polygonrenderer.h index 17b7558049a..6bf670e84eb 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8polygonrenderer.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8polygonrenderer.h @@ -44,6 +44,8 @@ #include "sortingrenderer.h" #include "WW3D2/mesh.h" #include "dx8wrapper.h" +#include "RenderBackend.h" +#include "IRenderBackend.h" class DX8PolygonRendererClass; class DX8TextureCategoryClass; @@ -114,7 +116,7 @@ inline void DX8PolygonRendererClass::Render(/*const Matrix3D & tm,*/int base_ver // SNAPSHOT_SAY(("Set_Transform")); SNAPSHOT_SAY(("Set_Index_Buffer_Index_Offset(%d)",base_vertex_offset)); - DX8Wrapper::Set_Index_Buffer_Index_Offset(base_vertex_offset); + g_renderBackend->Set_Index_Buffer_Index_Offset(base_vertex_offset); if (strip) { SNAPSHOT_SAY(("Draw_Strip(%d,%d,%d,%d)",index_offset,index_count-2,min_vertex_index,vertex_index_range)); DX8Wrapper::Draw_Strip( @@ -125,7 +127,7 @@ inline void DX8PolygonRendererClass::Render(/*const Matrix3D & tm,*/int base_ver } else { SNAPSHOT_SAY(("Draw_Triangles(%d,%d,%d,%d)",index_offset,index_count-2,min_vertex_index,vertex_index_range)); - DX8Wrapper::Draw_Triangles( + g_renderBackend->Draw_Triangles( index_offset, index_count/3, min_vertex_index, @@ -141,7 +143,7 @@ inline void DX8PolygonRendererClass::Render_Sorted(/*const Matrix3D & tm,*/int b SNAPSHOT_SAY(("Set_Index_Buffer_Index_Offset(%d)",base_vertex_offset)); SNAPSHOT_SAY(("Insert_Sorting_Triangles(%d,%d,%d,%d)",index_offset,index_count-2,min_vertex_index,vertex_index_range)); - DX8Wrapper::Set_Index_Buffer_Index_Offset(base_vertex_offset); + g_renderBackend->Set_Index_Buffer_Index_Offset(base_vertex_offset); SortingRendererClass::Insert_Triangles( bounding_sphere, index_offset, diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp index 31394a37617..973356ff24a 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp @@ -48,6 +48,8 @@ #include "dx8fvf.h" #include "dx8caps.h" #include "dx8rendererdebugger.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" #include "WWDebug/wwdebug.h" #include "WWDebug/wwprofile.h" #include "WWDebug/wwmemlog.h" @@ -348,8 +350,8 @@ void DX8RigidFVFCategoryContainer::Render_Delayed_Procedural_Material_Passes() if (!Any_Delayed_Passes_To_Render()) return; AnyDelayedPassesToRender=false; - DX8Wrapper::Set_Vertex_Buffer(vertex_buffer); - DX8Wrapper::Set_Index_Buffer(index_buffer,0); + g_renderBackend->Set_Vertex_Buffer(vertex_buffer, 0); + g_renderBackend->Set_Index_Buffer(index_buffer, 0); SNAPSHOT_SAY(("DX8RigidFVFCategoryContainer::Render_Delayed_Procedural_Material_Passes()")); @@ -805,8 +807,8 @@ void DX8RigidFVFCategoryContainer::Render() if (!Anything_To_Render()) return; AnythingToRender=false; - DX8Wrapper::Set_Vertex_Buffer(vertex_buffer); - DX8Wrapper::Set_Index_Buffer(index_buffer,0); + g_renderBackend->Set_Vertex_Buffer(vertex_buffer, 0); + g_renderBackend->Set_Index_Buffer(index_buffer, 0); SNAPSHOT_SAY(("DX8RigidFVFCategoryContainer::Render()")); for (unsigned p=0;pSet_Vertex_Buffer(nullptr, 0); // Free up the reference to the current vertex buffer // (in case it is the dynamic, which may have to be resized) //'Generals' customization to allow more than 65535 vertices @@ -1365,7 +1367,13 @@ void DX8SkinFVFCategoryContainer::Render() verts[v].diffuse=*diffuse++; } else { - verts[v].diffuse=0; + // TheSuperHackers @fix bobtista 12/04/2026 Opaque white + // so the bgfx fragment shader (which multiplies by vertex + // diffuse) does not zero out the output. The DX8 fixed- + // function pipeline ignores vertex diffuse when the TSS + // selects texture-only, but our bgfx shaders always + // multiply. Team color comes through u_matDiffuse. + verts[v].diffuse=0xFFFFFFFF; } if (uv0) { verts[v].u1=(*uv0)[0]; @@ -1400,8 +1408,17 @@ void DX8SkinFVFCategoryContainer::Render() SNAPSHOT_SAY(("Set vb: %x ib: %x",&vb.FVF_Info(),index_buffer)); - DX8Wrapper::Set_Vertex_Buffer(vb); - DX8Wrapper::Set_Index_Buffer(index_buffer,0); + // TheSuperHackers @refactor bobtista 11/04/2026 skin + // vertices come out of Get_Deformed_Vertices already in world + // space (the HTree bone matrices include the container's world + // transform), so the draw must use an identity world matrix. + // On the dx8 path this works because DX8Wrapper's cached world + // state happens to be identity at this point in the frame; on + // the bgfx path we need to explicitly push identity so the + // backend does not inherit the last rigid mesh's transform. + g_renderBackend->Set_World_Identity(); + g_renderBackend->Set_Vertex_Buffer(vb); + g_renderBackend->Set_Index_Buffer(index_buffer, 0); //Flush the meshes which fit in the vertex buffer, applying all texture variations for (unsigned pass=0;passGet_Texture_Name().str() : "null")); - DX8Wrapper::Set_Texture(i,Peek_Texture(i)); + g_renderBackend->Set_Texture(i, Peek_Texture(i)); } #ifdef WWDEBUG @@ -1694,7 +1711,7 @@ void DX8TextureCategoryClass::Render() SNAPSHOT_SAY(("Set_Material(%s)",Peek_Material() ? Peek_Material()->Get_Name() : "null")); VertexMaterialClass *vmaterial=(VertexMaterialClass *)Peek_Material(); //ugly cast from const but we'll restore it after changes so okay. -MW - DX8Wrapper::Set_Material(vmaterial); + g_renderBackend->Set_Material(vmaterial); SNAPSHOT_SAY(("Set_Shader(%x)",Get_Shader().Get_Bits())); ShaderClass theShader = Get_Shader(); @@ -1707,12 +1724,12 @@ void DX8TextureCategoryClass::Render() //this will cause sorting errors on this mesh. //theAlphaShader.Set_Depth_Mask(ShaderClass::DEPTH_WRITE_DISABLE); - DX8Wrapper::Set_Shader(theShader); + g_renderBackend->Set_Shader(theShader); if (m_gForceMultiply && theShader.Get_Dst_Blend_Func() == ShaderClass::DSTBLEND_ZERO) { theShader.Set_Dst_Blend_Func(ShaderClass::DSTBLEND_SRC_COLOR); theShader.Set_Src_Blend_Func(ShaderClass::SRCBLEND_ZERO); - DX8Wrapper::Set_Shader(theShader); + g_renderBackend->Set_Shader(theShader); //VertexMaterialClass *material = VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); //DX8Wrapper::Set_Material(material); //REF_PTR_RELEASE(material); @@ -1850,11 +1867,11 @@ void DX8TextureCategoryClass::Render() if (identity) { SNAPSHOT_SAY(("Set_World_Identity")); - DX8Wrapper::Set_World_Identity(); + g_renderBackend->Set_World_Identity(); } else { SNAPSHOT_SAY(("Set_World_Transform")); - DX8Wrapper::Set_Transform(D3DTS_WORLD,*world_transform); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD, *world_transform); } @@ -1901,7 +1918,7 @@ void DX8TextureCategoryClass::Render() theAlphaShader = theShader; //keep using additive blending. } vmaterial->Set_Opacity(mesh->Get_Alpha_Override()); - DX8Wrapper::Set_Shader(theAlphaShader); + g_renderBackend->Set_Shader(theAlphaShader); DX8Wrapper::Apply_Render_State_Changes(); DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF,(int)((float)0x60*mesh->Get_Alpha_Override())); @@ -1910,7 +1927,7 @@ void DX8TextureCategoryClass::Render() DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF,0x60); vmaterial->Set_Opacity(oldOpacity); //restore previous value vmaterial->Set_Diffuse(oldDiffuse.X,oldDiffuse.Y,oldDiffuse.Z); - DX8Wrapper::Set_Shader(theShader); //restore previous value + g_renderBackend->Set_Shader(theShader); //restore previous value } else renderer->Render(mesh->Get_Base_Vertex_Offset()); @@ -1919,8 +1936,8 @@ void DX8TextureCategoryClass::Render() { oldMapper->Set_LastUsedSyncTime(oldUVOffsetSyncTime); oldMapper->Set_Current_UV_Offset(oldUVOffset); } - DX8Wrapper::Set_Material(nullptr); //force a reset of vertex material since we secretly changed opacity - DX8Wrapper::Set_Material(vmaterial); //restore previous material. + g_renderBackend->Set_Material(nullptr); //force a reset of vertex material since we secretly changed opacity + g_renderBackend->Set_Material(vmaterial); //restore previous material. } else renderer->Render(mesh->Get_Base_Vertex_Offset()); @@ -2201,8 +2218,8 @@ void DX8MeshRendererClass::Flush() Render_FVF_Category_Container_List_Delayed_Passes(*texture_category_container_lists_rigid[i]); } - DX8Wrapper::Set_Vertex_Buffer(nullptr); - DX8Wrapper::Set_Index_Buffer(nullptr,0); + g_renderBackend->Set_Vertex_Buffer(nullptr, 0); + g_renderBackend->Set_Index_Buffer(nullptr, 0); } @@ -2284,4 +2301,3 @@ void DX8MeshRendererClass::Invalidate( bool shutdown) - diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp index 3ab6a730a3b..d5e0e12910a 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp @@ -202,12 +202,10 @@ VertexBufferClass::WriteLockClass::WriteLockClass(VertexBufferClass* VertexBuffe VertexBufferClass::WriteLockClass::~WriteLockClass() { DX8_THREAD_ASSERT(); - // TheSuperHackers @refactor bobtista 11/04/2026 capture - // vertex data into the active render backend BEFORE unlocking. After - // Unlock the source pointer becomes invalid, so this is the last - // safe moment. The bgfx backend snapshots the bytes; the dx8 backend - // ignores the call. - if (g_renderBackend != NULL && Vertices != NULL && VertexBuffer->Type() == BUFFER_TYPE_DX8) { + // TheSuperHackers @refactor bobtista 11/04/2026 Capture STATIC and SORTING vertex writes + // into the render backend before Unlock invalidates the source pointer. + if (g_renderBackend != NULL && Vertices != NULL && + (VertexBuffer->Type() == BUFFER_TYPE_DX8 || VertexBuffer->Type() == BUFFER_TYPE_SORTING)) { const unsigned int total_bytes = VertexBuffer->Get_Vertex_Count() * VertexBuffer->FVF_Info().Get_FVF_Size(); g_renderBackend->Capture_Vertex_Data(VertexBuffer, Vertices, total_bytes); } @@ -236,7 +234,9 @@ VertexBufferClass::WriteLockClass::~WriteLockClass() VertexBufferClass::AppendLockClass::AppendLockClass(VertexBufferClass* VertexBuffer,unsigned start_index, unsigned index_range) : - VertexBufferLockClass(VertexBuffer) + VertexBufferLockClass(VertexBuffer), + AppendStartIndex(start_index), + AppendIndexRange(index_range) { DX8_THREAD_ASSERT(); WWASSERT(VertexBuffer); @@ -277,12 +277,14 @@ VertexBufferClass::AppendLockClass::AppendLockClass(VertexBufferClass* VertexBuf VertexBufferClass::AppendLockClass::~AppendLockClass() { DX8_THREAD_ASSERT(); - // TheSuperHackers @refactor bobtista 11/04/2026 append locks - // are intentionally NOT captured for the bgfx backend. The Vertices - // pointer here is offset to a sub-range, not the start of the VB, so we - // cannot do a "snapshot the whole buffer" copy from it. Append locks - // are rare in the static rendering path; if a buffer is exclusively - // updated via append locks the bgfx backend will simply skip drawing it. + // TheSuperHackers @refactor bobtista 11/04/2026 Capture the locked sub-range for STATIC + // and SORTING buffers; BgfxBackend updates its dynamic VB at the matching vertex offset. + if (g_renderBackend != NULL && Vertices != NULL && + (VertexBuffer->Type() == BUFFER_TYPE_DX8 || VertexBuffer->Type() == BUFFER_TYPE_SORTING)) { + const unsigned int fvf_size = VertexBuffer->FVF_Info().Get_FVF_Size(); + const unsigned int size_bytes = AppendIndexRange * fvf_size; + g_renderBackend->Capture_Vertex_Sub_Range(VertexBuffer, Vertices, AppendStartIndex, size_bytes); + } switch (VertexBuffer->Type()) { case BUFFER_TYPE_DX8: DX8_Assert(); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.h index 08ec118931b..754a9e75326 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.h @@ -100,6 +100,12 @@ class VertexBufferClass : public RefCountClass public: AppendLockClass(VertexBufferClass* vertex_buffer,unsigned start_index, unsigned index_range); ~AppendLockClass(); + protected: + // TheSuperHackers @refactor bobtista 11/04/2026 + // stored so the destructor can report the locked sub-range to + // the bgfx write-side capture hook. Not used by the dx8 path. + unsigned AppendStartIndex; + unsigned AppendIndexRange; }; static unsigned Get_Total_Buffer_Count(); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp index 7a9ccb78ab3..f4400f57e27 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp @@ -57,6 +57,7 @@ #include "dx8indexbuffer.h" #include "dx8renderer.h" #include "RenderBackend.h" +#include "WW3D2/IRenderBackend.h" #include "WW3D2/ww3d.h" #include "WW3D2/camera.h" #include "WWLib/wwstring.h" @@ -374,6 +375,24 @@ void DX8Wrapper::Do_Onetime_Device_Dependent_Inits() */ Compute_Caps(D3DFormat_To_WW3DFormat(DisplayFormat)); + // TheSuperHackers @refactor bobtista 11/04/2026 + // Construct and initialize the render backend BEFORE the engine + // subsystem _Init() calls below. Several of them (notably + // PointGroupClass::_Init and BoxRenderObjClass::Init) allocate + // static index / vertex buffers and populate them via the Write + // lock classes, whose destructors call g_renderBackend->Capture_* + // to mirror the data into the bgfx caches. If the backend is not + // fully initialized (g_bgfxInitialized == false) at that moment + // those buffers are never captured and every later bgfx draw that + // binds them misses the cache and silently skips submission - + // which is why particles, lasers, tracers, and debris were + // invisible in the bgfx popup even though the underlying W3D + // effect modules ran. The d3d8 device and HWND are already valid + // by the time this function is called, so it is safe to run the + // backend's real Initialize() here, well before the _Init() calls. + Init_Render_Backend(); + g_renderBackend->Initialize(_Hwnd, ResolutionWidth, ResolutionHeight); + /* ** Initialize any other subsystems inside of WW3D */ @@ -391,17 +410,6 @@ void DX8Wrapper::Do_Onetime_Device_Dependent_Inits() TextureLoader::Init(); Set_Default_Global_Render_States(); - - // TheSuperHackers @refactor bobtista 10/04/2026 Construct the global - // IRenderBackend instance now that the D3D device is ready. See - // Core/Libraries/Source/WWVegas/WW3D2/RENDER_BACKEND.md. - Init_Render_Backend(); - - // TheSuperHackers @refactor bobtista 11/04/2026 - // Hand the HWND and current back-buffer dimensions to the render backend - // so it can perform any API-specific initialization (bgfx::init for the - // bgfx backend, no-op for DX8Backend). - g_renderBackend->Initialize(_Hwnd, ResolutionWidth, ResolutionHeight); } inline DWORD F2DW(float f) { return *((unsigned*)&f); } @@ -2022,6 +2030,20 @@ void DX8Wrapper::Draw_Sorting_IB_VB( polygon_count)); DX8_RECORD_RENDER(polygon_count,vertex_count,render_state.shader); + + // TheSuperHackers @refactor bobtista 11/04/2026 hand the + // internal dynamic VB/IB to the render backend so a bgfx co-resident + // can submit the same draw using its transient captures of these + // inner buffers. The Write locks above already fired the backend's + // Capture_Dynamic_* hooks, so pending transients are keyed by + // &dyn_vb_access / &dyn_ib_access. The backend remaps the outer + // Draw_Triangles args (start_index=0, min_vertex_index=0) and sets + // an internal skip flag so the outer BgfxBackend::Draw_Triangles + // does not emit a stale second submit. No-op on DX8Backend. + if (g_renderBackend != nullptr) { + g_renderBackend->Submit_Sorted_Draw(dyn_vb_access, dyn_ib_access, + polygon_count, vertex_count); + } } // ---------------------------------------------------------------------------- diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dynamesh.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dynamesh.cpp index 74276b2aecd..936b768fdb7 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dynamesh.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dynamesh.cpp @@ -42,6 +42,8 @@ #include "WW3D2/rinfo.h" #include "WW3D2/camera.h" #include "dx8fvf.h" +#include "RenderBackend.h" +#include "IRenderBackend.h" @@ -248,8 +250,8 @@ void DynamicMeshModel::Render(RenderInfoClass & rinfo) /* ** Set vertex and index buffers */ - DX8Wrapper::Set_Vertex_Buffer(dynamic_vb); - DX8Wrapper::Set_Index_Buffer(dynamic_ib,0); + g_renderBackend->Set_Vertex_Buffer(dynamic_vb); + g_renderBackend->Set_Index_Buffer(dynamic_ib,0); /* ** Draw dynamesh, one pass at a time @@ -301,26 +303,26 @@ void DynamicMeshModel::Render(RenderInfoClass & rinfo) // Set the DX8 state to the first triangle's state if (texture_array0) { - DX8Wrapper::Set_Texture(0,texture_array0[0]); + g_renderBackend->Set_Texture(0,texture_array0[0]); } else { - DX8Wrapper::Set_Texture(0,MatDesc->Peek_Single_Texture(pass, 0)); + g_renderBackend->Set_Texture(0,MatDesc->Peek_Single_Texture(pass, 0)); } if (texture_array1) { - DX8Wrapper::Set_Texture(1,texture_array1[0]); + g_renderBackend->Set_Texture(1,texture_array1[0]); } else { - DX8Wrapper::Set_Texture(1,MatDesc->Peek_Single_Texture(pass, 1)); + g_renderBackend->Set_Texture(1,MatDesc->Peek_Single_Texture(pass, 1)); } if (material_array) { - DX8Wrapper::Set_Material(material_array[tris[0].I]); + g_renderBackend->Set_Material(material_array[tris[0].I]); } else { - DX8Wrapper::Set_Material(MatDesc->Peek_Single_Material(pass)); + g_renderBackend->Set_Material(MatDesc->Peek_Single_Material(pass)); } if (shader_array) { - DX8Wrapper::Set_Shader(shader_array[0]); + g_renderBackend->Set_Shader(shader_array[0]); } else { - DX8Wrapper::Set_Shader(MatDesc->Get_Single_Shader(pass)); + g_renderBackend->Set_Shader(MatDesc->Get_Single_Shader(pass)); } SphereClass sphere; @@ -332,7 +334,7 @@ void DynamicMeshModel::Render(RenderInfoClass & rinfo) SortingRendererClass::Insert_Triangles(sphere,0, DynamicMeshPNum, 0, DynamicMeshVNum); } else { - DX8Wrapper::Draw_Triangles(0, DynamicMeshPNum, 0, DynamicMeshVNum); + g_renderBackend->Draw_Triangles(0, DynamicMeshPNum, 0, DynamicMeshVNum); } continue; } @@ -372,7 +374,7 @@ void DynamicMeshModel::Render(RenderInfoClass & rinfo) 1 + max_vert_idx - min_vert_idx); } else { - DX8Wrapper::Draw_Triangles( + g_renderBackend->Draw_Triangles( (start_tri_idx * 3), (1 + cur_tri_idx - start_tri_idx), min_vert_idx, @@ -381,10 +383,10 @@ void DynamicMeshModel::Render(RenderInfoClass & rinfo) start_tri_idx = next_tri_idx; min_vert_idx = DynamicMeshVNum - 1; max_vert_idx = 0; - if (texture_changed) DX8Wrapper::Set_Texture(0,texture_array0[next_tri_idx]); - if (texture1_changed) DX8Wrapper::Set_Texture(1,texture_array1[next_tri_idx]); - if (material_changed) DX8Wrapper::Set_Material(material_array[tris[next_tri_idx].I]); - if (shader_changed) DX8Wrapper::Set_Shader(shader_array[next_tri_idx]); + if (texture_changed) g_renderBackend->Set_Texture(0,texture_array0[next_tri_idx]); + if (texture1_changed) g_renderBackend->Set_Texture(1,texture_array1[next_tri_idx]); + if (material_changed) g_renderBackend->Set_Material(material_array[tris[next_tri_idx].I]); + if (shader_changed) g_renderBackend->Set_Shader(shader_array[next_tri_idx]); } cur_tri_idx = next_tri_idx; @@ -430,7 +432,7 @@ void DynamicMeshClass::Render(RenderInfoClass & rinfo) const FrustumClass & frustum = rinfo.Camera.Get_Frustum(); if (CollisionMath::Overlap_Test(frustum, Get_Bounding_Box()) != CollisionMath::OUTSIDE) { - DX8Wrapper::Set_Transform(D3DTS_WORLD, Transform); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD, Transform); Model->Render(rinfo); } } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/line3d.cpp b/Core/Libraries/Source/WWVegas/WW3D2/line3d.cpp index 0bfaa121587..026f50df50c 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/line3d.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/line3d.cpp @@ -57,6 +57,8 @@ #include "dx8vertexbuffer.h" #include "dx8indexbuffer.h" #include "dx8fvf.h" +#include "RenderBackend.h" +#include "IRenderBackend.h" // 12 Triangles for index buffer const unsigned short Indices[]= @@ -267,13 +269,13 @@ void Line3DClass::Render(RenderInfoClass & rinfo) return; } - DX8Wrapper::Set_Shader(Shader); - DX8Wrapper::Set_Texture(0,nullptr); + g_renderBackend->Set_Shader(Shader); + g_renderBackend->Set_Texture(0,nullptr); VertexMaterialClass *vm=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vm); + g_renderBackend->Set_Material(vm); REF_PTR_RELEASE(vm); - DX8Wrapper::Set_Transform(D3DTS_WORLD,Transform); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,Transform); DynamicVBAccessClass vb(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,8); { @@ -299,9 +301,9 @@ void Line3DClass::Render(RenderInfoClass & rinfo) mem[i]=Indices[i]; } - DX8Wrapper::Set_Vertex_Buffer(vb); - DX8Wrapper::Set_Index_Buffer(ib,0); - DX8Wrapper::Draw_Triangles(0,36/3,0,8); + g_renderBackend->Set_Vertex_Buffer(vb); + g_renderBackend->Set_Index_Buffer(ib,0); + g_renderBackend->Draw_Triangles(0,36/3,0,8); } /************************************************************************** diff --git a/Core/Libraries/Source/WWVegas/WW3D2/matpass.cpp b/Core/Libraries/Source/WWVegas/WW3D2/matpass.cpp index d300e5ffe36..f8958024a39 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/matpass.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/matpass.cpp @@ -52,6 +52,8 @@ #include "texture.h" #include "statistics.h" #include "dx8wrapper.h" +#include "RenderBackend.h" +#include "IRenderBackend.h" bool MaterialPassClass::EnablePerPolygonCulling = true; @@ -117,11 +119,11 @@ MaterialPassClass::~MaterialPassClass() *=============================================================================================*/ void MaterialPassClass::Install_Materials() const { - DX8Wrapper::Set_Material(Peek_Material()); - DX8Wrapper::Set_Shader(Peek_Shader()); + g_renderBackend->Set_Material(Peek_Material()); + g_renderBackend->Set_Shader(Peek_Shader()); for (int i=0;iGet_Max_Textures_Per_Pass();++i) { - DX8Wrapper::Set_Texture(i,Peek_Texture(i)); + g_renderBackend->Set_Texture(i,Peek_Texture(i)); } } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/pointgr.cpp b/Core/Libraries/Source/WWVegas/WW3D2/pointgr.cpp index 4bbbd6f0534..35d54e5942c 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/pointgr.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/pointgr.cpp @@ -88,6 +88,8 @@ #include "dx8fvf.h" #include "d3dx8math.h" #include "sortingrenderer.h" +#include "RenderBackend.h" +#include "IRenderBackend.h" // Upgraded to DX8 2/2/01 HY @@ -919,12 +921,12 @@ void PointGroupClass::Render(RenderInfoClass &rinfo) // so set world and view matrices to identity and render Matrix4x4 identity(true); - DX8Wrapper::Set_Transform(D3DTS_WORLD,identity); - DX8Wrapper::Set_Transform(D3DTS_VIEW,identity); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,identity); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW, identity); - DX8Wrapper::Set_Material(PointMaterial); - DX8Wrapper::Set_Shader(Shader); - DX8Wrapper::Set_Texture(0,Texture); + g_renderBackend->Set_Material(PointMaterial); + g_renderBackend->Set_Shader(Shader); + g_renderBackend->Set_Texture(0,Texture); // Enable sorting if the primitives are translucent and alpha testing is not enabled. // TheSuperHackers @bugfix stephanmeesters 30/06/2026 However, do not apply sorting to ground-aligned particles. @@ -980,8 +982,8 @@ void PointGroupClass::Render(RenderInfoClass &rinfo) } } - DX8Wrapper::Set_Index_Buffer (indexbuffer, 0); - DX8Wrapper::Set_Vertex_Buffer (PointVerts); + g_renderBackend->Set_Index_Buffer (indexbuffer, 0); + g_renderBackend->Set_Vertex_Buffer (PointVerts); if ( sort ) { @@ -989,14 +991,14 @@ void PointGroupClass::Render(RenderInfoClass &rinfo) } else { - DX8Wrapper::Draw_Triangles (0, delta / verticesperprimitive, 0, delta); + g_renderBackend->Draw_Triangles (0, delta / verticesperprimitive, 0, delta); } current+=delta; } // restore the matrices - DX8Wrapper::Set_Transform(D3DTS_VIEW,view); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW,view); } @@ -1833,12 +1835,12 @@ void PointGroupClass::RenderVolumeParticle(RenderInfoClass &rinfo, unsigned int // so set world and view matrices to identity and render Matrix4x4 identity(true); - DX8Wrapper::Set_Transform(D3DTS_WORLD,identity); - DX8Wrapper::Set_Transform(D3DTS_VIEW,identity); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,identity); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW, identity); - DX8Wrapper::Set_Material(PointMaterial); - DX8Wrapper::Set_Shader(Shader); - DX8Wrapper::Set_Texture(0,Texture); + g_renderBackend->Set_Material(PointMaterial); + g_renderBackend->Set_Shader(Shader); + g_renderBackend->Set_Texture(0,Texture); // Enable sorting if the primitives are translucent and alpha testing is not enabled. // TheSuperHackers @info Volumetric particles, both billboarded and ground-aligned, must have sorting enabled to @@ -1896,8 +1898,8 @@ void PointGroupClass::RenderVolumeParticle(RenderInfoClass &rinfo, unsigned int } } - DX8Wrapper::Set_Index_Buffer (indexbuffer, 0); - DX8Wrapper::Set_Vertex_Buffer (PointVerts); + g_renderBackend->Set_Index_Buffer (indexbuffer, 0); + g_renderBackend->Set_Vertex_Buffer (PointVerts); /// @todo lorenzen sez: precompute these params, above @@ -1905,7 +1907,7 @@ void PointGroupClass::RenderVolumeParticle(RenderInfoClass &rinfo, unsigned int if ( sort ) SortingRendererClass::Insert_Triangles (0, delta / verticesperprimitive, 0, delta); else - DX8Wrapper::Draw_Triangles (0, delta / verticesperprimitive, 0, delta); + g_renderBackend->Draw_Triangles (0, delta / verticesperprimitive, 0, delta); current+=delta; @@ -1919,5 +1921,5 @@ void PointGroupClass::RenderVolumeParticle(RenderInfoClass &rinfo, unsigned int // restore the matrices - DX8Wrapper::Set_Transform(D3DTS_VIEW,view); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW,view); } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/ringobj.cpp b/Core/Libraries/Source/WWVegas/WW3D2/ringobj.cpp index 7f246b51432..c69194559a1 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/ringobj.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/ringobj.cpp @@ -94,6 +94,8 @@ #include "sortingrenderer.h" #include "WWMath/Vector3i.h" #include "visrasterizer.h" +#include "RenderBackend.h" +#include "IRenderBackend.h" static bool Ring_Array_Valid = false; @@ -531,9 +533,9 @@ void RingRenderObjClass::render_ring(RenderInfoClass & rinfo,const Vector3 & cen } else { RingShader.Set_Texturing (ShaderClass::TEXTURING_DISABLE); } - DX8Wrapper::Set_Shader(RingShader); - DX8Wrapper::Set_Texture(0,RingTexture); - DX8Wrapper::Set_Material(RingMaterial); + g_renderBackend->Set_Shader(RingShader); + g_renderBackend->Set_Texture(0,RingTexture); + g_renderBackend->Set_Material(RingMaterial); // Enable sorting if the primitive is translucent, alpha testing is not enabled, and sorting is enabled globally. const bool sort = (RingShader.Get_Dst_Blend_Func() != ShaderClass::DSTBLEND_ZERO) && (RingShader.Get_Alpha_Test() == ShaderClass::ALPHATEST_DISABLE) && (WW3D::Is_Sorting_Enabled()); @@ -586,13 +588,13 @@ void RingRenderObjClass::render_ring(RenderInfoClass & rinfo,const Vector3 & cen } } - DX8Wrapper::Set_Vertex_Buffer(vb); - DX8Wrapper::Set_Index_Buffer(ib,0); + g_renderBackend->Set_Vertex_Buffer(vb); + g_renderBackend->Set_Index_Buffer(ib,0); if (sort) { SortingRendererClass::Insert_Triangles(Get_Bounding_Sphere(), 0, ring.face_ct, 0, ring.Vertex_ct); } else { - DX8Wrapper::Draw_Triangles(0, ring.face_ct, 0, ring.Vertex_ct); + g_renderBackend->Draw_Triangles(0, ring.face_ct, 0, ring.Vertex_ct); } } @@ -722,9 +724,9 @@ void RingRenderObjClass::Render(RenderInfoClass & rinfo) Matrix3D temp; temp.Look_At(obj_position, obj_position + camera_z_vector, 0.0f); - DX8Wrapper::Set_Transform(D3DTS_WORLD, temp); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD, temp); } else { - DX8Wrapper::Set_Transform(D3DTS_WORLD, Transform); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD, Transform); } // diff --git a/Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp index 0d96c6b3a70..7a4cbf1f999 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp @@ -47,6 +47,8 @@ #include "WWLib/RANDOM.h" #include "WWMath/v3_rnd.h" #include "WW3D2/meshgeometry.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" /* We have chunking logic which handles N segments at a time. To simplify the subdivision logic, @@ -221,8 +223,8 @@ void SegLineRendererClass::Render DX8Wrapper::Get_Transform(D3DTS_VIEW,view); Matrix4x4 identity(true); - DX8Wrapper::Set_Transform(D3DTS_WORLD,identity); - DX8Wrapper::Set_Transform(D3DTS_VIEW,identity); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,identity); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW, identity); /* ** Handle texture UV offset animation (done once for entire line). @@ -1174,23 +1176,23 @@ void SegLineRendererClass::Render } } - DX8Wrapper::Set_Index_Buffer(ib_access,0); - DX8Wrapper::Set_Vertex_Buffer(Verts); - DX8Wrapper::Set_Material(mat); - DX8Wrapper::Set_Texture(0,Texture); - DX8Wrapper::Set_Shader(shader); + g_renderBackend->Set_Index_Buffer(ib_access,0); + g_renderBackend->Set_Vertex_Buffer(Verts); + g_renderBackend->Set_Material(mat); + g_renderBackend->Set_Texture(0,Texture); + g_renderBackend->Set_Shader(shader); if (sorting) { SortingRendererClass::Insert_Triangles(obj_sphere,0,tidx,0,vnum); } else { - DX8Wrapper::Draw_Triangles(0,tidx,0,vnum); + g_renderBackend->Draw_Triangles(0,tidx,0,vnum); } REF_PTR_RELEASE(mat); } - DX8Wrapper::Set_Transform(D3DTS_VIEW,view); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW,view); } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_solid_lit.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_solid_lit.sc index 95d4d14d5df..b8983f0d8c0 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_solid_lit.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_solid_lit.sc @@ -1,12 +1,16 @@ $input v_color0 // TheSuperHackers @refactor bobtista 11/04/2026 lit solid -// fragment shader. Outputs the interpolated vertex diffuse directly - -// equivalent to D3DTOP_SELECTARG1(D3DTA_DIFFUSE) with no texture stage. +// fragment shader. Outputs the interpolated vertex diffuse tinted by +// the material diffuse so untextured meshes can still pick up team +// colors. Equivalent to D3DTOP_SELECTARG1(D3DTA_DIFFUSE) but with the +// W3D vertex material diffuse multiplied in. #include +uniform vec4 u_matDiffuse; + void main() { - gl_FragColor = v_color0; + gl_FragColor = v_color0 * u_matDiffuse; } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit.sc index 089d676720c..ce21ae7c0b6 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit.sc @@ -1,11 +1,14 @@ -$input v_color0, v_texcoord0 +$input v_color0, v_texcoord0, v_normal // TheSuperHackers @refactor bobtista 11/04/2026 lit textured -// fragment shader. Samples stages 0-3 and multiplies by the interpolated -// vertex diffuse. Stages 1-3 were added later so terrain and -// multi-textured surfaces show their detail/blend/noise layers. All -// stages default to a 1x1 white texture when unbound, so single-texture -// draws are unaffected (white * color = color). +// fragment shader. Samples stages 0-3, modulates by interpolated +// vertex diffuse, applies a hardcoded directional + ambient light so +// meshes have depth, and tints by the material diffuse (team color). +// +// Lighting model is intentionally minimal - one sun +// direction + ambient - because the W3D fixed-function light +// environment isn't wired into the bgfx path yet. Good enough to +// read unit silhouettes and shading at a glance. #include @@ -13,6 +16,7 @@ SAMPLER2D(s_tex0, 0); SAMPLER2D(s_tex1, 1); SAMPLER2D(s_tex2, 2); SAMPLER2D(s_tex3, 3); +uniform vec4 u_matDiffuse; void main() { @@ -20,5 +24,16 @@ void main() vec4 tex1 = texture2D(s_tex1, v_texcoord0); vec4 tex2 = texture2D(s_tex2, v_texcoord0); vec4 tex3 = texture2D(s_tex3, v_texcoord0); - gl_FragColor = tex0 * tex1 * tex2 * tex3 * v_color0; + + // Simple headlamp + ambient. sunDir points from the surface + // toward the sun. Ambient raises the floor so backfaces are not + // pitch black. + vec3 sunDir = normalize(vec3(0.35, 0.55, 0.75)); + float ambient = 0.45; + float nDotL = max(0.0, dot(normalize(v_normal), sunDir)); + float lighting = min(1.0, ambient + nDotL * 0.75); + + vec4 base = tex0 * tex1 * tex2 * tex3; + vec4 tinted = base * v_color0 * u_matDiffuse; + gl_FragColor = vec4(tinted.rgb * lighting, tinted.a); } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit_atest.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit_atest.sc index f9bb42184e3..3df26b66c97 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit_atest.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit_atest.sc @@ -1,13 +1,12 @@ -$input v_color0, v_texcoord0 +$input v_color0, v_texcoord0, v_normal // TheSuperHackers @refactor bobtista 11/04/2026 alpha-tested -// lit textured fragment shader. Same as fs_textured_lit but with a -// discard when the sampled alpha falls below u_atestParams.x. Used by -// the ATest* presets (trees, fences, see-through foliage). See -// Stages -// 1-3 to match the non-atest variants. +// lit textured fragment shader. Same as fs_textured_lit but discards +// fragments whose sampled alpha falls below u_atestParams.x. Used by +// the ATest* presets (trees, fences, see-through foliage). Stages +// 1-3 were added later, along with lighting + material diffuse. // -// u_atestParams: x = reference in [0,1]. The engine should write +// u_atestParams: x = reference in [0,1]. The engine writes // ShaderClass::Get_Alpha_Reference() normalized by 255 into .x. // // Note: deliberately NOT named u_alphaRef - bgfx_shader.sh has an @@ -21,6 +20,7 @@ SAMPLER2D(s_tex0, 0); SAMPLER2D(s_tex1, 1); SAMPLER2D(s_tex2, 2); SAMPLER2D(s_tex3, 3); +uniform vec4 u_matDiffuse; uniform vec4 u_atestParams; void main() @@ -29,10 +29,17 @@ void main() vec4 tex1 = texture2D(s_tex1, v_texcoord0); vec4 tex2 = texture2D(s_tex2, v_texcoord0); vec4 tex3 = texture2D(s_tex3, v_texcoord0); - vec4 lit = tex0 * tex1 * tex2 * tex3 * v_color0; - if (lit.a < u_atestParams.x) + + vec3 sunDir = normalize(vec3(0.35, 0.55, 0.75)); + float ambient = 0.45; + float nDotL = max(0.0, dot(normalize(v_normal), sunDir)); + float lighting = min(1.0, ambient + nDotL * 0.75); + + vec4 base = tex0 * tex1 * tex2 * tex3; + vec4 tinted = base * v_color0 * u_matDiffuse; + if (tinted.a < u_atestParams.x) { discard; } - gl_FragColor = lit; + gl_FragColor = vec4(tinted.rgb * lighting, tinted.a); } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_unlit.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_unlit.sc index 43250a3c06c..2526bd04abc 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_unlit.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_unlit.sc @@ -1,9 +1,11 @@ $input v_texcoord0 // TheSuperHackers @refactor bobtista 11/04/2026 unlit textured -// fragment shader. Samples stages 0-3 and multiplies them. Stages -// 4G.4 added stages 1-3 for multi-textured UI / sprites. Unused stages -// default to a 1x1 white texture so single-texture draws are unaffected. +// fragment shader. Samples stages 0-3 straight to output. Stages +// 1-3 were added later, along with the material diffuse tint so +// UI sprites and unlit-preset meshes still pick up team colors. +// Unused stages default to a 1x1 white texture so single-texture draws +// are unaffected. #include @@ -11,6 +13,7 @@ SAMPLER2D(s_tex0, 0); SAMPLER2D(s_tex1, 1); SAMPLER2D(s_tex2, 2); SAMPLER2D(s_tex3, 3); +uniform vec4 u_matDiffuse; void main() { @@ -18,5 +21,5 @@ void main() vec4 tex1 = texture2D(s_tex1, v_texcoord0); vec4 tex2 = texture2D(s_tex2, v_texcoord0); vec4 tex3 = texture2D(s_tex3, v_texcoord0); - gl_FragColor = tex0 * tex1 * tex2 * tex3; + gl_FragColor = tex0 * tex1 * tex2 * tex3 * u_matDiffuse; } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc index 2aa3cb425c0..8abd09099e4 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc @@ -1,6 +1,8 @@ vec4 v_color0 : COLOR0 = vec4(1.0, 0.0, 0.0, 1.0); vec2 v_texcoord0 : TEXCOORD0 = vec2(0.0, 0.0); +vec3 v_normal : NORMAL = vec3(0.0, 0.0, 1.0); vec3 a_position : POSITION; +vec3 a_normal : NORMAL; vec4 a_color0 : COLOR0; vec2 a_texcoord0 : TEXCOORD0; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_solid_lit.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_solid_lit.sc index de41b577745..dea52a96bd7 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_solid_lit.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_solid_lit.sc @@ -13,5 +13,8 @@ $output v_color0 void main() { gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0)); - v_color0 = a_color0; + // D3D8 BGRA → bgfx RGBA swap + vec4 c = a_color0.bgra; + c.a = 1.0; + v_color0 = c; } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_textured_lit.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_textured_lit.sc index 84585b861ae..a9b682f8edb 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_textured_lit.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_textured_lit.sc @@ -1,16 +1,38 @@ -$input a_position, a_color0, a_texcoord0 -$output v_color0, v_texcoord0 +$input a_position, a_normal, a_color0, a_texcoord0 +$output v_color0, v_texcoord0, v_normal // TheSuperHackers @refactor bobtista 11/04/2026 lit textured // vertex shader. Transforms position by the bgfx model/view/projection -// matrix, forwards vertex diffuse (which carries the fixed-function lit -// color from the engine side) and texcoord 0. +// matrix, forwards vertex diffuse (carries team colors and prelit +// vertex brightness the engine bakes in), texcoord 0, and the world- +// space normal for the fragment shader to do a simple N.L. +// +// Fallback: if the bound FVF does not carry a diffuse +// attribute (e.g. 0x112 = XYZ|NORMAL|TEX1 used by rigid mesh containers +// for infantry/vehicles), bgfx defaults a_color0 to zero. Multiplying +// tex*0 produces black invisible meshes. Detect the zero case and swap +// in an opaque white so the texture shows through untinted, then the +// fragment shader modulates by u_matDiffuse for the team color. Meshes +// with a real diffuse attribute (terrain 0x242, etc.) are unaffected. #include void main() { gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0)); - v_color0 = a_color0; + // D3D8 vertex diffuse is D3DCOLOR (BGRA byte order) but bgfx Color0 + // expects RGBA. Swap R and B to correct the channel mapping. + // Check RGB only (not alpha) for the zero-detection: D3D11 defaults + // missing Color0 to (0,0,0,1), so including alpha makes the sum > 0 + // and the white fallback never fires for meshes without a diffuse + // attribute (infantry, vehicles). + vec4 c = a_color0.bgra; + if (c.r + c.g + c.b < 0.001) + { + c = vec4(1.0, 1.0, 1.0, 1.0); + } + c.a = 1.0; + v_color0 = c; v_texcoord0 = a_texcoord0; + v_normal = mul(u_model[0], vec4(a_normal, 0.0)).xyz; } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/sortingrenderer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/sortingrenderer.cpp index 14b51020152..b6bbec59be8 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/sortingrenderer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/sortingrenderer.cpp @@ -42,6 +42,8 @@ #include "dx8vertexbuffer.h" #include "dx8indexbuffer.h" #include "dx8wrapper.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" #include "WW3D2/vertmaterial.h" #include "texture.h" #include "d3d8.h" @@ -216,7 +218,7 @@ void SortingRendererClass::Insert_Triangles( unsigned short vertex_count) { if (!WW3D::Is_Sorting_Enabled()) { - DX8Wrapper::Draw_Triangles(start_index,polygon_count,min_vertex_index,vertex_count); + g_renderBackend->Draw_Triangles(start_index, polygon_count, min_vertex_index, vertex_count); return; } @@ -364,18 +366,36 @@ void SortingRendererClass::Insert_To_Sorting_Pool(SortingNodeStruct* state) static void Apply_Render_State(RenderStateStruct& render_state) { - DX8Wrapper::Set_Shader(render_state.shader); + // TheSuperHackers @refactor bobtista 11/04/2026 Route + // sorted batch state through g_renderBackend so the bgfx backend + // sees the per-batch shader/material/texture/world/view. Previously + // these went straight to DX8Wrapper, so the bgfx side kept whatever + // state the last opaque draw left behind and sorted particles, + // rotors, tracers, and explosions never showed up. + g_renderBackend->Set_Shader(render_state.shader); - DX8Wrapper::Set_Material(render_state.material); + g_renderBackend->Set_Material(render_state.material); for (int i=0;iGet_Max_Textures_Per_Pass();++i) { - DX8Wrapper::Set_Texture(i,render_state.Textures[i]); + g_renderBackend->Set_Texture(i,render_state.Textures[i]); } - DX8Wrapper::_Set_DX8_Transform(D3DTS_WORLD,render_state.world); - DX8Wrapper::_Set_DX8_Transform(D3DTS_VIEW,render_state.view); - + // Must use _Set_DX8_Transform (direct device write) not the + // render_state-dirty-flag path: the sort loop does not run + // Apply_Render_State_Changes between batches, so the dirty-flag + // route would leave each batch with the previous batch's device + // transform and things like helicopter rotors disappear. + DX8Wrapper::_Set_DX8_Transform(D3DTS_WORLD, render_state.world); + DX8Wrapper::_Set_DX8_Transform(D3DTS_VIEW, render_state.view); + + // feed the bgfx backend the same per-batch transforms + // via a dedicated capture hook. BgfxBackend pre-multiplies them + // into an effective world matrix and submits to its own view id + // so the opaque view is never stomped. No-op on DX8Backend. + g_renderBackend->Capture_Sorted_Batch_Transforms( + reinterpret_cast(render_state.world), + reinterpret_cast(render_state.view)); if (!render_state.material->Get_Lighting()) return; //no point changing lights if they are ignored. @@ -520,6 +540,11 @@ void SortingRendererClass::Flush_Sorting_Pool() // TheSuperHackers @fix stephanmeesters 10/06/2026 // Split rendering into chunks to prevent a crash when exceeding the 16-bit index buffer limit. constexpr const unsigned MAX_INDEX_CHUNK = 65535; + + // route bgfx submits through the dedicated sort view + // id for the rest of this flush. No-op on DX8Backend. + g_renderBackend->Begin_Sorted_Batch_Pass(); + unsigned chunkOffset = 0; while (chunkOffset < overlapping_polygon_count) { @@ -541,8 +566,8 @@ void SortingRendererClass::Flush_Sorting_Pool() // Set index buffer and render! - DX8Wrapper::Set_Index_Buffer(dyn_ib_access,0); // Override with this buffer (do something to prevent need for this!) - DX8Wrapper::Set_Vertex_Buffer(dyn_vb_access); // Override with this buffer (do something to prevent need for this!) + g_renderBackend->Set_Index_Buffer(dyn_ib_access, 0); // Override with this buffer (do something to prevent need for this!) + g_renderBackend->Set_Vertex_Buffer(dyn_vb_access); // Override with this buffer (do something to prevent need for this!) DX8Wrapper::Apply_Render_State_Changes(); @@ -554,7 +579,7 @@ void SortingRendererClass::Flush_Sorting_Pool() SortingNodeStruct* state=overlapping_nodes[node_id]; Apply_Render_State(state->sorting_state); - DX8Wrapper::Draw_Triangles( + g_renderBackend->Draw_Triangles( start_index*3, count_to_render, state->min_vertex_index, @@ -572,7 +597,7 @@ void SortingRendererClass::Flush_Sorting_Pool() SortingNodeStruct* state=overlapping_nodes[node_id]; Apply_Render_State(state->sorting_state); - DX8Wrapper::Draw_Triangles( + g_renderBackend->Draw_Triangles( start_index*3, count_to_render, state->min_vertex_index, @@ -582,6 +607,10 @@ void SortingRendererClass::Flush_Sorting_Pool() chunkOffset += chunkCount; } + // sort flush complete, resume routing bgfx submits + // through the main engine view id. + g_renderBackend->End_Sorted_Batch_Pass(); + // Release all references and return nodes back to the clean list for the frame... for (unsigned node_id=0;node_idSet_Shader(state->sorting_state.shader); + g_renderBackend->Set_Material(state->sorting_state.material); + for (int t = 0; t < DX8Wrapper::Get_Current_Caps()->Get_Max_Textures_Per_Pass(); ++t) + { + g_renderBackend->Set_Texture(t, state->sorting_state.Textures[t]); + } + g_renderBackend->Set_Vertex_Buffer(state->sorting_state.vertex_buffers[0], 0); + g_renderBackend->Set_Index_Buffer(state->sorting_state.index_buffer, + state->sorting_state.index_base_offset); + g_renderBackend->Set_Index_Buffer_Index_Offset(state->sorting_state.vba_offset); + + // Restore DX8 render state: the g_renderBackend calls above + // forwarded to DX8Wrapper which corrupted vba_offset/iba_offset + // (Set_Vertex_Buffer resets vba_offset to 0). Re-applying the + // saved state fixes this for the DX8 draw path. DX8Wrapper::Set_Render_State(state->sorting_state); - DX8Wrapper::Draw_Triangles(state->start_index,state->polygon_count,state->min_vertex_index,state->vertex_count); + + // Use the sort view for transforms to avoid stomping view 1. + g_renderBackend->Begin_Sorted_Batch_Pass(); + g_renderBackend->Capture_Sorted_Batch_Transforms( + reinterpret_cast(state->sorting_state.world), + reinterpret_cast(state->sorting_state.view)); + + g_renderBackend->Draw_Triangles(state->start_index, state->polygon_count, state->min_vertex_index, state->vertex_count); + g_renderBackend->End_Sorted_Batch_Pass(); DX8Wrapper::Release_Render_State(); Release_Refs(state); clean_list.push_front(state); @@ -636,8 +688,8 @@ void SortingRendererClass::Flush() Flush_Sorting_Pool(); DX8Wrapper::_Enable_Triangle_Draw(old_enable); - DX8Wrapper::Set_Index_Buffer(nullptr,0); - DX8Wrapper::Set_Vertex_Buffer(nullptr); + g_renderBackend->Set_Index_Buffer(nullptr, 0); + g_renderBackend->Set_Vertex_Buffer(nullptr, 0); total_sorting_vertices=0; DynamicIBAccessClass::_Reset(false); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/sphereobj.cpp b/Core/Libraries/Source/WWVegas/WW3D2/sphereobj.cpp index ad4bb0bc549..9dfc5628c81 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/sphereobj.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/sphereobj.cpp @@ -90,6 +90,8 @@ #include "dx8indexbuffer.h" #include "sortingrenderer.h" #include "visrasterizer.h" +#include "RenderBackend.h" +#include "IRenderBackend.h" static bool Sphere_Array_Valid = false; @@ -468,9 +470,9 @@ void SphereRenderObjClass::render_sphere() } else { SphereShader.Set_Texturing (ShaderClass::TEXTURING_DISABLE); } - DX8Wrapper::Set_Shader(SphereShader); - DX8Wrapper::Set_Texture(0,SphereTexture); - DX8Wrapper::Set_Material(SphereMaterial); + g_renderBackend->Set_Shader(SphereShader); + g_renderBackend->Set_Texture(0,SphereTexture); + g_renderBackend->Set_Material(SphereMaterial); // Enable sorting if the primitive is translucent, alpha testing is not enabled, and sorting is enabled globally. const bool sort = (SphereShader.Get_Dst_Blend_Func() != ShaderClass::DSTBLEND_ZERO) && (SphereShader.Get_Alpha_Test() == ShaderClass::ALPHATEST_DISABLE) && (WW3D::Is_Sorting_Enabled()); @@ -517,13 +519,13 @@ void SphereRenderObjClass::render_sphere() } } - DX8Wrapper::Set_Vertex_Buffer(vb); - DX8Wrapper::Set_Index_Buffer(ib,0); + g_renderBackend->Set_Vertex_Buffer(vb); + g_renderBackend->Set_Index_Buffer(ib,0); if (sort) { SortingRendererClass::Insert_Triangles(Get_Bounding_Sphere(), 0, mesh.face_ct, 0, mesh.Vertex_ct); } else { - DX8Wrapper::Draw_Triangles(0,mesh.face_ct,0,mesh.Vertex_ct); + g_renderBackend->Draw_Triangles(0,mesh.face_ct,0,mesh.Vertex_ct); } } @@ -670,12 +672,12 @@ void SphereRenderObjClass::Render(RenderInfoClass & rinfo) 1.0f, 0.0f, 0.0f, cpos.Z); tm.Scale(real_scale); - DX8Wrapper::Set_Transform(D3DTS_WORLD,ident); - DX8Wrapper::Set_Transform(D3DTS_VIEW,tm); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,ident); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW, tm); render_sphere(); - DX8Wrapper::Set_Transform(D3DTS_VIEW,view); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW, view); } else { - DX8Wrapper::Set_Transform(D3DTS_WORLD,temp); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,temp); render_sphere(); } } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/streakRender.cpp b/Core/Libraries/Source/WWVegas/WW3D2/streakRender.cpp index 4341c24d7b2..ef3fefc2c8f 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/streakRender.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/streakRender.cpp @@ -34,6 +34,8 @@ #include "WWMath/Vector3i.h" #include "WWLib/RANDOM.h" #include "WWMath/v3_rnd.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" /* We have chunking logic which handles N segments at a time. To simplify the subdivision logic, @@ -314,8 +316,8 @@ void StreakRendererClass::RenderStreak DX8Wrapper::Get_Transform(D3DTS_VIEW,view); Matrix4x4 identity(true); - DX8Wrapper::Set_Transform(D3DTS_WORLD,identity); - DX8Wrapper::Set_Transform(D3DTS_VIEW,identity); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,identity); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW, identity); /* ** Handle texture UV offset animation (done once for entire line). @@ -1303,7 +1305,7 @@ void StreakRendererClass::RenderStreak VertexMaterialClass *mat; mat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(mat); + g_renderBackend->Set_Material(mat); REF_PTR_RELEASE(mat); // If Texture is non-null enable texturing in shader - otherwise disable. @@ -1372,10 +1374,10 @@ void StreakRendererClass::RenderStreak } - DX8Wrapper::Set_Index_Buffer(ib_access,0); - DX8Wrapper::Set_Vertex_Buffer(Verts); - DX8Wrapper::Set_Texture(0,Texture); - DX8Wrapper::Set_Shader(shader); + g_renderBackend->Set_Index_Buffer(ib_access,0); + g_renderBackend->Set_Vertex_Buffer(Verts); + g_renderBackend->Set_Texture(0,Texture); + g_renderBackend->Set_Shader(shader); if (sorting) { @@ -1383,12 +1385,12 @@ void StreakRendererClass::RenderStreak } else { - DX8Wrapper::Draw_Triangles(0,triangleIndex,0,vnum); + g_renderBackend->Draw_Triangles(0,triangleIndex,0,vnum); } } - DX8Wrapper::Set_Transform(D3DTS_VIEW,view); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW,view); } diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/boxrobj.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/boxrobj.cpp index 2f5dc5f5e9d..b4dacf2c4e3 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/boxrobj.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/boxrobj.cpp @@ -98,6 +98,8 @@ #include "WW3D2/coltest.h" #include "WW3D2/inttest.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" #include "WW3D2/dx8indexbuffer.h" #include "WW3D2/dx8vertexbuffer.h" #include "WW3D2/dx8fvf.h" @@ -508,10 +510,10 @@ void BoxRenderObjClass::render_box(RenderInfoClass & rinfo,const Vector3 & cente DX8Wrapper::Set_Shader(_BoxShader); DX8Wrapper::Set_Texture(0,nullptr); - DX8Wrapper::Set_Index_Buffer(ibaccess,0); - DX8Wrapper::Set_Vertex_Buffer(vbaccess); + g_renderBackend->Set_Index_Buffer(ibaccess, 0); + g_renderBackend->Set_Vertex_Buffer(vbaccess); - DX8Wrapper::Draw_Triangles(buffer_type,0,NUM_BOX_FACES,0,NUM_BOX_VERTS); + g_renderBackend->Draw_Triangles(buffer_type, 0, NUM_BOX_FACES, 0, NUM_BOX_VERTS); } } @@ -1385,4 +1387,3 @@ RenderObjClass * BoxPrototypeClass::Create() */ BoxLoaderClass _BoxLoader; - diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp index 27fc3cd0230..d35816cc7be 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp @@ -58,6 +58,8 @@ #include "WWSaveLoad/persistfactory.h" #include "WW3D2/ww3dids.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" #include "WW3D2/dx8vertexbuffer.h" #include "WW3D2/dx8indexbuffer.h" #include "WW3D2/sortingrenderer.h" @@ -1201,7 +1203,7 @@ void DazzleRenderObjClass::Render_Dazzle(CameraClass* camera) return; } - DX8Wrapper::Set_Vertex_Buffer(vb_access); + g_renderBackend->Set_Vertex_Buffer(vb_access); DynamicIBAccessClass ib_access(BUFFER_TYPE_DYNAMIC_DX8,poly_count*3); { @@ -1224,24 +1226,24 @@ void DazzleRenderObjClass::Render_Dazzle(CameraClass* camera) DX8Wrapper::Set_Transform(D3DTS_PROJECTION,Matrix4x4(true)); if (halo_poly_count) { - DX8Wrapper::Set_Index_Buffer(ib_access,dazzle_vertex_count); + g_renderBackend->Set_Index_Buffer(ib_access, dazzle_vertex_count); DX8Wrapper::Set_Shader(default_halo_shader); DX8Wrapper::Set_Texture(0,types[type]->Get_Halo_Texture()); - DX8Wrapper::Draw_Triangles(0,halo_poly_count,0,vertex_count); + g_renderBackend->Draw_Triangles(0, halo_poly_count, 0, vertex_count); } if (dazzle_poly_count) { - DX8Wrapper::Set_Index_Buffer(ib_access,0); + g_renderBackend->Set_Index_Buffer(ib_access, 0); DX8Wrapper::Set_Shader(default_dazzle_shader); DX8Wrapper::Set_Texture(0,types[type]->Get_Dazzle_Texture()); - DX8Wrapper::Draw_Triangles(0,dazzle_poly_count,0,vertex_count); + g_renderBackend->Draw_Triangles(0, dazzle_poly_count, 0, vertex_count); } if (lensflare_poly_count) { - DX8Wrapper::Set_Index_Buffer(ib_access,dazzle_vertex_count+halo_vertex_count); + g_renderBackend->Set_Index_Buffer(ib_access, dazzle_vertex_count+halo_vertex_count); DX8Wrapper::Set_Shader(default_dazzle_shader); DX8Wrapper::Set_Texture(0,lensflare->Get_Texture()); - DX8Wrapper::Draw_Triangles(0,lensflare_poly_count,0,vertex_count); + g_renderBackend->Draw_Triangles(0, lensflare_poly_count, 0, vertex_count); } DX8Wrapper::Set_Transform(D3DTS_PROJECTION,old_projection_transform); diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/decalmsh.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/decalmsh.cpp index 4448ba801db..5cf59a15bfb 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/decalmsh.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/decalmsh.cpp @@ -63,6 +63,8 @@ #include "WWLib/simplevec.h" #include "WW3D2/texture.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" #include "WW3D2/dx8caps.h" #define DISABLE_CLIPPING 0 @@ -354,9 +356,9 @@ void RigidDecalMeshClass::Render() while (next_poly_index < Polys.Count()) { next_poly_index = Process_Material_Run(cur_poly_index); - DX8Wrapper::Set_Index_Buffer(dynamic_ib,0); - DX8Wrapper::Set_Vertex_Buffer(dynamic_vb); - DX8Wrapper::Draw_Triangles( 3*cur_poly_index, + g_renderBackend->Set_Index_Buffer(dynamic_ib, 0); + g_renderBackend->Set_Vertex_Buffer(dynamic_vb); + g_renderBackend->Draw_Triangles(3*cur_poly_index, (next_poly_index - cur_poly_index), // poly count Polys[cur_poly_index].I, 1 + Polys[next_poly_index-1].K - Polys[cur_poly_index].I); @@ -852,9 +854,9 @@ void SkinDecalMeshClass::Render() while (next_poly_index < Polys.Count()) { next_poly_index = Process_Material_Run(cur_poly_index); - DX8Wrapper::Set_Index_Buffer(dynamic_ib,0); - DX8Wrapper::Set_Vertex_Buffer(dynamic_vb); - DX8Wrapper::Draw_Triangles(3*cur_poly_index, + g_renderBackend->Set_Index_Buffer(dynamic_ib, 0); + g_renderBackend->Set_Vertex_Buffer(dynamic_vb); + g_renderBackend->Draw_Triangles(3*cur_poly_index, (next_poly_index - cur_poly_index), // poly count Polys[cur_poly_index].I, 1 + Polys[next_poly_index-1].K - Polys[cur_poly_index].I); diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/linegrp.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/linegrp.cpp index e61142bc226..a5cbec2ea21 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/linegrp.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/linegrp.cpp @@ -43,6 +43,8 @@ #include "vertmaterial.h" #include "WW3D2/dx8wrapper.h" #include "WWMath/wwmath.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" #include "rinfo.h" #include "camera.h" #include "WW3D2/dx8indexbuffer.h" @@ -465,13 +467,13 @@ void LineGroupClass::Render(RenderInfoClass &rinfo) } } - DX8Wrapper::Set_Index_Buffer(iba, 0); - DX8Wrapper::Set_Vertex_Buffer(vba); + g_renderBackend->Set_Index_Buffer(iba, 0); + g_renderBackend->Set_Vertex_Buffer(vba); if (sort) { SortingRendererClass::Insert_Triangles(0, num_tris, 0, num_vertices); } else { - DX8Wrapper::Draw_Triangles(0, num_tris, 0, num_vertices); + g_renderBackend->Draw_Triangles(0, num_tris, 0, num_vertices); } // restore the matrices diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/mesh.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/mesh.cpp index 0da89d80964..30627fb2961 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/mesh.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/mesh.cpp @@ -89,6 +89,8 @@ #include "mesh.h" #include #include "w3d_file.h" +#include "RenderBackend.h" +#include "IRenderBackend.h" #include "assetmgr.h" #include "WW3D2/w3derr.h" #include "WWDebug/wwdebug.h" @@ -852,10 +854,10 @@ void MeshClass::Render_Material_Pass(MaterialPassClass * pass,IndexBufferClass * } } pass->Install_Materials(); - DX8Wrapper::Set_Index_Buffer(ib,0); + g_renderBackend->Set_Index_Buffer(ib, 0); SNAPSHOT_SAY(("Set_World_Identity")); - DX8Wrapper::Set_World_Identity(); + g_renderBackend->Set_World_Identity(); DX8PolygonRendererListIterator it(&Model->PolygonRendererList); while (!it.Is_Done()) { @@ -945,10 +947,10 @@ void MeshClass::Render_Material_Pass(MaterialPassClass * pass,IndexBufferClass * int vertex_offset = Model->PolygonRendererList.Peek_Head()->Get_Vertex_Offset(); pass->Install_Materials(); - DX8Wrapper::Set_Transform(D3DTS_WORLD,Get_Transform()); - DX8Wrapper::Set_Index_Buffer(dynamic_ib,vertex_offset); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD, Get_Transform()); + g_renderBackend->Set_Index_Buffer(dynamic_ib, vertex_offset); - DX8Wrapper::Draw_Triangles( + g_renderBackend->Draw_Triangles( 0, temp_apt.Count(), min_v, @@ -978,10 +980,10 @@ void MeshClass::Render_Material_Pass(MaterialPassClass * pass,IndexBufferClass * } } pass->Install_Materials(); - DX8Wrapper::Set_Index_Buffer(ib,0); + g_renderBackend->Set_Index_Buffer(ib, 0); SNAPSHOT_SAY(("Set_World_Transform")); - DX8Wrapper::Set_Transform(D3DTS_WORLD,Transform); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD, Transform); DX8PolygonRendererListIterator it(&Model->PolygonRendererList); while (!it.Is_Done()) { From 434bf564cd3006e24858cd07fe6d81a23857d246 Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 21 Apr 2026 14:02:47 -0400 Subject: [PATCH 048/523] feat(bgfx): uber shader with TSS operation emulation (cherry picked from commit e353014867719c71110389108702034d842ac890) --- .../Source/WWVegas/WW3D2/BgfxBackend.cpp | 371 +++++++++++------- .../Source/WWVegas/WW3D2/CMakeLists.txt | 15 +- .../WWVegas/WW3D2/shaders/fs_solid_lit.sc | 16 - .../WWVegas/WW3D2/shaders/fs_textured_lit.sc | 39 -- .../WW3D2/shaders/fs_textured_lit_atest.sc | 45 --- .../WW3D2/shaders/fs_textured_unlit.sc | 25 -- .../Source/WWVegas/WW3D2/shaders/fs_uber.sc | 160 ++++++++ .../WWVegas/WW3D2/shaders/vs_solid_lit.sc | 20 - .../WWVegas/WW3D2/shaders/vs_textured_lit.sc | 38 -- .../WW3D2/shaders/vs_textured_unlit.sc | 15 - .../Source/WWVegas/WW3D2/shaders/vs_uber.sc | 24 ++ 11 files changed, 421 insertions(+), 347 deletions(-) delete mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_solid_lit.sc delete mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit.sc delete mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit_atest.sc delete mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_unlit.sc create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc delete mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_solid_lit.sc delete mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_textured_lit.sc delete mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_textured_unlit.sc create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_uber.sc diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp index 2c7199850d8..849951fcc7e 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -71,16 +71,11 @@ #include "vs_passthrough_dx11.bin.h" #include "fs_passthrough_dx11.bin.h" -// TheSuperHackers @refactor bobtista 11/04/2026 preset shader -// programs. These cover the three most common ShaderClass preset buckets -// (lit textured, unlit textured, alpha-tested lit textured). See -#include "vs_textured_lit_dx11.bin.h" -#include "fs_textured_lit_dx11.bin.h" -#include "vs_textured_unlit_dx11.bin.h" -#include "fs_textured_unlit_dx11.bin.h" -#include "fs_textured_lit_atest_dx11.bin.h" -#include "vs_solid_lit_dx11.bin.h" -#include "fs_solid_lit_dx11.bin.h" +// TheSuperHackers @refactor bobtista 12/04/2026 uber shader pair. +// Single program handles all TSS combinations via uniforms. Replaces the +// the earlier per-preset shader pairs. +#include "vs_uber_dx11.bin.h" +#include "fs_uber_dx11.bin.h" namespace { @@ -113,14 +108,9 @@ const int kBgfxWindowHeight = 500; // succeeds, destroyed in Shutdown before bgfx::shutdown. bgfx::ProgramHandle g_passthroughProgram = BGFX_INVALID_HANDLE; -// TheSuperHackers @refactor bobtista 11/04/2026 preset shader -// programs. Created in Initialize, destroyed in Shutdown. Not yet used by -// any draw call - that wiring lands in a later session along with the -// ShaderClass preset -> ProgramHandle mapping table. -bgfx::ProgramHandle g_texturedLitProgram = BGFX_INVALID_HANDLE; -bgfx::ProgramHandle g_texturedUnlitProgram = BGFX_INVALID_HANDLE; -bgfx::ProgramHandle g_texturedLitAtestProgram = BGFX_INVALID_HANDLE; -bgfx::ProgramHandle g_solidLitProgram = BGFX_INVALID_HANDLE; +// TheSuperHackers @refactor bobtista 12/04/2026 uber shader +// program. Single program handles all TSS combinations via uniforms. +bgfx::ProgramHandle g_uberProgram = BGFX_INVALID_HANDLE; // Sampler uniform shared by all textured fragment shaders. Bound to stage 0. bgfx::UniformHandle g_sTex0 = BGFX_INVALID_HANDLE; @@ -140,6 +130,14 @@ float g_currentMatDiffuse[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; // u_alphaRef4 conflict. See fs_textured_lit_atest.sc for details. bgfx::UniformHandle g_uAtestParams = BGFX_INVALID_HANDLE; +// TSS operation uniforms. Encode the DX8 texture stage state +// operations so the uber fragment shader can evaluate them at runtime. +bgfx::UniformHandle g_uTssOps0 = BGFX_INVALID_HANDLE; +bgfx::UniformHandle g_uTssOps1 = BGFX_INVALID_HANDLE; +float g_currentTssOps0[4] = { 3.0f, 3.0f, 0.0f, 0.0f }; // default: MODULATE color+alpha, no secondary +float g_currentTssOps1[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; // default: arg1=TEXTURE for all +float g_currentAtestRef = 0.0f; // alpha test reference, 0 = disabled + // TheSuperHackers @refactor bobtista 11/04/2026 default 1x1 // white texture. Real Set_Texture wiring is not in place yet, so the // textured shaders need SOMETHING bound to s_tex0 or D3D11 returns @@ -355,29 +353,162 @@ uint64_t TranslateDepthCompare(ShaderClass::DepthCompareType cmp) } } -bgfx::ProgramHandle PickProgramForShader(const ShaderClass & shader) +// Extract TSS operation IDs from ShaderClass preset bits. +// Maps the same logic as shader.cpp's Apply() into float IDs that the +// uber fragment shader evaluates at runtime. +// +// TSS op IDs must match the #defines in fs_uber.sc: +// 0=DISABLE 1=SELECTARG1 2=SELECTARG2 3=MODULATE 4=MODULATE2X +// 5=ADD 6=ADDSIGNED 7=SUBTRACT 8=BLENDTEXALPHA 9=BLENDCURALPHA 10=ADDSMOOTH +// Arg source IDs: 0=TEXTURE 1=DIFFUSE 2=CURRENT + +void BuildTssOpsForShader(const ShaderClass & shader, + float * ops0, float * ops1, float * atestRef) { - const bool textured = (shader.Get_Texturing() != ShaderClass::TEXTURING_DISABLE); - const bool lit = (shader.Get_Primary_Gradient() != ShaderClass::GRADIENT_DISABLE); - const bool atest = (shader.Get_Alpha_Test() != ShaderClass::ALPHATEST_DISABLE); + float priColorOp = 3.0f; // MODULATE + float priAlphaOp = 3.0f; // MODULATE + float priCArg1Src = 0.0f; // TEXTURE + float priAArg1Src = 0.0f; // TEXTURE + float secColorOp = 0.0f; // DISABLE + float secAlphaOp = 0.0f; // DISABLE + float secCArg1Src = 0.0f; // TEXTURE + float secAArg1Src = 0.0f; // TEXTURE + + if (shader.Get_Texturing() == ShaderClass::TEXTURING_ENABLE) + { + switch (shader.Get_Primary_Gradient()) + { + case ShaderClass::GRADIENT_DISABLE: + priColorOp = 1.0f; // SELECTARG1 + priAlphaOp = 1.0f; // SELECTARG1 + priCArg1Src = 0.0f; // TEXTURE + priAArg1Src = 0.0f; // TEXTURE + break; + default: + case ShaderClass::GRADIENT_MODULATE: + priColorOp = 3.0f; // MODULATE + priAlphaOp = 3.0f; // MODULATE + priCArg1Src = 0.0f; // TEXTURE + priAArg1Src = 0.0f; // TEXTURE + break; + case ShaderClass::GRADIENT_ADD: + priColorOp = 5.0f; // ADD + priAlphaOp = 3.0f; // MODULATE (alpha always modulates) + priCArg1Src = 0.0f; // TEXTURE + priAArg1Src = 0.0f; // TEXTURE + break; + case ShaderClass::GRADIENT_MODULATE2X: + priColorOp = 4.0f; // MODULATE2X + priAlphaOp = 3.0f; // MODULATE + priCArg1Src = 0.0f; // TEXTURE + priAArg1Src = 0.0f; // TEXTURE + break; + case ShaderClass::GRADIENT_BUMPENVMAP: + case ShaderClass::GRADIENT_BUMPENVMAPLUMINANCE: + priColorOp = 1.0f; // SELECTARG1 + priAlphaOp = 1.0f; // SELECTARG1 + priCArg1Src = 1.0f; // DIFFUSE + priAArg1Src = 1.0f; // DIFFUSE + break; + } - if (atest && textured) - { - return g_texturedLitAtestProgram; + switch (shader.Get_Post_Detail_Color_Func()) + { + default: + case ShaderClass::DETAILCOLOR_DISABLE: + secColorOp = 0.0f; + break; + case ShaderClass::DETAILCOLOR_DETAIL: + secColorOp = 1.0f; // SELECTARG1 + secCArg1Src = 0.0f; // TEXTURE + break; + case ShaderClass::DETAILCOLOR_SCALE: + secColorOp = 3.0f; // MODULATE + secCArg1Src = 0.0f; // TEXTURE + break; + case ShaderClass::DETAILCOLOR_INVSCALE: + secColorOp = 10.0f; // ADDSMOOTH + secCArg1Src = 0.0f; // TEXTURE + break; + case ShaderClass::DETAILCOLOR_ADD: + secColorOp = 5.0f; // ADD + secCArg1Src = 0.0f; // TEXTURE + break; + case ShaderClass::DETAILCOLOR_SUB: + secColorOp = 7.0f; // SUBTRACT (current - tex) + secCArg1Src = 2.0f; // CURRENT as arg1 (so result = current - tex) + break; + case ShaderClass::DETAILCOLOR_SUBR: + secColorOp = 7.0f; // SUBTRACT (tex - current) + secCArg1Src = 0.0f; // TEXTURE + break; + case ShaderClass::DETAILCOLOR_BLEND: + secColorOp = 9.0f; // BLENDCURRENTALPHA + secCArg1Src = 0.0f; // TEXTURE + break; + case ShaderClass::DETAILCOLOR_DETAILBLEND: + secColorOp = 8.0f; // BLENDTEXTUREALPHA + secCArg1Src = 0.0f; // TEXTURE + break; + } + + switch (shader.Get_Post_Detail_Alpha_Func()) + { + default: + case ShaderClass::DETAILALPHA_DISABLE: + secAlphaOp = 0.0f; + break; + case ShaderClass::DETAILALPHA_DETAIL: + secAlphaOp = 1.0f; // SELECTARG1 + secAArg1Src = 0.0f; // TEXTURE + break; + case ShaderClass::DETAILALPHA_SCALE: + secAlphaOp = 3.0f; // MODULATE + secAArg1Src = 0.0f; // TEXTURE + break; + case ShaderClass::DETAILALPHA_INVSCALE: + secAlphaOp = 10.0f; // ADDSMOOTH + secAArg1Src = 0.0f; // TEXTURE + break; + } } - if (textured && lit) + else { - return g_texturedLitProgram; + switch (shader.Get_Primary_Gradient()) + { + case ShaderClass::GRADIENT_DISABLE: + priColorOp = 0.0f; // DISABLE (output black/default) + priAlphaOp = 0.0f; + break; + default: + case ShaderClass::GRADIENT_MODULATE: + case ShaderClass::GRADIENT_ADD: + priColorOp = 2.0f; // SELECTARG2 + priAlphaOp = 2.0f; // SELECTARG2 + priCArg1Src = 0.0f; + priAArg1Src = 0.0f; + break; + } } - if (textured && !lit) + + ops0[0] = priColorOp; + ops0[1] = priAlphaOp; + ops0[2] = secColorOp; + ops0[3] = secAlphaOp; + + ops1[0] = priCArg1Src; + ops1[1] = priAArg1Src; + ops1[2] = secCArg1Src; + ops1[3] = secAArg1Src; + + if (shader.Get_Alpha_Test() != ShaderClass::ALPHATEST_DISABLE) { - return g_texturedUnlitProgram; + *atestRef = 0x60 / 255.0f; } - if (!textured && lit) + else { - return g_solidLitProgram; + *atestRef = 0.0f; } - return g_passthroughProgram; } uint64_t BuildBgfxStateForShader(const ShaderClass & shader) @@ -411,7 +542,7 @@ uint64_t BuildBgfxStateForShader(const ShaderClass & shader) return state; } -[[maybe_unused]] const auto kShaderTranslationAnchor = &PickProgramForShader; +[[maybe_unused]] const auto kShaderTranslationAnchor = &BuildTssOpsForShader; // TheSuperHackers @refactor bobtista 11/04/2026 generic FVF // to bgfx::VertexLayout translator. Walks the FVFInfoClass offset @@ -912,11 +1043,8 @@ bool RecordPresetIfNew(unsigned int bits) const char * ProgramDebugName(bgfx::ProgramHandle h) { - if (h.idx == g_passthroughProgram.idx) return "passthrough"; - if (h.idx == g_texturedLitProgram.idx) return "textured_lit"; - if (h.idx == g_texturedUnlitProgram.idx) return "textured_unlit"; - if (h.idx == g_texturedLitAtestProgram.idx) return "textured_lit_atest"; - if (h.idx == g_solidLitProgram.idx) return "solid_lit"; + if (h.idx == g_passthroughProgram.idx) return "passthrough"; + if (h.idx == g_uberProgram.idx) return "uber"; return "?"; } @@ -1112,17 +1240,14 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) WWDEBUG_SAY(("[BgfxBackend] passthrough shader createShader FAILED.")); } - // TheSuperHackers @refactor bobtista 11/04/2026 build the - // three preset shader programs. They share two vertex shaders and three - // fragment shaders. The lit-atest program reuses vs_textured_lit. Each - // createProgram(..., true) hands ownership of its shader handles to the - // program, so we must NOT manually destroy them later. g_sTex0 = bgfx::createUniform("s_tex0", bgfx::UniformType::Sampler); g_sTex1 = bgfx::createUniform("s_tex1", bgfx::UniformType::Sampler); g_sTex2 = bgfx::createUniform("s_tex2", bgfx::UniformType::Sampler); g_sTex3 = bgfx::createUniform("s_tex3", bgfx::UniformType::Sampler); g_uMatDiffuse = bgfx::createUniform("u_matDiffuse", bgfx::UniformType::Vec4); g_uAtestParams = bgfx::createUniform("u_atestParams", bgfx::UniformType::Vec4); + g_uTssOps0 = bgfx::createUniform("u_tssOps0", bgfx::UniformType::Vec4); + g_uTssOps1 = bgfx::createUniform("u_tssOps1", bgfx::UniformType::Vec4); // default 1x1 white texture (opaque ABGR=0xffffffff). static const uint8_t kWhitePixel[4] = { 0xff, 0xff, 0xff, 0xff }; @@ -1132,84 +1257,30 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) BGFX_TEXTURE_NONE | BGFX_SAMPLER_POINT, bgfx::copy(kWhitePixel, sizeof(kWhitePixel))); - const bgfx::Memory * vsLitMem = bgfx::makeRef(vs_textured_lit_dx11, - sizeof(vs_textured_lit_dx11)); - const bgfx::Memory * fsLitMem = bgfx::makeRef(fs_textured_lit_dx11, - sizeof(fs_textured_lit_dx11)); - const bgfx::Memory * vsUnlitMem = bgfx::makeRef(vs_textured_unlit_dx11, - sizeof(vs_textured_unlit_dx11)); - const bgfx::Memory * fsUnlitMem = bgfx::makeRef(fs_textured_unlit_dx11, - sizeof(fs_textured_unlit_dx11)); - const bgfx::Memory * fsAtestMem = bgfx::makeRef(fs_textured_lit_atest_dx11, - sizeof(fs_textured_lit_atest_dx11)); - - bgfx::ShaderHandle vsLit = bgfx::createShader(vsLitMem); - bgfx::ShaderHandle fsLit = bgfx::createShader(fsLitMem); - bgfx::ShaderHandle vsUnlit = bgfx::createShader(vsUnlitMem); - bgfx::ShaderHandle fsUnlit = bgfx::createShader(fsUnlitMem); - bgfx::ShaderHandle fsAtest = bgfx::createShader(fsAtestMem); - - if (bgfx::isValid(vsLit) && bgfx::isValid(fsLit)) - { - bgfx::setName(vsLit, "vs_textured_lit"); - bgfx::setName(fsLit, "fs_textured_lit"); - // vsLit is also used by the atest program; createProgram(..., true) - // would destroy it after the first call. Pass false here, then - // again for the atest program; we destroy vsLit ourselves below. - g_texturedLitProgram = bgfx::createProgram(vsLit, fsLit, false); - } - if (bgfx::isValid(vsUnlit) && bgfx::isValid(fsUnlit)) - { - bgfx::setName(vsUnlit, "vs_textured_unlit"); - bgfx::setName(fsUnlit, "fs_textured_unlit"); - g_texturedUnlitProgram = bgfx::createProgram(vsUnlit, fsUnlit, true); - } - if (bgfx::isValid(vsLit) && bgfx::isValid(fsAtest)) - { - bgfx::setName(fsAtest, "fs_textured_lit_atest"); - g_texturedLitAtestProgram = bgfx::createProgram(vsLit, fsAtest, false); - } - // Now safe to release standalone shader handles - the programs hold - // their own internal refs. - if (bgfx::isValid(vsLit)) - { - bgfx::destroy(vsLit); - } - if (bgfx::isValid(fsLit)) + // Uber shader program — single program for all engine draws. + const bgfx::Memory * vsUberMem = bgfx::makeRef(vs_uber_dx11, sizeof(vs_uber_dx11)); + const bgfx::Memory * fsUberMem = bgfx::makeRef(fs_uber_dx11, sizeof(fs_uber_dx11)); + bgfx::ShaderHandle vsUber = bgfx::createShader(vsUberMem); + bgfx::ShaderHandle fsUber = bgfx::createShader(fsUberMem); + if (bgfx::isValid(vsUber) && bgfx::isValid(fsUber)) { - bgfx::destroy(fsLit); + bgfx::setName(vsUber, "vs_uber"); + bgfx::setName(fsUber, "fs_uber"); + g_uberProgram = bgfx::createProgram(vsUber, fsUber, true); } - if (bgfx::isValid(fsAtest)) - { - bgfx::destroy(fsAtest); - } - - // Solid lit shader (bucket C) - vertex color only, no texture sample. - const bgfx::Memory * vsSolidMem = bgfx::makeRef(vs_solid_lit_dx11, - sizeof(vs_solid_lit_dx11)); - const bgfx::Memory * fsSolidMem = bgfx::makeRef(fs_solid_lit_dx11, - sizeof(fs_solid_lit_dx11)); - bgfx::ShaderHandle vsSolid = bgfx::createShader(vsSolidMem); - bgfx::ShaderHandle fsSolid = bgfx::createShader(fsSolidMem); - if (bgfx::isValid(vsSolid) && bgfx::isValid(fsSolid)) + else { - bgfx::setName(vsSolid, "vs_solid_lit"); - bgfx::setName(fsSolid, "fs_solid_lit"); - g_solidLitProgram = bgfx::createProgram(vsSolid, fsSolid, true); + WWDEBUG_SAY(("[BgfxBackend] uber shader createShader FAILED.")); } const bgfx::RendererType::Enum selected = bgfx::getRendererType(); const char * rendererName = bgfx::getRendererName(selected); WWDEBUG_SAY(("[BgfxBackend] bgfx::init OK on debug window " - "(renderer=%s, %dx%d, hwnd=%p, passthrough=%s, " - "lit=%s, unlit=%s, lit_atest=%s, solid=%s).", + "(renderer=%s, %dx%d, hwnd=%p, passthrough=%s, uber=%s).", rendererName, kBgfxWindowWidth, kBgfxWindowHeight, g_bgfxWindow, - bgfx::isValid(g_passthroughProgram) ? "ok" : "FAILED", - bgfx::isValid(g_texturedLitProgram) ? "ok" : "FAILED", - bgfx::isValid(g_texturedUnlitProgram) ? "ok" : "FAILED", - bgfx::isValid(g_texturedLitAtestProgram) ? "ok" : "FAILED", - bgfx::isValid(g_solidLitProgram) ? "ok" : "FAILED")); + bgfx::isValid(g_passthroughProgram) ? "ok" : "FAILED", + bgfx::isValid(g_uberProgram) ? "ok" : "FAILED")); } void BgfxBackend::Shutdown() @@ -1221,25 +1292,10 @@ void BgfxBackend::Shutdown() bgfx::destroy(g_passthroughProgram); g_passthroughProgram = BGFX_INVALID_HANDLE; } - if (bgfx::isValid(g_texturedLitProgram)) - { - bgfx::destroy(g_texturedLitProgram); - g_texturedLitProgram = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_texturedUnlitProgram)) - { - bgfx::destroy(g_texturedUnlitProgram); - g_texturedUnlitProgram = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_texturedLitAtestProgram)) - { - bgfx::destroy(g_texturedLitAtestProgram); - g_texturedLitAtestProgram = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_solidLitProgram)) + if (bgfx::isValid(g_uberProgram)) { - bgfx::destroy(g_solidLitProgram); - g_solidLitProgram = BGFX_INVALID_HANDLE; + bgfx::destroy(g_uberProgram); + g_uberProgram = BGFX_INVALID_HANDLE; } if (bgfx::isValid(g_sTex0)) { @@ -1271,6 +1327,16 @@ void BgfxBackend::Shutdown() bgfx::destroy(g_uAtestParams); g_uAtestParams = BGFX_INVALID_HANDLE; } + if (bgfx::isValid(g_uTssOps0)) + { + bgfx::destroy(g_uTssOps0); + g_uTssOps0 = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(g_uTssOps1)) + { + bgfx::destroy(g_uTssOps1); + g_uTssOps1 = BGFX_INVALID_HANDLE; + } if (bgfx::isValid(g_defaultWhiteTexture)) { bgfx::destroy(g_defaultWhiteTexture); @@ -1889,6 +1955,20 @@ void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, bgfx::setUniform(g_uMatDiffuse, g_currentMatDiffuse); } + if (bgfx::isValid(g_uTssOps0)) + { + bgfx::setUniform(g_uTssOps0, g_currentTssOps0); + } + if (bgfx::isValid(g_uTssOps1)) + { + bgfx::setUniform(g_uTssOps1, g_currentTssOps1); + } + if (bgfx::isValid(g_uAtestParams)) + { + float atestParams[4] = { g_currentAtestRef, 0.0f, 0.0f, 0.0f }; + bgfx::setUniform(g_uAtestParams, atestParams); + } + uint64_t state = (g_currentBgfxState != 0) ? g_currentBgfxState : (BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); @@ -2013,22 +2093,17 @@ void BgfxBackend::Set_Shader(const ShaderClass & shader) { DX8Backend::Set_Shader(shader); - // TheSuperHackers @refactor bobtista 11/04/2026 cache the - // picked program and state bits for the next draw call, and log any - // shader bit pattern we have not seen before (capped at kMaxLoggedPresets - // to keep WWDebug.txt readable). No draw call consumes the cached state - // yet - this is purely diagnostic so we can confirm the engine is - // routing Set_Shader through the bgfx backend and see which preset - // buckets the game actually uses. - g_currentBgfxProgram = PickProgramForShader(shader); + g_currentBgfxProgram = g_uberProgram; g_currentBgfxState = BuildBgfxStateForShader(shader); + BuildTssOpsForShader(shader, g_currentTssOps0, g_currentTssOps1, &g_currentAtestRef); if (RecordPresetIfNew(shader.Get_Bits())) { WWDEBUG_SAY(("[BgfxBackend] Set_Shader new preset bits=0x%08x -> " - "program=%s state=0x%016llx", + "tssOps=(%.0f,%.0f,%.0f,%.0f) state=0x%016llx", shader.Get_Bits(), - ProgramDebugName(g_currentBgfxProgram), + g_currentTssOps0[0], g_currentTssOps0[1], + g_currentTssOps0[2], g_currentTssOps0[3], static_cast(g_currentBgfxState))); } } @@ -2416,6 +2491,24 @@ void SubmitEngineDraw(unsigned short start_index, bgfx::setUniform(g_uMatDiffuse, g_currentMatDiffuse); } + // Push TSS operation uniforms so the uber fragment shader + // knows how to combine texture stages. + if (bgfx::isValid(g_uTssOps0)) + { + bgfx::setUniform(g_uTssOps0, g_currentTssOps0); + } + if (bgfx::isValid(g_uTssOps1)) + { + bgfx::setUniform(g_uTssOps1, g_currentTssOps1); + } + // Push alpha test reference. Previously created but never + // set per-draw — now extracted from ShaderClass in Set_Shader. + if (bgfx::isValid(g_uAtestParams)) + { + float atestParams[4] = { g_currentAtestRef, 0.0f, 0.0f, 0.0f }; + bgfx::setUniform(g_uAtestParams, atestParams); + } + // Use the cached state if it was built; otherwise fall back to a // sane default that at least lets the draw run. uint64_t state = (g_currentBgfxState != 0) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt index 89c85cd6080..4190721f827 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt @@ -258,16 +258,11 @@ if(GGC_RENDER_BACKEND STREQUAL "bgfx") # z_ww3d2 inherit the include directory and build order dependency. ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_passthrough.sc) ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_passthrough.sc) - # TheSuperHackers @refactor bobtista 11/04/2026 preset - # shader pairs - lit textured (bucket A), unlit textured (bucket B) - # and alpha-tested variant (bucket D). - ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_textured_lit.sc) - ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_textured_lit.sc) - ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_textured_unlit.sc) - ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_textured_unlit.sc) - ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_textured_lit_atest.sc) - ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_solid_lit.sc) - ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_solid_lit.sc) + # TheSuperHackers @refactor bobtista 12/04/2026 uber shader + # pair. Single program handles all TSS combinations via uniforms. + # Replaces the earlier per-preset shader pairs. + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_uber.sc) + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_uber.sc) target_link_libraries(corei_ww3d2 INTERFACE ggc_bgfx_shaders) endif() diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_solid_lit.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_solid_lit.sc deleted file mode 100644 index b8983f0d8c0..00000000000 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_solid_lit.sc +++ /dev/null @@ -1,16 +0,0 @@ -$input v_color0 - -// TheSuperHackers @refactor bobtista 11/04/2026 lit solid -// fragment shader. Outputs the interpolated vertex diffuse tinted by -// the material diffuse so untextured meshes can still pick up team -// colors. Equivalent to D3DTOP_SELECTARG1(D3DTA_DIFFUSE) but with the -// W3D vertex material diffuse multiplied in. - -#include - -uniform vec4 u_matDiffuse; - -void main() -{ - gl_FragColor = v_color0 * u_matDiffuse; -} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit.sc deleted file mode 100644 index ce21ae7c0b6..00000000000 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit.sc +++ /dev/null @@ -1,39 +0,0 @@ -$input v_color0, v_texcoord0, v_normal - -// TheSuperHackers @refactor bobtista 11/04/2026 lit textured -// fragment shader. Samples stages 0-3, modulates by interpolated -// vertex diffuse, applies a hardcoded directional + ambient light so -// meshes have depth, and tints by the material diffuse (team color). -// -// Lighting model is intentionally minimal - one sun -// direction + ambient - because the W3D fixed-function light -// environment isn't wired into the bgfx path yet. Good enough to -// read unit silhouettes and shading at a glance. - -#include - -SAMPLER2D(s_tex0, 0); -SAMPLER2D(s_tex1, 1); -SAMPLER2D(s_tex2, 2); -SAMPLER2D(s_tex3, 3); -uniform vec4 u_matDiffuse; - -void main() -{ - vec4 tex0 = texture2D(s_tex0, v_texcoord0); - vec4 tex1 = texture2D(s_tex1, v_texcoord0); - vec4 tex2 = texture2D(s_tex2, v_texcoord0); - vec4 tex3 = texture2D(s_tex3, v_texcoord0); - - // Simple headlamp + ambient. sunDir points from the surface - // toward the sun. Ambient raises the floor so backfaces are not - // pitch black. - vec3 sunDir = normalize(vec3(0.35, 0.55, 0.75)); - float ambient = 0.45; - float nDotL = max(0.0, dot(normalize(v_normal), sunDir)); - float lighting = min(1.0, ambient + nDotL * 0.75); - - vec4 base = tex0 * tex1 * tex2 * tex3; - vec4 tinted = base * v_color0 * u_matDiffuse; - gl_FragColor = vec4(tinted.rgb * lighting, tinted.a); -} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit_atest.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit_atest.sc deleted file mode 100644 index 3df26b66c97..00000000000 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_lit_atest.sc +++ /dev/null @@ -1,45 +0,0 @@ -$input v_color0, v_texcoord0, v_normal - -// TheSuperHackers @refactor bobtista 11/04/2026 alpha-tested -// lit textured fragment shader. Same as fs_textured_lit but discards -// fragments whose sampled alpha falls below u_atestParams.x. Used by -// the ATest* presets (trees, fences, see-through foliage). Stages -// 1-3 were added later, along with lighting + material diffuse. -// -// u_atestParams: x = reference in [0,1]. The engine writes -// ShaderClass::Get_Alpha_Reference() normalized by 255 into .x. -// -// Note: deliberately NOT named u_alphaRef - bgfx_shader.sh has an -// internal u_alphaRef4 from its legacy state-based alpha test path, -// and shaderc rewrites uniforms ending in vec4 size, causing a -// "redefinition of u_alphaRef4" error in the generated HLSL. - -#include - -SAMPLER2D(s_tex0, 0); -SAMPLER2D(s_tex1, 1); -SAMPLER2D(s_tex2, 2); -SAMPLER2D(s_tex3, 3); -uniform vec4 u_matDiffuse; -uniform vec4 u_atestParams; - -void main() -{ - vec4 tex0 = texture2D(s_tex0, v_texcoord0); - vec4 tex1 = texture2D(s_tex1, v_texcoord0); - vec4 tex2 = texture2D(s_tex2, v_texcoord0); - vec4 tex3 = texture2D(s_tex3, v_texcoord0); - - vec3 sunDir = normalize(vec3(0.35, 0.55, 0.75)); - float ambient = 0.45; - float nDotL = max(0.0, dot(normalize(v_normal), sunDir)); - float lighting = min(1.0, ambient + nDotL * 0.75); - - vec4 base = tex0 * tex1 * tex2 * tex3; - vec4 tinted = base * v_color0 * u_matDiffuse; - if (tinted.a < u_atestParams.x) - { - discard; - } - gl_FragColor = vec4(tinted.rgb * lighting, tinted.a); -} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_unlit.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_unlit.sc deleted file mode 100644 index 2526bd04abc..00000000000 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_textured_unlit.sc +++ /dev/null @@ -1,25 +0,0 @@ -$input v_texcoord0 - -// TheSuperHackers @refactor bobtista 11/04/2026 unlit textured -// fragment shader. Samples stages 0-3 straight to output. Stages -// 1-3 were added later, along with the material diffuse tint so -// UI sprites and unlit-preset meshes still pick up team colors. -// Unused stages default to a 1x1 white texture so single-texture draws -// are unaffected. - -#include - -SAMPLER2D(s_tex0, 0); -SAMPLER2D(s_tex1, 1); -SAMPLER2D(s_tex2, 2); -SAMPLER2D(s_tex3, 3); -uniform vec4 u_matDiffuse; - -void main() -{ - vec4 tex0 = texture2D(s_tex0, v_texcoord0); - vec4 tex1 = texture2D(s_tex1, v_texcoord0); - vec4 tex2 = texture2D(s_tex2, v_texcoord0); - vec4 tex3 = texture2D(s_tex3, v_texcoord0); - gl_FragColor = tex0 * tex1 * tex2 * tex3 * u_matDiffuse; -} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc new file mode 100644 index 00000000000..e4c075a15f5 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc @@ -0,0 +1,160 @@ +$input v_color0, v_texcoord0, v_normal + +#include + +SAMPLER2D(s_tex0, 0); +SAMPLER2D(s_tex1, 1); +SAMPLER2D(s_tex2, 2); +SAMPLER2D(s_tex3, 3); +uniform vec4 u_matDiffuse; +uniform vec4 u_atestParams; +uniform vec4 u_tssOps0; // (priColorOp, priAlphaOp, secColorOp, secAlphaOp) +uniform vec4 u_tssOps1; // (priColorArg1Src, priAlphaArg1Src, secColorArg1Src, secAlphaArg1Src) + +// TSS operation IDs (must match BgfxBackend.cpp encoding) +#define TSS_DISABLE 0.0 +#define TSS_SELECTARG1 1.0 +#define TSS_SELECTARG2 2.0 +#define TSS_MODULATE 3.0 +#define TSS_MODULATE2X 4.0 +#define TSS_ADD 5.0 +#define TSS_ADDSIGNED 6.0 +#define TSS_SUBTRACT 7.0 +#define TSS_BLENDTEXALPHA 8.0 +#define TSS_BLENDCURALPHA 9.0 +#define TSS_ADDSMOOTH 10.0 + +// Arg source IDs +#define SRC_TEXTURE 0.0 +#define SRC_DIFFUSE 1.0 +#define SRC_CURRENT 2.0 + +vec3 applyColorOp(float op, vec3 arg1, vec3 arg2) +{ + // SELECTARG1 + if (op < 1.5) return arg1; + // SELECTARG2 + if (op < 2.5) return arg2; + // MODULATE + if (op < 3.5) return arg1 * arg2; + // MODULATE2X + if (op < 4.5) return arg1 * arg2 * 2.0; + // ADD + if (op < 5.5) return arg1 + arg2; + // ADDSIGNED + if (op < 6.5) return arg1 + arg2 - vec3_splat(0.5); + // SUBTRACT + if (op < 7.5) return arg1 - arg2; + // BLENDTEXALPHA — handled specially by caller + // BLENDCURALPHA — handled specially by caller + // ADDSMOOTH + if (op < 10.5) return arg1 + arg2 - arg1 * arg2; + return arg1; +} + +float applyAlphaOp(float op, float arg1, float arg2) +{ + if (op < 1.5) return arg1; + if (op < 2.5) return arg2; + if (op < 3.5) return arg1 * arg2; + if (op < 4.5) return arg1 * arg2 * 2.0; + if (op < 5.5) return arg1 + arg2; + if (op < 6.5) return arg1 + arg2 - 0.5; + if (op < 7.5) return arg1 - arg2; + return arg1; +} + +void main() +{ + vec4 tex0 = texture2D(s_tex0, v_texcoord0); + vec4 tex1 = texture2D(s_tex1, v_texcoord0); + vec4 tex2 = texture2D(s_tex2, v_texcoord0); + vec4 tex3 = texture2D(s_tex3, v_texcoord0); + vec4 diffuse = v_color0; + + // --- Stage 0: Primary (texture0 vs diffuse) --- + float priColorOp = u_tssOps0.x; + float priAlphaOp = u_tssOps0.y; + float priArg1Src = u_tssOps1.x; + float priAlphaArg1Src = u_tssOps1.y; + + // Determine arg1/arg2 for primary stage + vec4 priArg1 = (priArg1Src < 0.5) ? tex0 : diffuse; + vec4 priArg2 = (priArg1Src < 0.5) ? diffuse : tex0; + vec4 priAlphaA1 = (priAlphaArg1Src < 0.5) ? tex0 : diffuse; + vec4 priAlphaA2 = (priAlphaArg1Src < 0.5) ? diffuse : tex0; + + vec3 priColor; + float priAlpha; + + if (priColorOp < 0.5) + { + // DISABLE — pass through diffuse (no texture) + priColor = diffuse.rgb; + priAlpha = diffuse.a; + } + else + { + priColor = applyColorOp(priColorOp, priArg1.rgb, priArg2.rgb); + priAlpha = applyAlphaOp(priAlphaOp, priAlphaA1.a, priAlphaA2.a); + } + + vec4 current = vec4(priColor, priAlpha); + + // --- Stage 1: Secondary/Detail (texture1 vs primary result) --- + float secColorOp = u_tssOps0.z; + float secAlphaOp = u_tssOps0.w; + + if (secColorOp > 0.5) + { + vec3 secArg1 = tex1.rgb; + vec3 secArg2 = current.rgb; + + // Handle BLENDTEXTUREALPHA and BLENDCURRENTALPHA specially + if (secColorOp > 7.5 && secColorOp < 8.5) + { + // BLENDTEXTUREALPHA: lerp(arg2, arg1, tex1.a) + current.rgb = mix(secArg2, secArg1, tex1.a); + } + else if (secColorOp > 8.5 && secColorOp < 9.5) + { + // BLENDCURRENTALPHA: lerp(arg2, arg1, current.a) + current.rgb = mix(secArg2, secArg1, current.a); + } + else + { + current.rgb = applyColorOp(secColorOp, secArg1, secArg2); + } + } + + if (secAlphaOp > 0.5) + { + float secAArg1 = tex1.a; + float secAArg2 = current.a; + current.a = applyAlphaOp(secAlphaOp, secAArg1, secAArg2); + } + + // --- Stages 2-3: simple multiply (engine rarely uses independent ops here) --- + current *= tex2 * tex3; + + // --- Lighting --- + if (priColorOp > 2.5 && priColorOp < 5.5) + { + vec3 sunDir = normalize(vec3(0.35, 0.55, 0.75)); + float ambient = 0.45; + float nDotL = max(0.0, dot(normalize(v_normal), sunDir)); + float lighting = min(1.0, ambient + nDotL * 0.75); + current.rgb *= lighting; + } + + // --- Material diffuse tint (team colors, opacity) --- + current *= u_matDiffuse; + + // --- Alpha test --- + if (u_atestParams.x > 0.0 && current.a < u_atestParams.x) + { + discard; + } + + gl_FragColor = current; +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_solid_lit.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_solid_lit.sc deleted file mode 100644 index dea52a96bd7..00000000000 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_solid_lit.sc +++ /dev/null @@ -1,20 +0,0 @@ -$input a_position, a_color0 -$output v_color0 - -// TheSuperHackers @refactor bobtista 11/04/2026 lit solid -// vertex shader. Used by the *Solid presets which have GRADIENT_MODULATE -// + TEXTURING_DISABLE - the engine writes the fixed-function lit color -// into the vertex diffuse and we just forward it. Identical to -// vs_passthrough but separated so the program lookup can stay 1:1 with -// the preset bucket. - -#include - -void main() -{ - gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0)); - // D3D8 BGRA → bgfx RGBA swap - vec4 c = a_color0.bgra; - c.a = 1.0; - v_color0 = c; -} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_textured_lit.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_textured_lit.sc deleted file mode 100644 index a9b682f8edb..00000000000 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_textured_lit.sc +++ /dev/null @@ -1,38 +0,0 @@ -$input a_position, a_normal, a_color0, a_texcoord0 -$output v_color0, v_texcoord0, v_normal - -// TheSuperHackers @refactor bobtista 11/04/2026 lit textured -// vertex shader. Transforms position by the bgfx model/view/projection -// matrix, forwards vertex diffuse (carries team colors and prelit -// vertex brightness the engine bakes in), texcoord 0, and the world- -// space normal for the fragment shader to do a simple N.L. -// -// Fallback: if the bound FVF does not carry a diffuse -// attribute (e.g. 0x112 = XYZ|NORMAL|TEX1 used by rigid mesh containers -// for infantry/vehicles), bgfx defaults a_color0 to zero. Multiplying -// tex*0 produces black invisible meshes. Detect the zero case and swap -// in an opaque white so the texture shows through untinted, then the -// fragment shader modulates by u_matDiffuse for the team color. Meshes -// with a real diffuse attribute (terrain 0x242, etc.) are unaffected. - -#include - -void main() -{ - gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0)); - // D3D8 vertex diffuse is D3DCOLOR (BGRA byte order) but bgfx Color0 - // expects RGBA. Swap R and B to correct the channel mapping. - // Check RGB only (not alpha) for the zero-detection: D3D11 defaults - // missing Color0 to (0,0,0,1), so including alpha makes the sum > 0 - // and the white fallback never fires for meshes without a diffuse - // attribute (infantry, vehicles). - vec4 c = a_color0.bgra; - if (c.r + c.g + c.b < 0.001) - { - c = vec4(1.0, 1.0, 1.0, 1.0); - } - c.a = 1.0; - v_color0 = c; - v_texcoord0 = a_texcoord0; - v_normal = mul(u_model[0], vec4(a_normal, 0.0)).xyz; -} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_textured_unlit.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_textured_unlit.sc deleted file mode 100644 index 7973f6ac1bf..00000000000 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_textured_unlit.sc +++ /dev/null @@ -1,15 +0,0 @@ -$input a_position, a_texcoord0 -$output v_texcoord0 - -// TheSuperHackers @refactor bobtista 11/04/2026 unlit textured -// vertex shader. Used by 2D/Sprite presets - no vertex lighting, just -// position transform and texcoord passthrough. -// shader bucket B. - -#include - -void main() -{ - gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0)); - v_texcoord0 = a_texcoord0; -} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_uber.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_uber.sc new file mode 100644 index 00000000000..71b74adda13 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_uber.sc @@ -0,0 +1,24 @@ +$input a_position, a_normal, a_color0, a_texcoord0 +$output v_color0, v_texcoord0, v_normal + +#include + +void main() +{ + gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0)); + + // D3D8 vertex diffuse is D3DCOLOR (BGRA byte order) but bgfx Color0 + // expects RGBA. Swap R and B to correct the channel mapping. + // Check RGB only (not alpha) for the zero-detection: D3D11 defaults + // missing Color0 to (0,0,0,1), so including alpha makes the sum > 0 + // and the white fallback never fires for meshes without a diffuse + // attribute (infantry, vehicles). + vec4 c = a_color0.bgra; + if (c.r + c.g + c.b < 0.001) + { + c = vec4(1.0, 1.0, 1.0, 1.0); + } + v_color0 = c; + v_texcoord0 = a_texcoord0; + v_normal = mul(u_model[0], vec4(a_normal, 0.0)).xyz; +} From 1d45dc42f2575e2a087a348cc322e354374cc5e3 Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 21 Apr 2026 14:02:47 -0400 Subject: [PATCH 049/523] feat(bgfx): real lighting from game light environment (cherry picked from commit fba375bb66d23b27cf254e504c41f955fb70b74d) --- .../Source/WWVegas/WW3D2/BgfxBackend.cpp | 255 ++++++++++++++++-- .../Source/WWVegas/WW3D2/BgfxBackend.h | 3 + .../Source/WWVegas/WW3D2/IRenderBackend.h | 1 + .../Source/WWVegas/WW3D2/dx8renderer.cpp | 2 +- .../Source/WWVegas/WW3D2/shaders/fs_uber.sc | 42 ++- .../Source/WWVegas/WW3D2/sortingrenderer.cpp | 7 +- .../Source/WWVegas/WW3D2/boxrobj.cpp | 10 +- .../Libraries/Source/WWVegas/WW3D2/dazzle.cpp | 26 +- .../Source/WWVegas/WW3D2/decalmsh.cpp | 16 +- .../Source/WWVegas/WW3D2/linegrp.cpp | 12 +- .../Libraries/Source/WWVegas/WW3D2/mesh.cpp | 2 +- .../Libraries/Source/WWVegas/WW3D2/ww3d.cpp | 4 +- 12 files changed, 316 insertions(+), 64 deletions(-) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp index 849951fcc7e..bd17e70e6bb 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -34,6 +34,7 @@ #include "dx8indexbuffer.h" #include "dx8vertexbuffer.h" #include "dx8wrapper.h" +#include "lightenvironment.h" #include "matrix3d.h" #include "matrix4.h" #include "shader.h" @@ -138,6 +139,38 @@ float g_currentTssOps0[4] = { 3.0f, 3.0f, 0.0f, 0.0f }; // default float g_currentTssOps1[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; // default: arg1=TEXTURE for all float g_currentAtestRef = 0.0f; // alpha test reference, 0 = disabled +// Lighting uniforms. The engine supports up to 4 lights +// (typically 1 directional sun + 0-3 point lights). We pack light data +// into vec4 arrays and push them per-draw so the uber fragment shader +// can evaluate real N.L lighting instead of the hardcoded fake sun. +// 4 lights packed into vec4 arrays (one element per light). +// u_lightDirs[i].xyz = direction toward light, .w = enabled flag +// u_lightColors[i].rgb = diffuse color +bgfx::UniformHandle g_uLightDirs = BGFX_INVALID_HANDLE; +bgfx::UniformHandle g_uLightColors = BGFX_INVALID_HANDLE; +bgfx::UniformHandle g_uSceneAmbient = BGFX_INVALID_HANDLE; +bgfx::UniformHandle g_uLightingEnabled = BGFX_INVALID_HANDLE; +// Defaults match the old hardcoded sun: direction TOWARD light (positive), +// white diffuse, reasonable ambient. These are used until the first +// Set_Light_Environment call provides real game lights. +float g_currentLightDirs[4][4] = { + { 0.35f, 0.55f, 0.75f, 1.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f } +}; +float g_currentLightColors[4][4] = { + { 0.75f, 0.75f, 0.75f, 1.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f } +}; +float g_currentSceneAmbient[4] = { 0.45f, 0.45f, 0.45f, 1.0f }; +// Tracks whether the current material has lighting enabled. +// When false, the vertex color contains pre-baked lighting (terrain) +// and the fragment shader should NOT apply N.L lighting on top. +float g_currentLightingEnabled[4] = { 1.0f, 0.0f, 0.0f, 0.0f }; + // TheSuperHackers @refactor bobtista 11/04/2026 default 1x1 // white texture. Real Set_Texture wiring is not in place yet, so the // textured shaders need SOMETHING bound to s_tex0 or D3D11 returns @@ -752,6 +785,14 @@ float g_bgfxWorld[16]; float g_bgfxView[16]; float g_bgfxProj[16]; bool g_bgfxViewProjDirty = true; + +// Snapshot of g_bgfxView and g_bgfxProj captured at the first opaque +// draw of each frame. Re-applied to view 1 at End_Scene to prevent +// later Set_Projection calls (water, shadows, sneak attack) from +// retroactively stomping the camera projection via setViewTransform. +float g_bgfxCameraView[16]; +float g_bgfxCameraProj[16]; +bool g_bgfxCameraCaptured = false; bool g_loggedFirstSetView = false; bool g_loggedFirstSetProj = false; bool g_loggedFirstSetWorld = false; @@ -928,9 +969,11 @@ bgfx::TextureHandle EnsureBgfxTexture(TextureBaseClass * tex) if (tex->Get_Pool() == TextureBaseClass::POOL_DEFAULT) { - // POOL_DEFAULT textures (render targets, dynamic) need a - // different code path. Skip for now. - g_textureCache[tex] = BGFX_INVALID_HANDLE; + // POOL_DEFAULT textures are GPU-only render targets (water + // reflections). Skip for now — future work will add proper + // render-to-texture via bgfx framebuffers. Don't cache the + // failure so we re-check each frame (the texture might not + // be initialized on the first check). return BGFX_INVALID_HANDLE; } @@ -1248,8 +1291,13 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) g_uAtestParams = bgfx::createUniform("u_atestParams", bgfx::UniformType::Vec4); g_uTssOps0 = bgfx::createUniform("u_tssOps0", bgfx::UniformType::Vec4); g_uTssOps1 = bgfx::createUniform("u_tssOps1", bgfx::UniformType::Vec4); + g_uLightDirs = bgfx::createUniform("u_lightDirs", bgfx::UniformType::Vec4, 4); + g_uLightColors = bgfx::createUniform("u_lightColors", bgfx::UniformType::Vec4, 4); + g_uSceneAmbient = bgfx::createUniform("u_sceneAmbient", bgfx::UniformType::Vec4); + g_uLightingEnabled = bgfx::createUniform("u_lightingEnabled", bgfx::UniformType::Vec4); - // default 1x1 white texture (opaque ABGR=0xffffffff). + // Default 1x1 white texture. Used as fallback for missing textures. + // Multiplying by white is the identity operation. static const uint8_t kWhitePixel[4] = { 0xff, 0xff, 0xff, 0xff }; g_defaultWhiteTexture = bgfx::createTexture2D( 1, 1, false, 1, @@ -1337,6 +1385,26 @@ void BgfxBackend::Shutdown() bgfx::destroy(g_uTssOps1); g_uTssOps1 = BGFX_INVALID_HANDLE; } + if (bgfx::isValid(g_uLightDirs)) + { + bgfx::destroy(g_uLightDirs); + g_uLightDirs = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(g_uLightColors)) + { + bgfx::destroy(g_uLightColors); + g_uLightColors = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(g_uSceneAmbient)) + { + bgfx::destroy(g_uSceneAmbient); + g_uSceneAmbient = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(g_uLightingEnabled)) + { + bgfx::destroy(g_uLightingEnabled); + g_uLightingEnabled = BGFX_INVALID_HANDLE; + } if (bgfx::isValid(g_defaultWhiteTexture)) { bgfx::destroy(g_defaultWhiteTexture); @@ -1413,12 +1481,18 @@ void BgfxBackend::End_Scene(bool /*flip_frame*/) { return; } - // Re-apply the camera projection to the sort view. The engine calls + // Re-apply captured camera transforms to both views. The engine calls // Set_Projection_Transform_With_Z_Bias multiple times per frame - // (camera, water reflections, shadows). The last call often uses a - // tiny near-field frustum that clips all sort geometry. Since bgfx's - // setViewTransform is retroactive for the whole frame, we re-apply - // the projection that was active at sort-flush time. + // (camera, water reflections, shadows, sneak attack). Since bgfx's + // setViewTransform is retroactive for the whole frame, the last call + // would stomp earlier draws. We re-apply the camera projection that + // was active at the first opaque draw (view 1) and at sort-flush + // time (view 2). + if (g_bgfxCameraCaptured) + { + bgfx::setViewTransform(kBgfxEngineView, g_bgfxCameraView, g_bgfxCameraProj); + g_bgfxCameraCaptured = false; + } if (g_bgfxSortProjCaptured) { float identityView[16]; @@ -1834,6 +1908,26 @@ void BgfxBackend::Capture_Sorted_Batch_Transforms(const Matrix4x4 & sortWorld, } } +void BgfxBackend::Capture_Sorted_Batch_Light(const D3DLIGHT8 & light, bool enabled) +{ + // Sort batch lights are always light 0 (the primary directional). + // Scene ambient was already captured by Set_Light_Environment. + if (enabled) + { + g_currentLightDirs[0][0] = light.Direction.x; + g_currentLightDirs[0][1] = light.Direction.y; + g_currentLightDirs[0][2] = light.Direction.z; + g_currentLightDirs[0][3] = 1.0f; + g_currentLightColors[0][0] = light.Diffuse.r; + g_currentLightColors[0][1] = light.Diffuse.g; + g_currentLightColors[0][2] = light.Diffuse.b; + } + else + { + g_currentLightDirs[0][3] = 0.0f; + } +} + // TheSuperHackers @refactor bobtista 11/04/2026 sorted VB // direct-draw submit. Called from DX8Wrapper::Draw_Sorting_IB_VB after // it populates an internal dynamic VB and dynamic IB by copying a slice @@ -2126,6 +2220,10 @@ void BgfxBackend::Set_Material(const VertexMaterialClass * material) g_currentMatDiffuse[1] = diffuse.Y; g_currentMatDiffuse[2] = diffuse.Z; g_currentMatDiffuse[3] = material->Get_Opacity(); + // Track whether this material uses hardware lighting. When false + // (terrain, pre-lit meshes), vertex colors already contain the + // lit result and the shader must NOT apply N.L on top. + g_currentLightingEnabled[0] = material->Get_Lighting() ? 1.0f : 0.0f; } else { @@ -2159,6 +2257,49 @@ void BgfxBackend::Set_Texture(unsigned int stage, TextureBaseClass * texture) } } +void BgfxBackend::Set_Ambient(const Vector3 & color) +{ + DX8Backend::Set_Ambient(color); + g_currentSceneAmbient[0] = color.X; + g_currentSceneAmbient[1] = color.Y; + g_currentSceneAmbient[2] = color.Z; +} + +void BgfxBackend::Set_Light_Environment(LightEnvironmentClass * light_env) +{ + DX8Backend::Set_Light_Environment(light_env); + + if (light_env != nullptr) + { + const Vector3 & ambient = light_env->Get_Equivalent_Ambient(); + g_currentSceneAmbient[0] = ambient.X; + g_currentSceneAmbient[1] = ambient.Y; + g_currentSceneAmbient[2] = ambient.Z; + + const int count = light_env->Get_Light_Count(); + for (int i = 0; i < 4; ++i) + { + if (i < count) + { + const Vector3 & dir = light_env->Get_Light_Direction(i); + const Vector3 & dif = light_env->Get_Light_Diffuse(i); + g_currentLightDirs[i][0] = -dir.X; + g_currentLightDirs[i][1] = -dir.Y; + g_currentLightDirs[i][2] = -dir.Z; + g_currentLightDirs[i][3] = 1.0f; // enabled + g_currentLightColors[i][0] = dif.X; + g_currentLightColors[i][1] = dif.Y; + g_currentLightColors[i][2] = dif.Z; + g_currentLightColors[i][3] = 1.0f; + } + else + { + g_currentLightDirs[i][3] = 0.0f; // disabled + } + } + } +} + // Get_Shader, Apply_Render_State_Changes, Apply_Default_State, // Invalidate_Cached_Render_States, Set_Blend_Op, Set_Blend_Factors, // Set_Color_Write_Enable, Set_Alpha_Blend_Enable, hardware cursor, and @@ -2358,6 +2499,16 @@ void SubmitEngineDraw(unsigned short start_index, // draws never touch g_bgfxView so the opaque view is never stomped. if (!g_inSortFlush && g_bgfxViewProjDirty) { + // Capture the camera view+proj at the first opaque draw of each + // frame. Later Set_Projection calls (water, shadows, sneak attack) + // may overwrite g_bgfxProj with a different frustum. We re-apply + // the camera projection to view 1 at End_Scene time. + if (!g_bgfxCameraCaptured) + { + std::memcpy(g_bgfxCameraView, g_bgfxView, sizeof(g_bgfxCameraView)); + std::memcpy(g_bgfxCameraProj, g_bgfxProj, sizeof(g_bgfxCameraProj)); + g_bgfxCameraCaptured = true; + } bgfx::setViewTransform(kBgfxEngineView, g_bgfxView, g_bgfxProj); g_bgfxViewProjDirty = false; } @@ -2508,21 +2659,91 @@ void SubmitEngineDraw(unsigned short start_index, float atestParams[4] = { g_currentAtestRef, 0.0f, 0.0f, 0.0f }; bgfx::setUniform(g_uAtestParams, atestParams); } + // Read the CURRENT D3D light state directly from DX8Wrapper's + // render_state. This captures lights regardless of how they were set + // (Set_Light_Environment, Set_DX8_Light, or game-level light setup). + { + RenderStateStruct rs; + DX8Wrapper::Get_Render_State(rs); + for (int li = 0; li < 4; ++li) + { + if (rs.LightEnable[li]) + { + const D3DLIGHT8 & dl = rs.Lights[li]; + // D3D8 Direction points FROM light toward surface. + // D3D internally negates for N.L. We must negate too. + g_currentLightDirs[li][0] = -dl.Direction.x; + g_currentLightDirs[li][1] = -dl.Direction.y; + g_currentLightDirs[li][2] = -dl.Direction.z; + g_currentLightDirs[li][3] = 1.0f; + g_currentLightColors[li][0] = dl.Diffuse.r; + g_currentLightColors[li][1] = dl.Diffuse.g; + g_currentLightColors[li][2] = dl.Diffuse.b; + g_currentLightColors[li][3] = 1.0f; + } + else + { + g_currentLightDirs[li][3] = 0.0f; + } + } + // Read scene ambient from D3DRS_AMBIENT (packed D3DCOLOR = ARGB) + const unsigned ambientColor = DX8Wrapper::Get_DX8_Render_State(D3DRS_AMBIENT); + static bool s_loggedLightRead = false; + if (!s_loggedLightRead && rs.LightEnable[0]) + { + s_loggedLightRead = true; + WWDEBUG_SAY(("[BgfxBackend] LIGHT READ: amb=0x%08x lights=%d%d%d%d " + "dir0=%.2f,%.2f,%.2f dif0=%.2f,%.2f,%.2f", + ambientColor, + int(rs.LightEnable[0]), int(rs.LightEnable[1]), + int(rs.LightEnable[2]), int(rs.LightEnable[3]), + rs.Lights[0].Direction.x, rs.Lights[0].Direction.y, + rs.Lights[0].Direction.z, + rs.Lights[0].Diffuse.r, rs.Lights[0].Diffuse.g, + rs.Lights[0].Diffuse.b)); + } + g_currentSceneAmbient[0] = ((ambientColor >> 16) & 0xFF) / 255.0f; + g_currentSceneAmbient[1] = ((ambientColor >> 8) & 0xFF) / 255.0f; + g_currentSceneAmbient[2] = ((ambientColor >> 0) & 0xFF) / 255.0f; + } + if (bgfx::isValid(g_uLightDirs)) + bgfx::setUniform(g_uLightDirs, g_currentLightDirs, 4); + if (bgfx::isValid(g_uLightColors)) + bgfx::setUniform(g_uLightColors, g_currentLightColors, 4); + if (bgfx::isValid(g_uSceneAmbient)) + bgfx::setUniform(g_uSceneAmbient, g_currentSceneAmbient); + if (bgfx::isValid(g_uLightingEnabled)) + bgfx::setUniform(g_uLightingEnabled, g_currentLightingEnabled); // Use the cached state if it was built; otherwise fall back to a - // sane default that at least lets the draw run. + // sane default that at least lets the draw run. Enable MSAA on all + // draws so the 4x MSAA framebuffer actually smooths edges. uint64_t state = (g_currentBgfxState != 0) ? g_currentBgfxState : (BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); - - // Disable depth test for sort view draws. Sort - // particles are pre-transformed to view space with identity - // world/view, so their Z values don't match the opaque view's - // depth buffer (which used the real camera view transform). + state |= BGFX_STATE_MSAA; + + // Sort view depth handling: particles are pre-transformed to view + // space with identity world/view, so their Z values don't match the + // opaque depth buffer — they need DEPTH_TEST_ALWAYS. But mesh-based + // sorted draws (water, rotors) have real world transforms and should + // use normal depth testing to avoid drawing on top of opaque geometry. + // Distinguish by checking if the sort world matrix is identity + // (particles) vs non-identity (meshes). if (submitView == kBgfxEngineSortView) { - state &= ~BGFX_STATE_DEPTH_TEST_MASK; - state |= BGFX_STATE_DEPTH_TEST_ALWAYS; + const bool isIdentityWorld = + g_bgfxSortWorld[0] == 1.0f && g_bgfxSortWorld[5] == 1.0f && + g_bgfxSortWorld[10] == 1.0f && g_bgfxSortWorld[15] == 1.0f && + g_bgfxSortWorld[12] == 0.0f && g_bgfxSortWorld[13] == 0.0f && + g_bgfxSortWorld[14] == 0.0f; + if (isIdentityWorld) + { + // Particles: view-space positions, skip depth test + state &= ~BGFX_STATE_DEPTH_TEST_MASK; + state |= BGFX_STATE_DEPTH_TEST_ALWAYS; + } + // else: mesh-based sorted draw, use the shader's depth compare } bgfx::setState(state); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h index c6ef15ddf86..18cd8ddf4c8 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h @@ -100,6 +100,7 @@ class BgfxBackend : public DX8Backend virtual void End_Sorted_Batch_Pass(); virtual void Capture_Sorted_Batch_Transforms(const Matrix4x4 & world, const Matrix4x4 & view); + virtual void Capture_Sorted_Batch_Light(const D3DLIGHT8 & light, bool enabled); virtual void Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, const DynamicIBAccessClass & dyn_ib, unsigned short polygon_count, @@ -114,6 +115,8 @@ class BgfxBackend : public DX8Backend virtual void Set_Shader(const ShaderClass & shader); virtual void Set_Material(const VertexMaterialClass * material); virtual void Set_Texture(unsigned int stage, TextureBaseClass * texture); + virtual void Set_Light_Environment(LightEnvironmentClass * light_env); + virtual void Set_Ambient(const Vector3 & color); // -- Transforms ----------------------------------------------------------- // diff --git a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h index ec759263f4a..3afdd15daba 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h @@ -278,6 +278,7 @@ class IRenderBackend virtual void End_Sorted_Batch_Pass() {} virtual void Capture_Sorted_Batch_Transforms(const Matrix4x4 & /*world*/, const Matrix4x4 & /*view*/) {} + virtual void Capture_Sorted_Batch_Light(const D3DLIGHT8 & /*light*/, bool /*enabled*/) {} // TheSuperHackers @refactor bobtista 11/04/2026 sorted // direct-draw path hook. DX8Wrapper::Draw_Sorting_IB_VB handles diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp index 973356ff24a..4c859c4e0be 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp @@ -1819,7 +1819,7 @@ void DX8TextureCategoryClass::Render() LightEnvironmentClass * lenv = mesh->Get_Lighting_Environment(); if (lenv != nullptr) { SNAPSHOT_SAY(("LightEnvironment, lights: %d",lenv->Get_Light_Count())); - DX8Wrapper::Set_Light_Environment(lenv); + g_renderBackend->Set_Light_Environment(lenv); } else { SNAPSHOT_SAY(("No light environment")); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc index e4c075a15f5..9c1c93bda2a 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc @@ -10,6 +10,10 @@ uniform vec4 u_matDiffuse; uniform vec4 u_atestParams; uniform vec4 u_tssOps0; // (priColorOp, priAlphaOp, secColorOp, secAlphaOp) uniform vec4 u_tssOps1; // (priColorArg1Src, priAlphaArg1Src, secColorArg1Src, secAlphaArg1Src) +uniform vec4 u_lightDirs[4]; // per-light direction (xyz=toward light, w=enabled) +uniform vec4 u_lightColors[4]; // per-light diffuse color (rgb) +uniform vec4 u_sceneAmbient; // scene ambient color (rgb) +uniform vec4 u_lightingEnabled; // .x > 0.5 = apply N.L lighting; else vertex is pre-lit // TSS operation IDs (must match BgfxBackend.cpp encoding) #define TSS_DISABLE 0.0 @@ -138,17 +142,37 @@ void main() current *= tex2 * tex3; // --- Lighting --- - if (priColorOp > 2.5 && priColorOp < 5.5) + if (priColorOp > 2.5 && priColorOp < 5.5 && u_lightingEnabled.x > 0.5) { - vec3 sunDir = normalize(vec3(0.35, 0.55, 0.75)); - float ambient = 0.45; - float nDotL = max(0.0, dot(normalize(v_normal), sunDir)); - float lighting = min(1.0, ambient + nDotL * 0.75); - current.rgb *= lighting; + // Material has lighting enabled. D3D's T&L pipeline REPLACES the + // vertex color with the computed lit result. Our vertex shader + // passes through the raw vertex attribute which is meaningless + // for lit meshes. Undo the TSS modulate with vertex color by + // dividing it out, then apply proper lighting. + // Simpler approach: recompute current as tex-only * lighting. + vec4 texOnly = tex0; + if (secColorOp > 0.5) texOnly *= tex1; + texOnly *= tex2 * tex3; + + vec3 nrm = normalize(v_normal); + vec3 litColor = u_sceneAmbient.rgb * u_matDiffuse.rgb; + for (int li = 0; li < 4; ++li) + { + if (u_lightDirs[li].w > 0.5) + { + vec3 ldir = normalize(u_lightDirs[li].xyz); + float nDotL = max(0.0, dot(nrm, ldir)); + litColor += u_lightColors[li].rgb * u_matDiffuse.rgb * nDotL; + } + } + current = vec4(texOnly.rgb * min(vec3_splat(1.0), litColor), + texOnly.a * u_matDiffuse.a); + } + else + { + // Pre-lit or unlit: vertex color contains baked lighting. + current *= u_matDiffuse; } - - // --- Material diffuse tint (team colors, opacity) --- - current *= u_matDiffuse; // --- Alpha test --- if (u_atestParams.x > 0.0 && current.a < u_atestParams.x) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/sortingrenderer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/sortingrenderer.cpp index b6bbec59be8..6a39411a0c6 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/sortingrenderer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/sortingrenderer.cpp @@ -401,6 +401,9 @@ static void Apply_Render_State(RenderStateStruct& render_state) return; //no point changing lights if they are ignored. //prevLight = render_state.lightsHash; + g_renderBackend->Capture_Sorted_Batch_Light( + render_state.Lights[0], render_state.LightEnable[0]); + if (render_state.LightEnable[0]) { DX8Wrapper::Set_DX8_Light(0,&render_state.Lights[0]); if (render_state.LightEnable[1]) { @@ -696,8 +699,8 @@ void SortingRendererClass::Flush() DynamicVBAccessClass::_Reset(false); - DX8Wrapper::Set_Transform(D3DTS_VIEW,old_view); - DX8Wrapper::Set_Transform(D3DTS_WORLD,old_world); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW,old_view); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,old_world); } diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/boxrobj.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/boxrobj.cpp index b4dacf2c4e3..2039f6542e4 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/boxrobj.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/boxrobj.cpp @@ -506,9 +506,9 @@ void BoxRenderObjClass::render_box(RenderInfoClass & rinfo,const Vector3 & cente /* ** Apply the shader and material */ - DX8Wrapper::Set_Material(_BoxMaterial); - DX8Wrapper::Set_Shader(_BoxShader); - DX8Wrapper::Set_Texture(0,nullptr); + g_renderBackend->Set_Material(_BoxMaterial); + g_renderBackend->Set_Shader(_BoxShader); + g_renderBackend->Set_Texture(0,nullptr); g_renderBackend->Set_Index_Buffer(ibaccess, 0); g_renderBackend->Set_Vertex_Buffer(vbaccess); @@ -703,7 +703,7 @@ void AABoxRenderObjClass::Render(RenderInfoClass & rinfo) { Matrix3D temp(1); temp.Translate(Transform.Get_Translation()); - DX8Wrapper::Set_Transform(D3DTS_WORLD,temp); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,temp); render_box(rinfo,ObjSpaceCenter,ObjSpaceExtent); } @@ -1087,7 +1087,7 @@ int OBBoxRenderObjClass::Class_ID() const *=============================================================================================*/ void OBBoxRenderObjClass::Render(RenderInfoClass & rinfo) { - DX8Wrapper::Set_Transform(D3DTS_WORLD,Transform); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,Transform); render_box(rinfo,ObjSpaceCenter,ObjSpaceExtent); } diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp index d35816cc7be..ff0d471f2d3 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp @@ -1221,34 +1221,34 @@ void DazzleRenderObjClass::Render_Dazzle(CameraClass* camera) } } - DX8Wrapper::Set_World_Identity(); - DX8Wrapper::Set_View_Identity(); - DX8Wrapper::Set_Transform(D3DTS_PROJECTION,Matrix4x4(true)); + g_renderBackend->Set_World_Identity(); + g_renderBackend->Set_View_Identity(); + g_renderBackend->Set_Transform(RB_TRANSFORM_PROJECTION,Matrix4x4(true)); if (halo_poly_count) { g_renderBackend->Set_Index_Buffer(ib_access, dazzle_vertex_count); - DX8Wrapper::Set_Shader(default_halo_shader); - DX8Wrapper::Set_Texture(0,types[type]->Get_Halo_Texture()); + g_renderBackend->Set_Shader(default_halo_shader); + g_renderBackend->Set_Texture(0,types[type]->Get_Halo_Texture()); g_renderBackend->Draw_Triangles(0, halo_poly_count, 0, vertex_count); } if (dazzle_poly_count) { g_renderBackend->Set_Index_Buffer(ib_access, 0); - DX8Wrapper::Set_Shader(default_dazzle_shader); - DX8Wrapper::Set_Texture(0,types[type]->Get_Dazzle_Texture()); + g_renderBackend->Set_Shader(default_dazzle_shader); + g_renderBackend->Set_Texture(0,types[type]->Get_Dazzle_Texture()); g_renderBackend->Draw_Triangles(0, dazzle_poly_count, 0, vertex_count); } if (lensflare_poly_count) { g_renderBackend->Set_Index_Buffer(ib_access, dazzle_vertex_count+halo_vertex_count); - DX8Wrapper::Set_Shader(default_dazzle_shader); - DX8Wrapper::Set_Texture(0,lensflare->Get_Texture()); + g_renderBackend->Set_Shader(default_dazzle_shader); + g_renderBackend->Set_Texture(0,lensflare->Get_Texture()); g_renderBackend->Draw_Triangles(0, lensflare_poly_count, 0, vertex_count); } - DX8Wrapper::Set_Transform(D3DTS_PROJECTION,old_projection_transform); - DX8Wrapper::Set_Transform(D3DTS_VIEW,old_view_transform); - DX8Wrapper::Set_Transform(D3DTS_WORLD,old_world_transform); + g_renderBackend->Set_Transform(RB_TRANSFORM_PROJECTION,old_projection_transform); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW,old_view_transform); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,old_world_transform); } // ---------------------------------------------------------------------------- @@ -1578,7 +1578,7 @@ void DazzleLayerClass::Render(CameraClass* camera) camera->Apply(); - DX8Wrapper::Set_Material(nullptr); + g_renderBackend->Set_Material(nullptr); for (unsigned type=0;typeGet_Transform()); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,Parent->Get_Transform()); /* ** Copy the vertices into the dynamic vb @@ -385,9 +385,9 @@ void RigidDecalMeshClass::Render() *=============================================================================================*/ int RigidDecalMeshClass::Process_Material_Run(int start_index) { - DX8Wrapper::Set_Texture(0,Textures[start_index]); - DX8Wrapper::Set_Material(VertexMaterials[Polys[start_index].I]); - DX8Wrapper::Set_Shader(Shaders[start_index]); + g_renderBackend->Set_Texture(0,Textures[start_index]); + g_renderBackend->Set_Material(VertexMaterials[Polys[start_index].I]); + g_renderBackend->Set_Shader(Shaders[start_index]); int next_index = start_index; while ( (next_index < Polys.Count()) && @@ -790,7 +790,7 @@ void SkinDecalMeshClass::Render() /* ** Skin decals coordinates are in world space */ - DX8Wrapper::Set_Transform(D3DTS_WORLD,Matrix3D::Identity); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,Matrix3D::Identity); /* ** Skin decals have to get the deformed vertices of their parent meshes. For this @@ -883,9 +883,9 @@ void SkinDecalMeshClass::Render() *=============================================================================================*/ int SkinDecalMeshClass::Process_Material_Run(int start_index) { - DX8Wrapper::Set_Texture(0,Textures[start_index]); - DX8Wrapper::Set_Material(VertexMaterials[Polys[start_index].I]); - DX8Wrapper::Set_Shader(Shaders[start_index]); + g_renderBackend->Set_Texture(0,Textures[start_index]); + g_renderBackend->Set_Material(VertexMaterials[Polys[start_index].I]); + g_renderBackend->Set_Shader(Shaders[start_index]); int next_index = start_index; while ( (next_index < Polys.Count()) && diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/linegrp.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/linegrp.cpp index a5cbec2ea21..3d856553753 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/linegrp.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/linegrp.cpp @@ -259,9 +259,9 @@ void LineGroupClass::Render(RenderInfoClass &rinfo) } VertexMaterialClass * linemat = VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(linemat); - DX8Wrapper::Set_Shader(Shader); - DX8Wrapper::Set_Texture(0, Texture); + g_renderBackend->Set_Material(linemat); + g_renderBackend->Set_Shader(Shader); + g_renderBackend->Set_Texture(0, Texture); REF_PTR_RELEASE(linemat); WWASSERT(StartLineLoc && StartLineLoc->Get_Array()); @@ -286,7 +286,7 @@ void LineGroupClass::Render(RenderInfoClass &rinfo) DX8Wrapper::Get_Transform(D3DTS_VIEW, view); Matrix4x4 identity(true); - DX8Wrapper::Set_Transform(D3DTS_WORLD, identity); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD, identity); // if the points are in world space, transform the offsets if (Get_Flag(TRANSFORM)) { @@ -298,7 +298,7 @@ void LineGroupClass::Render(RenderInfoClass &rinfo) Matrix3D::Transform_Vector(xform_mat, offset[i], &offset[i]); } } else { - DX8Wrapper::Set_Transform(D3DTS_VIEW, identity); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW, identity); } int num_tris=0; @@ -477,7 +477,7 @@ void LineGroupClass::Render(RenderInfoClass &rinfo) } // restore the matrices - DX8Wrapper::Set_Transform(D3DTS_VIEW, view); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW, view); } int LineGroupClass::Get_Polygon_Count() diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/mesh.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/mesh.cpp index 30627fb2961..43c8fa00986 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/mesh.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/mesh.cpp @@ -829,7 +829,7 @@ void MeshClass::Render_Material_Pass(MaterialPassClass * pass,IndexBufferClass * Vector3 oldEmissive(-1,-1,-1); if (LightEnvironment != nullptr) { - DX8Wrapper::Set_Light_Environment(LightEnvironment); + g_renderBackend->Set_Light_Environment(LightEnvironment); } if (Model->Get_Flag(MeshModelClass::SKIN)) { diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp index c4c41701ffb..5a0d96a29de 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp @@ -988,7 +988,7 @@ WW3DErrorType WW3D::Render(SceneClass * scene,CameraClass * cam,bool clear,bool // Set the global ambient light value here. If the scene is using the LightEnvironment system // this setting will get overridden. - DX8Wrapper::Set_Ambient(scene->Get_Ambient_Light()); + g_renderBackend->Set_Ambient(scene->Get_Ambient_Light()); // render the scene @@ -1040,7 +1040,7 @@ WW3DErrorType WW3D::Render( // Install the lighting environment if one is supplied if (rinfo.light_environment != nullptr) { - DX8Wrapper::Set_Light_Environment(rinfo.light_environment); + g_renderBackend->Set_Light_Environment(rinfo.light_environment); } // Render the object From 2ef46633edd5e391ef4ba12c14c3b66b86fb2b8d Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 21 Apr 2026 14:02:47 -0400 Subject: [PATCH 050/523] feat(bgfx): render-to-texture, water rendering, terrain blend pass, shoreline alpha (cherry picked from commit b693c9b8274f278a123f96998a211c04031c6000) --- .../W3DDevice/GameClient/BaseHeightMap.cpp | 12 +- .../Source/W3DDevice/GameClient/HeightMap.cpp | 2 +- .../W3DDevice/GameClient/Water/W3DWater.cpp | 49 +- .../Source/WWVegas/WW3D2/BgfxBackend.cpp | 873 +++++++++++------- .../Source/WWVegas/WW3D2/BgfxBackend.h | 13 +- .../Source/WWVegas/WW3D2/DX8Backend.cpp | 3 +- .../Source/WWVegas/WW3D2/IRenderBackend.h | 16 + .../Source/WWVegas/WW3D2/dx8renderer.cpp | 2 + .../Source/WWVegas/WW3D2/dx8wrapper.h | 1 + .../Source/WWVegas/WW3D2/shaders/fs_uber.sc | 47 +- .../WWVegas/WW3D2/shaders/varying.def.sc | 2 + .../Source/WWVegas/WW3D2/shaders/vs_uber.sc | 11 +- .../Source/WWVegas/WW3D2/texproject.cpp | 3 +- .../W3DDevice/GameClient/W3DCustomEdging.cpp | 24 +- .../Libraries/Source/WWVegas/WW3D2/dazzle.cpp | 7 + 15 files changed, 660 insertions(+), 405 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp index fa99aa16a09..21214d95781 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp @@ -2487,7 +2487,7 @@ void BaseHeightMapRenderObjClass::renderShoreLines(CameraClass *pCamera) g_renderBackend->Set_Texture(0,m_destAlphaTexture); g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,Matrix3D(true)); //Enabled writes to destination alpha only - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_ALPHA); + g_renderBackend->Set_Color_Write_Enable(false, false, false, true); DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_TEXCOORDINDEX, 0); @@ -2502,7 +2502,7 @@ void BaseHeightMapRenderObjClass::renderShoreLines(CameraClass *pCamera) DynamicIBAccessClass::WriteLockClass lockib(&ib_access); UnsignedShort *ib=lockib.Get_Index_Array(); if (!ib || !vb) - { DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_BLUE|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_RED); + { g_renderBackend->Set_Color_Write_Enable(true, true, true, false); return; } @@ -2610,7 +2610,7 @@ void BaseHeightMapRenderObjClass::renderShoreLines(CameraClass *pCamera) } //Disable writes to destination alpha - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_BLUE|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_RED); + g_renderBackend->Set_Color_Write_Enable(true, true, true, false); ShaderClass::Invalidate(); } @@ -2671,7 +2671,7 @@ void BaseHeightMapRenderObjClass::renderShoreLinesSorted(CameraClass *pCamera) g_renderBackend->Set_Texture(0,m_destAlphaTexture); g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,Matrix3D(true)); //Enabled writes to destination alpha only - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_ALPHA); + g_renderBackend->Set_Color_Write_Enable(false, false, false, true); DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_TEXCOORDINDEX, 0); Bool isDone=FALSE; @@ -2688,7 +2688,7 @@ void BaseHeightMapRenderObjClass::renderShoreLinesSorted(CameraClass *pCamera) DynamicIBAccessClass::WriteLockClass lockib(&ib_access); UnsignedShort *ib=lockib.Get_Index_Array(); if (!ib || !vb) - { DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_BLUE|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_RED); + { g_renderBackend->Set_Color_Write_Enable(true, true, true, false); return; } @@ -2950,7 +2950,7 @@ void BaseHeightMapRenderObjClass::renderShoreLinesSorted(CameraClass *pCamera) } //Disable writes to destination alpha - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_BLUE|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_RED); + g_renderBackend->Set_Color_Write_Enable(true, true, true, false); ShaderClass::Invalidate(); } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp index 7c814657da9..5c3f56078a6 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp @@ -2013,7 +2013,7 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) W3DShaderManager::setTexture(3,m_stageThreeTexture);//noise //Disable writes to destination alpha channel (if there is one) if (DX8Wrapper::getBackBufferFormat() == WW3D_FORMAT_A8R8G8B8) - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_BLUE|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_RED); + g_renderBackend->Set_Color_Write_Enable(true, true, true, false); } Int pass; diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp index 382afb94909..e3f594a0e7d 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp @@ -2908,6 +2908,14 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) g_renderBackend->Set_Texture(0,m_riverTexture); //set to blue setupJbaWaterShader(); + { + ShaderClass waterShader = ShaderClass::_PresetAlphaShader; + waterShader.Set_Cull_Mode(ShaderClass::CULL_MODE_DISABLE); + waterShader.Set_Depth_Mask(ShaderClass::DEPTH_WRITE_DISABLE); + g_renderBackend->Set_Shader(waterShader); + } + g_renderBackend->Override_Alpha_Blend_Enable(true); + g_renderBackend->Override_Material_Opacity(0.5f); //In additive blending we need to use the alpha at the edges of river to darken //rgb instead. @@ -2942,7 +2950,6 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) void WaterRenderObjClass::setupFlatWaterShader() { - g_renderBackend->Set_Texture(0,m_riverTexture); if (!TheWaterTransparency->m_additiveBlend) g_renderBackend->Set_Shader(ShaderClass::_PresetAlphaShader); @@ -3268,7 +3275,24 @@ void WaterRenderObjClass::drawTrapezoidWater(Vector3 points[4]) g_renderBackend->Set_Index_Buffer(ib_access,0); g_renderBackend->Set_Vertex_Buffer(vb_access); - setupFlatWaterShader();// lorenzen sez use the alpha shader + // setupFlatWaterShader sets texture, material, and calls + // Set_Shader(_PresetAlphaShader) which enables CULL_CW in bgfx. + // The water code later sets D3DCULL_NONE via direct D3D8 call but + // bgfx can't see that. Re-apply shader with cull disabled so the + // flat water surface is visible regardless of triangle winding. + setupFlatWaterShader(); + { + ShaderClass waterShader = ShaderClass::_PresetAlphaShader; + waterShader.Set_Cull_Mode(ShaderClass::CULL_MODE_DISABLE); + waterShader.Set_Depth_Mask(ShaderClass::DEPTH_WRITE_DISABLE); + g_renderBackend->Set_Shader(waterShader); + } + g_renderBackend->Override_Alpha_Blend_Enable(true); + // The DX8 path uses DESTALPHA blending from the shoreline pass to + // control water opacity. bgfx can't replicate that. Force 50% + // material opacity so water renders semi-transparent instead. + // Only affects the bgfx uniform — DX8 vertex data is unchanged. + g_renderBackend->Override_Material_Opacity(0.5f); //If video card supports it and it's enabled, feather the water edge using destination alpha if (DX8Wrapper::getBackBufferFormat() == WW3D_FORMAT_A8R8G8B8 && TheGlobalData->m_showSoftWaterEdge && TheWaterTransparency->m_transparentWaterDepth !=0) @@ -3284,27 +3308,8 @@ void WaterRenderObjClass::drawTrapezoidWater(Vector3 points[4]) DX8Wrapper::_Get_D3D_Device8()->GetRenderState(D3DRS_CULLMODE, &cull); DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); - - -//#ifdef FEATHER_WATER // the NEW WATER a'la LORENZEN - -// int layer = 0;//LORENZEN -// for (layer = 0; layer < FEATHER_LAYER_COUNT; ++layer)//LORENZEN -//#endif // FEATHER_WATER { -//#ifdef WAVY_WATER // the NEW WATER a'la LORENZEN - - //increment the depth of the water's surface for every vert in the buffer -//#ifdef FEATHER_WATER -// VertexFormatXYZNDUV2 *vertBuf = vertexBufferStart; -// while (vertBuf < vertexBufferStart + vCount * uCount) -// { -// vertBuf->z *= FEATHER_LAYER_THICKNESS; -// ++vertBuf; -// } -//#endif // FEATHER_WATER -//#endif //WAVY_WATER - g_renderBackend->Draw_Triangles( 0,rectangleCount*2, 0, (rectangleCount+1)*2);//lorenzen thinks this is where to itereate the soft shoreline effect + g_renderBackend->Draw_Triangles( 0,rectangleCount*2, 0, (rectangleCount+1)*2); } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp index bd17e70e6bb..a1ba10b9a43 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -39,6 +39,7 @@ #include "matrix4.h" #include "shader.h" #include "texture.h" +#include "texturefilter.h" #include "vector3.h" #include "ww3dformat.h" #include "wwdebug.h" @@ -101,8 +102,8 @@ const wchar_t * const kBgfxWindowClass = L"GGC_BgfxDebugWindow"; // dimensions chosen to match the game's 1.6 aspect ratio (1280x800) // so engine geometry rendered with the game's projection matrix // does not look horizontally squished. -const int kBgfxWindowWidth = 800; -const int kBgfxWindowHeight = 500; +const int kBgfxWindowWidth = 1024; +const int kBgfxWindowHeight = 768; // TheSuperHackers @refactor bobtista 11/04/2026 program handle // for the passthrough shader pair. Created once in Initialize after bgfx::init @@ -139,6 +140,24 @@ float g_currentTssOps0[4] = { 3.0f, 3.0f, 0.0f, 0.0f }; // default float g_currentTssOps1[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; // default: arg1=TEXTURE for all float g_currentAtestRef = 0.0f; // alpha test reference, 0 = disabled +// Post-ShaderClass blend/alpha-test overrides. Set by Override_Blend / +// Override_Alpha_Test, cleared by Clear_State_Overrides (called from Set_Shader). +bool g_blendOverrideActive = false; +uint64_t g_blendOverrideBits = 0; +bool g_atestOverrideActive = false; +float g_atestOverrideRef = 0.0f; +bool g_suppressBgfxDraw = false; +int g_colorWriteOverride = -1; +bool g_effectOverlayActive = false; + +// UV set selection: when > 0, the fragment shader samples stage 0 +// from v_texcoord1 instead of v_texcoord0. Set by the terrain shader +// when it changes D3DTSS_TEXCOORDINDEX to 1 for the blend pass. +bgfx::UniformHandle g_uTexcoordSelect = BGFX_INVALID_HANDLE; +float g_currentTexcoordSelect[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; +// .y = terrain blend flag: when > 0, shader does lerp(tex0, tex1, vertex_alpha) +// using UV set 0 for tex0 and UV set 1 for tex1 + // Lighting uniforms. The engine supports up to 4 lights // (typically 1 directional sun + 0-3 point lights). We pack light data // into vec4 arrays and push them per-draw so the uber fragment shader @@ -166,6 +185,11 @@ float g_currentLightColors[4][4] = { { 0.0f, 0.0f, 0.0f, 0.0f } }; float g_currentSceneAmbient[4] = { 0.45f, 0.45f, 0.45f, 1.0f }; + +// UV animation: texture transform for stage 0. +// Packed as (scaleU, scaleV, offsetU, offsetV). Default = identity (1,1,0,0). +bgfx::UniformHandle g_uTexTransform0 = BGFX_INVALID_HANDLE; +float g_currentTexTransform0[4] = { 1.0f, 1.0f, 0.0f, 0.0f }; // Tracks whether the current material has lighting enabled. // When false, the vertex color contains pre-baked lighting (terrain) // and the fragment shader should NOT apply N.L lighting on top. @@ -178,6 +202,19 @@ float g_currentLightingEnabled[4] = { 1.0f, 0.0f, 0.0f, 0.0f }; // because the fragment shader does texColor * v_color0 - white * vc // gives the vertex color through unmodified. bgfx::TextureHandle g_defaultWhiteTexture = BGFX_INVALID_HANDLE; +bgfx::TextureHandle g_defaultTransparentTexture = BGFX_INVALID_HANDLE; +// Track which engine textures are render targets so we can use the +// transparent fallback instead of white. +std::unordered_map g_renderTargetSet; + +struct BgfxFramebufferEntry +{ + bgfx::FrameBufferHandle fb; + bgfx::TextureHandle colorTex; + uint16_t width; + uint16_t height; +}; +std::unordered_map g_framebufferCache; // Vertex layout used by the test triangle. Position + packed RGBA color. // Initialized in Initialize since bgfx::VertexLayout::begin needs bgfx to be @@ -723,9 +760,21 @@ std::unordered_map g // SubmitEngineDraw - falls back to g_defaultWhiteTexture if invalid. bgfx::TextureHandle g_currentBgfxTexture0 = BGFX_INVALID_HANDLE; bgfx::TextureHandle g_currentBgfxTexture1 = BGFX_INVALID_HANDLE; +// Track texture names for diagnostics +const char * g_currentTexName0 = ""; +const char * g_currentTexName1 = ""; bgfx::TextureHandle g_currentBgfxTexture2 = BGFX_INVALID_HANDLE; bgfx::TextureHandle g_currentBgfxTexture3 = BGFX_INVALID_HANDLE; -bool g_loggedFirstBgfxTextureCreate = false; + +// Per-stage sampler flags captured from the source TextureClass's +// Get_U/V_Addr_Mode in Set_Texture. Default 0 = use bgfx's creation-time +// default (usually linear filter + wrap). Shoreline LUT needs CLAMP +// because its U coord can exceed [0,1] and WRAP produces a visible +// stripe/checker artifact at the boundary. +uint32_t g_currentSamplerFlags0 = 0; +uint32_t g_currentSamplerFlags1 = 0; +uint32_t g_currentSamplerFlags2 = 0; +uint32_t g_currentSamplerFlags3 = 0; // The most recent buffers and offsets cached from Set_Vertex_Buffer / // Set_Index_Buffer. Read by Draw_Triangles when it issues the bgfx @@ -763,12 +812,6 @@ bgfx::TransientVertexBuffer g_currentTransientVB = {}; bool g_currentUseTransientIB = false; bgfx::TransientIndexBuffer g_currentTransientIB = {}; -bool g_loggedFirstBgfxVbCreate = false; -bool g_loggedFirstBgfxIbCreate = false; -bool g_loggedFirstBgfxSubmit = false; -bool g_loggedFirstDynVbCapture = false; -bool g_loggedFirstDynIbCapture = false; - // TheSuperHackers @refactor bobtista 11/04/2026 transform // capture. The engine calls Set_Transform with world / view / projection // matrices in W3D row-major form (Vector4 Row[4]). bgfx wants column- @@ -793,9 +836,6 @@ bool g_bgfxViewProjDirty = true; float g_bgfxCameraView[16]; float g_bgfxCameraProj[16]; bool g_bgfxCameraCaptured = false; -bool g_loggedFirstSetView = false; -bool g_loggedFirstSetProj = false; -bool g_loggedFirstSetWorld = false; // Engine geometry submits to its own view so it does not collide with // the test triangle on view 0. View 0 keeps the test triangle for the @@ -811,6 +851,19 @@ const bgfx::ViewId kBgfxEngineView = 1; // because bgfx::setViewTransform is per-view-for-the-whole-frame and // would otherwise stomp view 1's camera view if shared. const bgfx::ViewId kBgfxEngineSortView = 2; +const bgfx::ViewId kBgfxRTTView = 3; +const bgfx::ViewId kBgfxWaterView = 4; +// Effect overlay view for dazzle / lens flare / muzzle flash draws that +// submit vertices already in clip/NDC space. These require identity +// view and identity projection to render correctly. Routing them +// through the sort view (which has the camera perspective projection) +// re-projects their NDC coords and pushes them off-screen. +const bgfx::ViewId kBgfxEffectOverlayView = 5; + +// Render-to-texture state. Set by Set_Render_Target_With_Z, cleared +// when the back buffer is restored. SubmitEngineDraw routes to +// kBgfxRTTView while this is true. +bool g_renderToTexture = false; // True between Begin_Sorted_Batch_Pass and End_Sorted_Batch_Pass; // SubmitEngineDraw routes to kBgfxEngineSortView and uses @@ -830,6 +883,9 @@ float g_bgfxSortWorld[16]; // so falling through would emit a second, incorrect submit. bool g_skipNextSubmitEngineDraw = false; +// Water override — set by Override_Material_Opacity, consumed by +// SubmitEngineDraw to route to the water view and apply DESTALPHA blend. +bool g_waterOverrideActive = false; // Snapshot of g_bgfxProj at the time the sort flush runs. The engine // calls Set_Projection_Transform_With_Z_Bias multiple times per frame @@ -969,17 +1025,45 @@ bgfx::TextureHandle EnsureBgfxTexture(TextureBaseClass * tex) if (tex->Get_Pool() == TextureBaseClass::POOL_DEFAULT) { - // POOL_DEFAULT textures are GPU-only render targets (water - // reflections). Skip for now — future work will add proper - // render-to-texture via bgfx framebuffers. Don't cache the - // failure so we re-check each frame (the texture might not - // be initialized on the first check). + auto fbIt = g_framebufferCache.find(tex); + if (fbIt != g_framebufferCache.end()) + { + static bool s_loggedRTTResolve = false; + if (!s_loggedRTTResolve) + { + s_loggedRTTResolve = true; + WWDEBUG_SAY(("[BgfxBackend] RTT RESOLVE: POOL_DEFAULT tex=%p " + "resolved to framebuffer color texture %dx%d", + tex, fbIt->second.width, fbIt->second.height)); + } + return fbIt->second.colorTex; + } + + static int s_poolDefaultCount = 0; + if (s_poolDefaultCount < 10) + { + ++s_poolDefaultCount; + WWDEBUG_SAY(("[BgfxBackend] POOL_DEFAULT texture skipped #%d: %s %dx%d fmt=%d", + s_poolDefaultCount, + tex2d->Get_Full_Path().str(), + tex2d->Get_Width(), tex2d->Get_Height(), + static_cast(tex2d->Get_Texture_Format()))); + } + g_renderTargetSet[tex] = true; return BGFX_INVALID_HANDLE; } const bgfx::TextureFormat::Enum bgfxFmt = TranslateWW3DFormat(tex2d->Get_Texture_Format()); if (bgfxFmt == bgfx::TextureFormat::Unknown) { + static bool s_loggedUnknownFmt = false; + if (!s_loggedUnknownFmt) + { + s_loggedUnknownFmt = true; + WWDEBUG_SAY(("[BgfxBackend] UNKNOWN texture format: %s ww3dfmt=%d", + tex2d->Get_Full_Path().str(), + static_cast(tex2d->Get_Texture_Format()))); + } g_textureCache[tex] = BGFX_INVALID_HANDLE; return BGFX_INVALID_HANDLE; } @@ -987,11 +1071,6 @@ bgfx::TextureHandle EnsureBgfxTexture(TextureBaseClass * tex) IDirect3DBaseTexture8 * baseTex = tex->Peek_D3D_Base_Texture(); if (baseTex == nullptr) { - // Texture not yet uploaded to D3D - the background loader thread - // may not have finished. DO NOT cache the failure: we want to - // retry on the next Set_Texture call so the bgfx handle gets - // populated as soon as the engine finishes loading. Otherwise a - // texture that was simply slow to load would stay white forever. return BGFX_INVALID_HANDLE; } @@ -1039,66 +1118,12 @@ bgfx::TextureHandle EnsureBgfxTexture(TextureBaseClass * tex) mem); g_textureCache[tex] = h; - - if (!g_loggedFirstBgfxTextureCreate) - { - g_loggedFirstBgfxTextureCreate = true; - WWDEBUG_SAY(("[BgfxBackend] EnsureBgfxTexture first create " - "%dx%d ww3dfmt=%u bytes=%u handle=%s", - desc.Width, desc.Height, - static_cast(tex2d->Get_Texture_Format()), - totalBytes, - bgfx::isValid(h) ? "ok" : "FAILED")); - } return h; } -// TheSuperHackers @refactor bobtista 11/04/2026 Set_Shader -// telemetry. The bgfx backend caches the picked program and state bits -// for the most recent ShaderClass it received, and tracks the unique -// shader bit patterns it has seen so we can log them once each. This -// is diagnostic only - no draw call uses these yet, but it confirms -// that the engine is calling Set_Shader on the bgfx backend and gives -// us real preset coverage data without touching rendering. bgfx::ProgramHandle g_currentBgfxProgram = BGFX_INVALID_HANDLE; uint64_t g_currentBgfxState = 0; -const int kMaxLoggedPresets = 32; -unsigned int g_loggedPresetBits[kMaxLoggedPresets] = { 0 }; -int g_loggedPresetCount = 0; - -bool RecordPresetIfNew(unsigned int bits) -{ - for (int i = 0; i < g_loggedPresetCount; ++i) - { - if (g_loggedPresetBits[i] == bits) - { - return false; - } - } - if (g_loggedPresetCount < kMaxLoggedPresets) - { - g_loggedPresetBits[g_loggedPresetCount++] = bits; - return true; - } - return false; -} - -const char * ProgramDebugName(bgfx::ProgramHandle h) -{ - if (h.idx == g_passthroughProgram.idx) return "passthrough"; - if (h.idx == g_uberProgram.idx) return "uber"; - return "?"; -} - -// Per-method "first call" flags so we can confirm the engine is routing -// each entry point through the bgfx backend at least once. Logged once, -// then suppressed to keep WWDebug.txt readable. -bool g_loggedFirstSetTexture = false; -bool g_loggedFirstDrawTriangles = false; -bool g_loggedFirstSetVertexBuffer = false; -bool g_loggedFirstSetIndexBuffer = false; - // Create the bgfx debug popup window. Returns the HWND or nullptr on failure. HWND CreateBgfxDebugWindow() { @@ -1112,14 +1137,27 @@ HWND CreateBgfxDebugWindow() RECT rc = { 0, 0, kBgfxWindowWidth, kBgfxWindowHeight }; AdjustWindowRectEx(&rc, style, FALSE, exStyle); + const int frameW = rc.right - rc.left; + const int frameH = rc.bottom - rc.top; + + // Place the popup on the right edge of the primary monitor so the + // game's default top-left window and the bgfx popup are side by side + // without manual dragging. Fall back to (100,100) if the screen is + // narrower than the popup (multi-monitor edge case / RDP session). + const int screenW = GetSystemMetrics(SM_CXSCREEN); + const int screenH = GetSystemMetrics(SM_CYSCREEN); + int posX = screenW - frameW - 10; + int posY = 40; + if (posX < 0) posX = 100; + if (posY + frameH > screenH) posY = 10; HWND hwnd = CreateWindowExW( exStyle, kBgfxWindowClass, L"bgfx backend", style, - 100, 100, - rc.right - rc.left, rc.bottom - rc.top, + posX, posY, + frameW, frameH, nullptr, nullptr, GetModuleHandleW(nullptr), @@ -1192,9 +1230,14 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) // On D3D11 bgfx needs an explicit reset call to rebuild the swapchain // with a multi-sampled back buffer; setting resolution.reset in the // Init struct alone isn't enough in every bgfx build. + // Force RGBA8 back buffer format so the alpha channel is available + // for the DESTALPHA water blending technique. Without this, the + // swap chain may use an opaque format (e.g. BGRX8) where alpha + // is always 1.0, making destination-alpha blending ineffective. bgfx::reset(static_cast(kBgfxWindowWidth), static_cast(kBgfxWindowHeight), - BGFX_RESET_MSAA_X4); + BGFX_RESET_MSAA_X4, + bgfx::TextureFormat::RGBA8); // Configure view 0 to clear the debug window to a dark teal so it's // visually obvious bgfx is running and alive. View 0 holds the test @@ -1212,9 +1255,14 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) // own clear/depth and (eventually) its own view+projection matrices // captured from the engine's Set_Transform calls. Drawn after view 0 // so engine geometry overlays the test triangle. + // Clear depth AND color. The color clear initializes the framebuffer + // alpha to ~0.7 (m_minWaterOpacity) for the DESTALPHA water technique. + // Without this, deep water areas without shoreline tiles have alpha=0 + // (transparent) and the water polygon edge creates a visible zigzag. + // The RGB clear is black; terrain overwrites it with its own color. bgfx::setViewClear(kBgfxEngineView, - BGFX_CLEAR_DEPTH, - 0x00000000, + BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, + 0x000000ff, // Alpha=1.0 matches TransparentWaterMinOpacity=1.0 from INI 1.0f, 0); bgfx::setViewRect(kBgfxEngineView, 0, 0, @@ -1235,6 +1283,23 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) static_cast(kBgfxWindowWidth), static_cast(kBgfxWindowHeight)); + // Effect overlay view for dazzle draws with NDC-space vertices. + // Permanent identity view + identity projection; reuses the + // backbuffer + depth from earlier views. No clear. + bgfx::setViewClear(kBgfxEffectOverlayView, + BGFX_CLEAR_NONE, + 0x00000000, + 1.0f, + 0); + bgfx::setViewRect(kBgfxEffectOverlayView, 0, 0, + static_cast(kBgfxWindowWidth), + static_cast(kBgfxWindowHeight)); + { + float identityMtx[16]; + IdentityMatrix(identityMtx); + bgfx::setViewTransform(kBgfxEffectOverlayView, identityMtx, identityMtx); + } + // Default the cached transforms to identity until the engine writes // real values via Set_Transform. This keeps the first few engine // submits well-defined even if they fire before any matrices are @@ -1295,6 +1360,8 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) g_uLightColors = bgfx::createUniform("u_lightColors", bgfx::UniformType::Vec4, 4); g_uSceneAmbient = bgfx::createUniform("u_sceneAmbient", bgfx::UniformType::Vec4); g_uLightingEnabled = bgfx::createUniform("u_lightingEnabled", bgfx::UniformType::Vec4); + g_uTexcoordSelect = bgfx::createUniform("u_texcoordSelect", bgfx::UniformType::Vec4); + g_uTexTransform0 = bgfx::createUniform("u_texTransform0", bgfx::UniformType::Vec4); // Default 1x1 white texture. Used as fallback for missing textures. // Multiplying by white is the identity operation. @@ -1304,6 +1371,16 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_NONE | BGFX_SAMPLER_POINT, bgfx::copy(kWhitePixel, sizeof(kWhitePixel))); + // Water fallback for render target textures (water reflections). + // Semi-opaque dark blue simulates the water surface so the hull below + // is partially hidden and water ripple particles blend naturally. + // RGBA: (30, 50, 70, 180) = dark blue-grey, ~70% opaque. + static const uint8_t kWaterPixel[4] = { 0x1e, 0x32, 0x46, 0xb4 }; + g_defaultTransparentTexture = bgfx::createTexture2D( + 1, 1, false, 1, + bgfx::TextureFormat::RGBA8, + BGFX_TEXTURE_NONE | BGFX_SAMPLER_POINT, + bgfx::copy(kWaterPixel, sizeof(kWaterPixel))); // Uber shader program — single program for all engine draws. const bgfx::Memory * vsUberMem = bgfx::makeRef(vs_uber_dx11, sizeof(vs_uber_dx11)); @@ -1323,12 +1400,24 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) const bgfx::RendererType::Enum selected = bgfx::getRendererType(); const char * rendererName = bgfx::getRendererName(selected); + const bgfx::Caps * caps = bgfx::getCaps(); WWDEBUG_SAY(("[BgfxBackend] bgfx::init OK on debug window " "(renderer=%s, %dx%d, hwnd=%p, passthrough=%s, uber=%s).", rendererName, kBgfxWindowWidth, kBgfxWindowHeight, g_bgfxWindow, bgfx::isValid(g_passthroughProgram) ? "ok" : "FAILED", bgfx::isValid(g_uberProgram) ? "ok" : "FAILED")); + // Log whether RGBA8 is supported as a render target (needed for + // DESTALPHA water technique — back buffer must have alpha channel). + const bool rgba8Supported = (caps->formats[bgfx::TextureFormat::RGBA8] & + BGFX_CAPS_FORMAT_TEXTURE_FRAMEBUFFER) != 0; + const bool bgra8Supported = (caps->formats[bgfx::TextureFormat::BGRA8] & + BGFX_CAPS_FORMAT_TEXTURE_FRAMEBUFFER) != 0; + WWDEBUG_SAY(("[BgfxBackend] Caps: RGBA8_FB=%d BGRA8_FB=%d " + "homogeneousDepth=%d originBottomLeft=%d", + rgba8Supported ? 1 : 0, bgra8Supported ? 1 : 0, + caps->homogeneousDepth ? 1 : 0, + caps->originBottomLeft ? 1 : 0)); } void BgfxBackend::Shutdown() @@ -1405,11 +1494,35 @@ void BgfxBackend::Shutdown() bgfx::destroy(g_uLightingEnabled); g_uLightingEnabled = BGFX_INVALID_HANDLE; } + if (bgfx::isValid(g_uTexTransform0)) + { + bgfx::destroy(g_uTexTransform0); + g_uTexTransform0 = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(g_uTexcoordSelect)) + { + bgfx::destroy(g_uTexcoordSelect); + g_uTexcoordSelect = BGFX_INVALID_HANDLE; + } if (bgfx::isValid(g_defaultWhiteTexture)) { bgfx::destroy(g_defaultWhiteTexture); g_defaultWhiteTexture = BGFX_INVALID_HANDLE; } + if (bgfx::isValid(g_defaultTransparentTexture)) + { + bgfx::destroy(g_defaultTransparentTexture); + g_defaultTransparentTexture = BGFX_INVALID_HANDLE; + } + g_renderTargetSet.clear(); + for (auto & kv : g_framebufferCache) + { + if (bgfx::isValid(kv.second.fb)) + { + bgfx::destroy(kv.second.fb); + } + } + g_framebufferCache.clear(); // cached engine buffers. Destroy before bgfx::shutdown // so the handles outlive nothing. for (auto & kv : g_vbCache) @@ -1473,6 +1586,12 @@ void BgfxBackend::Begin_Scene() bgfx::touch(0); bgfx::touch(kBgfxEngineView); bgfx::touch(kBgfxEngineSortView); + bgfx::touch(kBgfxWaterView); + bgfx::touch(kBgfxEffectOverlayView); + bgfx::setViewRect(kBgfxWaterView, 0, 0, kBgfxWindowWidth, kBgfxWindowHeight); + // No clear on water view — it composites over the opaque scene. + bgfx::setViewClear(kBgfxWaterView, BGFX_CLEAR_NONE, 0, 1.0f, 0); + g_renderToTexture = false; } void BgfxBackend::End_Scene(bool /*flip_frame*/) @@ -1491,6 +1610,7 @@ void BgfxBackend::End_Scene(bool /*flip_frame*/) if (g_bgfxCameraCaptured) { bgfx::setViewTransform(kBgfxEngineView, g_bgfxCameraView, g_bgfxCameraProj); + bgfx::setViewTransform(kBgfxWaterView, g_bgfxCameraView, g_bgfxCameraProj); g_bgfxCameraCaptured = false; } if (g_bgfxSortProjCaptured) @@ -1501,6 +1621,13 @@ void BgfxBackend::End_Scene(bool /*flip_frame*/) g_bgfxSortProjCaptured = false; } + // View 3 (RTT) renders first. View 1 draws opaque scene. + // View 4 (water) draws DESTALPHA overlay. View 2 (sort). + // View 5 (effect overlay / dazzle) last so lens flares sit on top. + bgfx::ViewId viewOrder[] = { kBgfxRTTView, kBgfxEngineView, kBgfxWaterView, + kBgfxEngineSortView, kBgfxEffectOverlayView }; + bgfx::setViewOrder(0, 5, viewOrder); + bgfx::frame(); // Transient buffers are freed at bgfx::frame time. Invalidate the @@ -1592,14 +1719,6 @@ void BgfxBackend::Set_Index_Buffer(const IndexBufferClass * ib, unsigned short i { DX8Backend::Set_Index_Buffer(ib, index_base_offset); - if (!g_loggedFirstSetIndexBuffer && ib != nullptr) - { - g_loggedFirstSetIndexBuffer = true; - WWDEBUG_SAY(("[BgfxBackend] Set_Index_Buffer first call (static IB path) " - "indices=%u type=%u", - ib->Get_Index_Count(), - ib->Type())); - } g_currentUseTransientIB = false; auto it = g_ibCache.find(ib); if (it != g_ibCache.end()) @@ -1742,6 +1861,7 @@ void BgfxBackend::Capture_Vertex_Data(const VertexBufferClass * vb, { return; } + bgfx::DynamicVertexBufferHandle h = EnsureDynamicVertexBuffer(vb); if (!bgfx::isValid(h)) { @@ -1749,17 +1869,6 @@ void BgfxBackend::Capture_Vertex_Data(const VertexBufferClass * vb, } const bgfx::Memory * mem = bgfx::copy(data, size_bytes); bgfx::update(h, 0, mem); - - if (!g_loggedFirstBgfxVbCreate) - { - g_loggedFirstBgfxVbCreate = true; - WWDEBUG_SAY(("[BgfxBackend] Capture_Vertex_Data first update " - "fvf=0x%08x stride=%u verts=%u bytes=%u", - vb->FVF_Info().Get_FVF(), - vb->FVF_Info().Get_FVF_Size(), - vb->Get_Vertex_Count(), - size_bytes)); - } } void BgfxBackend::Capture_Index_Data(const IndexBufferClass * ib, @@ -1777,15 +1886,6 @@ void BgfxBackend::Capture_Index_Data(const IndexBufferClass * ib, } const bgfx::Memory * mem = bgfx::copy(data, size_bytes); bgfx::update(h, 0, mem); - - if (!g_loggedFirstBgfxIbCreate) - { - g_loggedFirstBgfxIbCreate = true; - WWDEBUG_SAY(("[BgfxBackend] Capture_Index_Data first update " - "indices=%u bytes=%u", - ib->Get_Index_Count(), - size_bytes)); - } } void BgfxBackend::Capture_Vertex_Sub_Range(const VertexBufferClass * vb, @@ -1840,30 +1940,15 @@ void BgfxBackend::Capture_Index_Sub_Range(const IndexBufferClass * ib, // row-major product sortWorld * sortView directly into bgfx // column-major layout (the transpose is baked into the index // pattern), avoiding an intermediate copy. -static bool g_loggedFirstSortBegin = false; -static bool g_loggedFirstSortCapture = false; void BgfxBackend::Begin_Sorted_Batch_Pass() { g_inSortFlush = true; - - // Capture the projection NOW (while it's still the camera projection). - // Later Set_Projection calls (water, shadows) may overwrite g_bgfxProj - // with a tiny frustum. We re-apply this saved projection at End_Scene. if (!g_bgfxSortProjCaptured) { std::memcpy(g_bgfxSortProj, g_bgfxProj, sizeof(g_bgfxSortProj)); g_bgfxSortProjCaptured = true; } - - if (!g_loggedFirstSortBegin) - { - g_loggedFirstSortBegin = true; - WWDEBUG_SAY(("[BgfxBackend] Begin_Sorted_Batch_Pass first fire " - "transVB=%d transIB=%d", - int(g_currentUseTransientVB), - int(g_currentUseTransientIB))); - } } void BgfxBackend::End_Sorted_Batch_Pass() @@ -1895,28 +1980,18 @@ void BgfxBackend::Capture_Sorted_Batch_Transforms(const Matrix4x4 & sortWorld, } } - if (!g_loggedFirstSortCapture) - { - g_loggedFirstSortCapture = true; - WWDEBUG_SAY(("[BgfxBackend] Capture_Sorted_Batch_Transforms first " - "sortWorld row0=%.3f,%.3f,%.3f,%.3f sortView row0=%.3f,%.3f,%.3f,%.3f", - sortWorld[0][0], sortWorld[0][1], sortWorld[0][2], sortWorld[0][3], - sortView[0][0], sortView[0][1], sortView[0][2], sortView[0][3])); - WWDEBUG_SAY(("[BgfxBackend] combined (column-major) col0=%.3f,%.3f,%.3f,%.3f col3=%.3f,%.3f,%.3f,%.3f", - g_bgfxSortWorld[0], g_bgfxSortWorld[1], g_bgfxSortWorld[2], g_bgfxSortWorld[3], - g_bgfxSortWorld[12], g_bgfxSortWorld[13], g_bgfxSortWorld[14], g_bgfxSortWorld[15])); - } } void BgfxBackend::Capture_Sorted_Batch_Light(const D3DLIGHT8 & light, bool enabled) { // Sort batch lights are always light 0 (the primary directional). // Scene ambient was already captured by Set_Light_Environment. + // D3D8 Direction points FROM light toward surface; negate for N.L. if (enabled) { - g_currentLightDirs[0][0] = light.Direction.x; - g_currentLightDirs[0][1] = light.Direction.y; - g_currentLightDirs[0][2] = light.Direction.z; + g_currentLightDirs[0][0] = -light.Direction.x; + g_currentLightDirs[0][1] = -light.Direction.y; + g_currentLightDirs[0][2] = -light.Direction.z; g_currentLightDirs[0][3] = 1.0f; g_currentLightColors[0][0] = light.Diffuse.r; g_currentLightColors[0][1] = light.Diffuse.g; @@ -1943,7 +2018,6 @@ void BgfxBackend::Capture_Sorted_Batch_Light(const D3DLIGHT8 & light, bool enabl // buffers), and set g_skipNextSubmitEngineDraw so the outer Draw_Triangles // does not emit a second, incorrect submit using the old sorting-VB // args. -static bool g_loggedFirstSortedDirectSubmit = false; void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, const DynamicIBAccessClass & dyn_ib, @@ -1960,9 +2034,10 @@ void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, // have stashed their transients keyed by &dyn_vb / &dyn_ib. if (!g_pendingDynVB.valid || g_pendingDynVB.owner != &dyn_vb) { - if (!g_loggedFirstSortedDirectSubmit) + static bool s_loggedSkipVB = false; + if (!s_loggedSkipVB) { - g_loggedFirstSortedDirectSubmit = true; + s_loggedSkipVB = true; WWDEBUG_SAY(("[BgfxBackend] Submit_Sorted_Draw SKIP: pendingDynVB not " "claimable (valid=%d ownerMatch=%d)", int(g_pendingDynVB.valid), @@ -1972,9 +2047,10 @@ void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, } if (!g_pendingDynIB.valid || g_pendingDynIB.owner != &dyn_ib) { - if (!g_loggedFirstSortedDirectSubmit) + static bool s_loggedSkipIB = false; + if (!s_loggedSkipIB) { - g_loggedFirstSortedDirectSubmit = true; + s_loggedSkipIB = true; WWDEBUG_SAY(("[BgfxBackend] Submit_Sorted_Draw SKIP: pendingDynIB not " "claimable (valid=%d ownerMatch=%d)", int(g_pendingDynIB.valid), @@ -2006,14 +2082,13 @@ void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, bgfx::setVertexBuffer(0, &vb, 0, vertex_count); bgfx::setIndexBuffer(&ib, 0, static_cast(polygon_count) * 3); - const uint32_t kSamplerFlags = 0; if (bgfx::isValid(g_sTex0)) { const bgfx::TextureHandle bound = bgfx::isValid(g_currentBgfxTexture0) ? g_currentBgfxTexture0 : g_defaultWhiteTexture; if (bgfx::isValid(bound)) { - bgfx::setTexture(0, g_sTex0, bound, kSamplerFlags); + bgfx::setTexture(0, g_sTex0, bound, g_currentSamplerFlags0); } } if (bgfx::isValid(g_sTex1)) @@ -2022,7 +2097,7 @@ void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, bgfx::isValid(g_currentBgfxTexture1) ? g_currentBgfxTexture1 : g_defaultWhiteTexture; if (bgfx::isValid(bound)) { - bgfx::setTexture(1, g_sTex1, bound, kSamplerFlags); + bgfx::setTexture(1, g_sTex1, bound, g_currentSamplerFlags1); } } if (bgfx::isValid(g_sTex2)) @@ -2031,7 +2106,7 @@ void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, bgfx::isValid(g_currentBgfxTexture2) ? g_currentBgfxTexture2 : g_defaultWhiteTexture; if (bgfx::isValid(bound)) { - bgfx::setTexture(2, g_sTex2, bound, kSamplerFlags); + bgfx::setTexture(2, g_sTex2, bound, g_currentSamplerFlags2); } } if (bgfx::isValid(g_sTex3)) @@ -2040,7 +2115,7 @@ void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, bgfx::isValid(g_currentBgfxTexture3) ? g_currentBgfxTexture3 : g_defaultWhiteTexture; if (bgfx::isValid(bound)) { - bgfx::setTexture(3, g_sTex3, bound, kSamplerFlags); + bgfx::setTexture(3, g_sTex3, bound, g_currentSamplerFlags3); } } @@ -2067,20 +2142,18 @@ void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, ? g_currentBgfxState : (BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); - bgfx::setState(state); - - bgfx::submit(kBgfxEngineSortView, g_currentBgfxProgram); - - if (!g_loggedFirstSortedDirectSubmit) + // Apply color write mask override — same as SubmitEngineDraw. + // Without this, sorted particles/effects write alpha, destroying + // the shoreline depth gradient used by DESTALPHA water blending. + if (g_colorWriteOverride >= 0) { - g_loggedFirstSortedDirectSubmit = true; - WWDEBUG_SAY(("[BgfxBackend] Submit_Sorted_Draw first fire " - "(polys=%u verts=%u worldCol3=%.2f,%.2f,%.2f state=0x%016llx)", - polygon_count, vertex_count, - worldMtx[12], worldMtx[13], worldMtx[14], - static_cast(state))); + state &= ~(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); + state |= static_cast(g_colorWriteOverride); } + bgfx::setState(state); + bgfx::submit(kBgfxEngineSortView, g_currentBgfxProgram); + g_skipNextSubmitEngineDraw = true; } @@ -2130,17 +2203,6 @@ void BgfxBackend::Capture_Dynamic_Vertex_Data(const DynamicVBAccessClass * vba, g_pendingDynVB.owner = vba; g_pendingDynVB.valid = true; - if (!g_loggedFirstDynVbCapture) - { - g_loggedFirstDynVbCapture = true; - WWDEBUG_SAY(("[BgfxBackend] Capture_Dynamic_Vertex_Data first " - "fvf=0x%08x stride=%u verts=%u bytes=%u", - vba->FVF_Info().Get_FVF(), - layout.getStride(), - num_verts, - bytes)); - } - } void BgfxBackend::Capture_Dynamic_Index_Data(const DynamicIBAccessClass * iba, @@ -2169,16 +2231,6 @@ void BgfxBackend::Capture_Dynamic_Index_Data(const DynamicIBAccessClass * iba, std::memcpy(g_pendingDynIB.tib.data, data, bytes); g_pendingDynIB.owner = iba; g_pendingDynIB.valid = true; - - if (!g_loggedFirstDynIbCapture) - { - g_loggedFirstDynIbCapture = true; - WWDEBUG_SAY(("[BgfxBackend] Capture_Dynamic_Index_Data first " - "indices=%u bytes=%u", - num_indices, - bytes)); - } - } // -- State: shaders, materials, textures ------------------------------------ @@ -2190,16 +2242,7 @@ void BgfxBackend::Set_Shader(const ShaderClass & shader) g_currentBgfxProgram = g_uberProgram; g_currentBgfxState = BuildBgfxStateForShader(shader); BuildTssOpsForShader(shader, g_currentTssOps0, g_currentTssOps1, &g_currentAtestRef); - - if (RecordPresetIfNew(shader.Get_Bits())) - { - WWDEBUG_SAY(("[BgfxBackend] Set_Shader new preset bits=0x%08x -> " - "tssOps=(%.0f,%.0f,%.0f,%.0f) state=0x%016llx", - shader.Get_Bits(), - g_currentTssOps0[0], g_currentTssOps0[1], - g_currentTssOps0[2], g_currentTssOps0[3], - static_cast(g_currentBgfxState))); - } + Clear_State_Overrides(); } void BgfxBackend::Set_Material(const VertexMaterialClass * material) @@ -2224,6 +2267,13 @@ void BgfxBackend::Set_Material(const VertexMaterialClass * material) // (terrain, pre-lit meshes), vertex colors already contain the // lit result and the shader must NOT apply N.L on top. g_currentLightingEnabled[0] = material->Get_Lighting() ? 1.0f : 0.0f; + // Reset texture transform to identity. The mapper's Apply() will + // set it if UV animation is needed. Without this reset, a previous + // draw's UV scroll would persist into the next draw. + g_currentTexTransform0[0] = 1.0f; + g_currentTexTransform0[1] = 1.0f; + g_currentTexTransform0[2] = 0.0f; + g_currentTexTransform0[3] = 0.0f; } else { @@ -2238,22 +2288,68 @@ void BgfxBackend::Set_Texture(unsigned int stage, TextureBaseClass * texture) { DX8Backend::Set_Texture(stage, texture); - if (!g_loggedFirstSetTexture) - { - g_loggedFirstSetTexture = true; - WWDEBUG_SAY(("[BgfxBackend] Set_Texture first call (stage=%u)", stage)); - } // stages 0-3 wired. Covers terrain base + detail // + cloud + noise, the standard 4-stage layout used by the // FlatHeightMap pixel shader family. Stages above 3 still fall // through unmigrated. - switch (stage) { - case 0: g_currentBgfxTexture0 = EnsureBgfxTexture(texture); break; - case 1: g_currentBgfxTexture1 = EnsureBgfxTexture(texture); break; - case 2: g_currentBgfxTexture2 = EnsureBgfxTexture(texture); break; - case 3: g_currentBgfxTexture3 = EnsureBgfxTexture(texture); break; - default: break; + bgfx::TextureHandle h = EnsureBgfxTexture(texture); + // If the texture is INVALID and it's a render target, use the + // water-colored fallback instead of white. + if (!bgfx::isValid(h) && texture != nullptr && + g_renderTargetSet.count(texture) > 0) + { + h = g_defaultTransparentTexture; + static int s_loggedRTFallback = 0; + if (s_loggedRTFallback < 5) + { + ++s_loggedRTFallback; + TextureClass * t2d_fb = texture->As_TextureClass(); + WWDEBUG_SAY(("[BgfxBackend] RT FALLBACK: stage=%u using dark blue fallback for %s", + stage, + t2d_fb ? t2d_fb->Get_Full_Path().str() : "(null)")); + } + } + // Log when an invalid texture goes to white fallback + if (!bgfx::isValid(h) && texture != nullptr && + g_renderTargetSet.count(texture) == 0) + { + static int s_loggedWhiteFallback = 0; + if (s_loggedWhiteFallback < 5) + { + ++s_loggedWhiteFallback; + TextureClass * t2d_fb = texture->As_TextureClass(); + WWDEBUG_SAY(("[BgfxBackend] WHITE FALLBACK: stage=%u tex=%s pool=%d", + stage, + t2d_fb ? t2d_fb->Get_Full_Path().str() : "(null)", + texture->Get_Pool())); + } + } + TextureClass * t2d_name = texture ? texture->As_TextureClass() : nullptr; + const char * texName = t2d_name ? t2d_name->Get_Full_Path().str() : ""; + // Capture the source texture's wrap mode into bgfx sampler flags + // so we can pass it at bind time. Without this, WRAP is the + // default and ramp/LUT textures with CLAMP semantics produce + // visible stripe artifacts at U>=1 (the shoreline checkerboard). + uint32_t samplerFlags = 0; + if (t2d_name != nullptr) + { + const TextureFilterClass & flt = t2d_name->Get_Filter(); + if (flt.Get_U_Addr_Mode() == TextureFilterClass::TEXTURE_ADDRESS_CLAMP) + samplerFlags |= BGFX_SAMPLER_U_CLAMP; + if (flt.Get_V_Addr_Mode() == TextureFilterClass::TEXTURE_ADDRESS_CLAMP) + samplerFlags |= BGFX_SAMPLER_V_CLAMP; + } + switch (stage) + { + case 0: g_currentBgfxTexture0 = h; g_currentTexName0 = texName; + g_currentSamplerFlags0 = samplerFlags; break; + case 1: g_currentBgfxTexture1 = h; g_currentTexName1 = texName; + g_currentSamplerFlags1 = samplerFlags; break; + case 2: g_currentBgfxTexture2 = h; g_currentSamplerFlags2 = samplerFlags; break; + case 3: g_currentBgfxTexture3 = h; g_currentSamplerFlags3 = samplerFlags; break; + default: break; + } } } @@ -2265,6 +2361,159 @@ void BgfxBackend::Set_Ambient(const Vector3 & color) g_currentSceneAmbient[2] = color.Z; } +void BgfxBackend::Override_Blend(unsigned srcBlend, unsigned dstBlend) +{ + static const uint64_t kBlendMap[9] = { + 0, + BGFX_STATE_BLEND_ZERO, // 1 + BGFX_STATE_BLEND_ONE, // 2 + BGFX_STATE_BLEND_SRC_COLOR, // 3 + BGFX_STATE_BLEND_INV_SRC_COLOR, // 4 + BGFX_STATE_BLEND_SRC_ALPHA, // 5 + BGFX_STATE_BLEND_INV_SRC_ALPHA, // 6 + BGFX_STATE_BLEND_DST_ALPHA, // 7 + BGFX_STATE_BLEND_INV_DST_ALPHA // 8 + }; + if (srcBlend >= 1 && srcBlend <= 8 && dstBlend >= 1 && dstBlend <= 8) + { + g_blendOverrideActive = true; + g_blendOverrideBits = BGFX_STATE_BLEND_FUNC(kBlendMap[srcBlend], kBlendMap[dstBlend]); + } + else + { + static bool s_loggedBlendBad = false; + if (!s_loggedBlendBad) + { + s_loggedBlendBad = true; + WWDEBUG_SAY(("[BgfxBackend] BLEND OVERRIDE BAD VALUES: src=%u dst=%u (out of range 1-8)", + srcBlend, dstBlend)); + } + } + DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, srcBlend); + DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, dstBlend); +} + +void BgfxBackend::Override_Alpha_Test(bool enable, unsigned ref, unsigned func) +{ + g_atestOverrideActive = enable; + g_atestOverrideRef = enable ? (ref / 255.0f) : 0.0f; + DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHATESTENABLE, enable ? TRUE : FALSE); + DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF, ref); + DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC, func); +} + +void BgfxBackend::Override_Alpha_Blend_Enable(bool enable) +{ + if (enable) + { + g_blendOverrideActive = true; + g_blendOverrideBits = BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_SRC_ALPHA, + BGFX_STATE_BLEND_INV_SRC_ALPHA); + } + DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE, enable ? TRUE : FALSE); +} + +void BgfxBackend::Override_Texcoord_Index(unsigned stage, unsigned uvIndex) +{ + if (stage == 0) + { + g_currentTexcoordSelect[0] = (uvIndex == 1) ? 1.0f : 0.0f; + } + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_TEXCOORDINDEX, uvIndex); +} + +void BgfxBackend::Override_Terrain_Blend(bool enable) +{ + g_currentTexcoordSelect[1] = enable ? 1.0f : 0.0f; +} + +void BgfxBackend::Override_Material_Opacity(float opacity) +{ + g_currentMatDiffuse[3] = opacity; + g_waterOverrideActive = true; + g_blendOverrideActive = true; + g_blendOverrideBits = BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_DST_ALPHA, + BGFX_STATE_BLEND_INV_DST_ALPHA); +} + +void BgfxBackend::Begin_Effect_Overlay() +{ + g_effectOverlayActive = true; +} + +void BgfxBackend::End_Effect_Overlay() +{ + g_effectOverlayActive = false; +} + +void BgfxBackend::Set_Color_Write_Enable(bool red, bool green, bool blue, bool alpha) +{ + DX8Backend::Set_Color_Write_Enable(red, green, blue, alpha); + uint64_t mask = 0; + if (red) mask |= BGFX_STATE_WRITE_R; + if (green) mask |= BGFX_STATE_WRITE_G; + if (blue) mask |= BGFX_STATE_WRITE_B; + if (alpha) mask |= BGFX_STATE_WRITE_A; + g_colorWriteOverride = static_cast(mask); + g_suppressBgfxDraw = false; +} + +void BgfxBackend::Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass * ztexture) +{ + DX8Backend::Set_Render_Target_With_Z(texture, ztexture); + + if (texture == nullptr || !g_bgfxInitialized) + { + g_renderToTexture = false; + return; + } + + auto it = g_framebufferCache.find(texture); + if (it == g_framebufferCache.end()) + { + TextureClass * tex2d = texture->As_TextureClass(); + const uint16_t w = tex2d ? static_cast(tex2d->Get_Width()) : 64; + const uint16_t h = tex2d ? static_cast(tex2d->Get_Height()) : 64; + + bgfx::TextureHandle colorTex = bgfx::createTexture2D( + w, h, false, 1, bgfx::TextureFormat::RGBA8, + BGFX_TEXTURE_RT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + bgfx::TextureHandle depthTex = bgfx::createTexture2D( + w, h, false, 1, bgfx::TextureFormat::D24S8, + BGFX_TEXTURE_RT_WRITE_ONLY); + + bgfx::TextureHandle attachments[2] = { colorTex, depthTex }; + bgfx::FrameBufferHandle fb = bgfx::createFrameBuffer(2, attachments, true); + + BgfxFramebufferEntry entry = { fb, colorTex, w, h }; + g_framebufferCache[texture] = entry; + it = g_framebufferCache.find(texture); + + WWDEBUG_SAY(("[BgfxBackend] RTT framebuffer created %dx%d for tex=%p", + w, h, texture)); + } + + const BgfxFramebufferEntry & entry = it->second; + + bgfx::setViewFrameBuffer(kBgfxRTTView, entry.fb); + bgfx::setViewRect(kBgfxRTTView, 0, 0, entry.width, entry.height); + bgfx::setViewClear(kBgfxRTTView, + BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, + 0x000000ff, 1.0f, 0); + bgfx::touch(kBgfxRTTView); + + g_renderToTexture = true; +} + +void BgfxBackend::Clear_State_Overrides() +{ + g_blendOverrideActive = false; + g_atestOverrideActive = false; + g_suppressBgfxDraw = false; + g_currentTexcoordSelect[0] = 0.0f; + g_currentTexcoordSelect[1] = 0.0f; +} + void BgfxBackend::Set_Light_Environment(LightEnvironmentClass * light_env) { DX8Backend::Set_Light_Environment(light_env); @@ -2316,36 +2565,15 @@ void BgfxBackend::Set_Transform(TransformKind transform, const Matrix4x4 & m) { case RB_TRANSFORM_WORLD: W3DMatrix4ToBgfx(m, g_bgfxWorld); - if (!g_loggedFirstSetWorld) - { - g_loggedFirstSetWorld = true; - WWDEBUG_SAY(("[BgfxBackend] Set_Transform first WORLD (Matrix4x4) " - "row0=%.3f,%.3f,%.3f,%.3f", - m[0][0], m[0][1], m[0][2], m[0][3])); - } break; case RB_TRANSFORM_VIEW: W3DMatrix4ToBgfx(m, g_bgfxView); g_bgfxViewProjDirty = true; - if (!g_loggedFirstSetView) - { - g_loggedFirstSetView = true; - WWDEBUG_SAY(("[BgfxBackend] Set_Transform first VIEW (Matrix4x4) " - "row0=%.3f,%.3f,%.3f,%.3f", - m[0][0], m[0][1], m[0][2], m[0][3])); - } break; case RB_TRANSFORM_PROJECTION: W3DMatrix4ToBgfx(m, g_bgfxProj); ApplyPopupAspectCorrection(g_bgfxProj); g_bgfxViewProjDirty = true; - if (!g_loggedFirstSetProj) - { - g_loggedFirstSetProj = true; - WWDEBUG_SAY(("[BgfxBackend] Set_Transform first PROJ (Matrix4x4) " - "row0=%.3f,%.3f,%.3f,%.3f", - m[0][0], m[0][1], m[0][2], m[0][3])); - } break; default: break; @@ -2360,24 +2588,10 @@ void BgfxBackend::Set_Transform(TransformKind transform, const Matrix3D & m) { case RB_TRANSFORM_WORLD: W3DMatrix3DToBgfx(m, g_bgfxWorld); - if (!g_loggedFirstSetWorld) - { - g_loggedFirstSetWorld = true; - WWDEBUG_SAY(("[BgfxBackend] Set_Transform first WORLD (Matrix3D) " - "row0=%.3f,%.3f,%.3f,%.3f", - m[0][0], m[0][1], m[0][2], m[0][3])); - } break; case RB_TRANSFORM_VIEW: W3DMatrix3DToBgfx(m, g_bgfxView); g_bgfxViewProjDirty = true; - if (!g_loggedFirstSetView) - { - g_loggedFirstSetView = true; - WWDEBUG_SAY(("[BgfxBackend] Set_Transform first VIEW (Matrix3D) " - "row0=%.3f,%.3f,%.3f,%.3f", - m[0][0], m[0][1], m[0][2], m[0][3])); - } break; default: break; @@ -2409,39 +2623,6 @@ void BgfxBackend::Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & matrix, ApplyPopupAspectCorrection(g_bgfxProj); g_bgfxViewProjDirty = true; - // View 2's projection is now set at End_Scene time from the saved - // sort-flush projection (g_bgfxSortProj). This avoids the issue - // where later Set_Projection calls (water, shadows) overwrite view - // 2's projection with a tiny near-field frustum. - if (!g_loggedFirstSetProj) - { - g_loggedFirstSetProj = true; - WWDEBUG_SAY(("[BgfxBackend] Set_Projection_Transform_With_Z_Bias first call " - "row0=%.3f,%.3f,%.3f,%.3f", - matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3])); - // Dump full bgfx column-major projection for Z debugging - WWDEBUG_SAY(("[BgfxBackend] PROJ bgfx col-major: " - "col0=%.4f,%.4f,%.4f,%.4f " - "col1=%.4f,%.4f,%.4f,%.4f " - "col2=%.4f,%.4f,%.4f,%.4f " - "col3=%.4f,%.4f,%.4f,%.4f", - g_bgfxProj[0], g_bgfxProj[1], g_bgfxProj[2], g_bgfxProj[3], - g_bgfxProj[4], g_bgfxProj[5], g_bgfxProj[6], g_bgfxProj[7], - g_bgfxProj[8], g_bgfxProj[9], g_bgfxProj[10], g_bgfxProj[11], - g_bgfxProj[12], g_bgfxProj[13], g_bgfxProj[14], g_bgfxProj[15])); - // Manual clip-space computation for a known sort vertex - // v = (-170.29, -5.09, -499.62, 1.0) - float vx=-170.29f, vy=-5.09f, vz=-499.62f, vw=1.0f; - float cx = g_bgfxProj[0]*vx + g_bgfxProj[4]*vy + g_bgfxProj[8]*vz + g_bgfxProj[12]*vw; - float cy = g_bgfxProj[1]*vx + g_bgfxProj[5]*vy + g_bgfxProj[9]*vz + g_bgfxProj[13]*vw; - float cz = g_bgfxProj[2]*vx + g_bgfxProj[6]*vy + g_bgfxProj[10]*vz + g_bgfxProj[14]*vw; - float cw = g_bgfxProj[3]*vx + g_bgfxProj[7]*vy + g_bgfxProj[11]*vz + g_bgfxProj[15]*vw; - WWDEBUG_SAY(("[BgfxBackend] PROJ TEST: clip=%.2f,%.2f,%.2f,%.2f ndc=%.4f,%.4f,%.4f", - cx, cy, cz, cw, - cw != 0.0f ? cx/cw : 0.0f, - cw != 0.0f ? cy/cw : 0.0f, - cw != 0.0f ? cz/cw : 0.0f)); - } } // Lighting and fog (Set_Light, Set_Ambient, Get_Ambient, Set_Fog, @@ -2470,6 +2651,17 @@ void SubmitEngineDraw(unsigned short start_index, unsigned short min_vertex_index, unsigned short vertex_count) { + if (g_suppressBgfxDraw) + { + return; + } + // Detect when the engine has restored the back buffer. The water/shadow + // code calls DX8Wrapper::Set_Render_Target(nullptr) directly, bypassing + // g_renderBackend. Poll the DX8 state to keep g_renderToTexture in sync. + if (g_renderToTexture && !DX8Wrapper::Is_Render_To_Texture()) + { + g_renderToTexture = false; + } if (!g_bgfxInitialized) { return; @@ -2490,14 +2682,34 @@ void SubmitEngineDraw(unsigned short start_index, // init and are refreshed by Set_Projection_Transform_With_Z_Bias, // so it never needs a per-submit setViewTransform - only view 1 // (the opaque view) uses the dirty flag. - const bgfx::ViewId submitView = g_inSortFlush ? kBgfxEngineSortView : kBgfxEngineView; + bgfx::ViewId submitView; + if (g_renderToTexture) + { + submitView = kBgfxRTTView; + } + else if (g_waterOverrideActive) + { + submitView = kBgfxWaterView; + } + else if (g_effectOverlayActive) + { + submitView = kBgfxEffectOverlayView; + } + else if (g_inSortFlush) + { + submitView = kBgfxEngineSortView; + } + else + { + submitView = kBgfxEngineView; + } const float * worldMtx = g_inSortFlush ? g_bgfxSortWorld : g_bgfxWorld; // Push the engine view+projection when they change. setViewTransform // applies until the next change so we do not need to call it per // submit, only when the engine has updated either matrix. Sort view // draws never touch g_bgfxView so the opaque view is never stomped. - if (!g_inSortFlush && g_bgfxViewProjDirty) + if (!g_inSortFlush && !g_renderToTexture && g_bgfxViewProjDirty) { // Capture the camera view+proj at the first opaque draw of each // frame. Later Set_Projection calls (water, shadows, sneak attack) @@ -2512,6 +2724,12 @@ void SubmitEngineDraw(unsigned short start_index, bgfx::setViewTransform(kBgfxEngineView, g_bgfxView, g_bgfxProj); g_bgfxViewProjDirty = false; } + // During RTT, push the current (reflected/shadow) view+proj to the RTT view. + if (g_renderToTexture && g_bgfxViewProjDirty) + { + bgfx::setViewTransform(kBgfxRTTView, g_bgfxView, g_bgfxProj); + g_bgfxViewProjDirty = false; + } // World matrix is per-submit. bgfx consumes the value at submit time // and resets the per-draw transform after each submit. @@ -2589,7 +2807,6 @@ void SubmitEngineDraw(unsigned short start_index, // neighbor mip selection, breaking mipmap smoothing and making // distant terrain patches look blocky. Let bgfx pick the best // filter it can for each texture. - const uint32_t kSamplerFlags = 0; if (bgfx::isValid(g_sTex0)) { const bgfx::TextureHandle bound = @@ -2598,7 +2815,7 @@ void SubmitEngineDraw(unsigned short start_index, : g_defaultWhiteTexture; if (bgfx::isValid(bound)) { - bgfx::setTexture(0, g_sTex0, bound, kSamplerFlags); + bgfx::setTexture(0, g_sTex0, bound, g_currentSamplerFlags0); } } if (bgfx::isValid(g_sTex1)) @@ -2609,7 +2826,7 @@ void SubmitEngineDraw(unsigned short start_index, : g_defaultWhiteTexture; if (bgfx::isValid(bound)) { - bgfx::setTexture(1, g_sTex1, bound, kSamplerFlags); + bgfx::setTexture(1, g_sTex1, bound, g_currentSamplerFlags1); } } if (bgfx::isValid(g_sTex2)) @@ -2620,7 +2837,7 @@ void SubmitEngineDraw(unsigned short start_index, : g_defaultWhiteTexture; if (bgfx::isValid(bound)) { - bgfx::setTexture(2, g_sTex2, bound, kSamplerFlags); + bgfx::setTexture(2, g_sTex2, bound, g_currentSamplerFlags2); } } if (bgfx::isValid(g_sTex3)) @@ -2631,7 +2848,7 @@ void SubmitEngineDraw(unsigned short start_index, : g_defaultWhiteTexture; if (bgfx::isValid(bound)) { - bgfx::setTexture(3, g_sTex3, bound, kSamplerFlags); + bgfx::setTexture(3, g_sTex3, bound, g_currentSamplerFlags3); } } @@ -2659,9 +2876,11 @@ void SubmitEngineDraw(unsigned short start_index, float atestParams[4] = { g_currentAtestRef, 0.0f, 0.0f, 0.0f }; bgfx::setUniform(g_uAtestParams, atestParams); } - // Read the CURRENT D3D light state directly from DX8Wrapper's - // render_state. This captures lights regardless of how they were set - // (Set_Light_Environment, Set_DX8_Light, or game-level light setup). + // Read current D3D light state per-draw. Set_Light_Environment and + // Set_Ambient capture some paths, but many callers set lights via + // DX8Wrapper::Set_DX8_Light or Apply_Render_State_Changes directly, + // bypassing g_renderBackend. Reading the device state here ensures + // bgfx always has the correct lights regardless of how they were set. { RenderStateStruct rs; DX8Wrapper::Get_Render_State(rs); @@ -2670,8 +2889,6 @@ void SubmitEngineDraw(unsigned short start_index, if (rs.LightEnable[li]) { const D3DLIGHT8 & dl = rs.Lights[li]; - // D3D8 Direction points FROM light toward surface. - // D3D internally negates for N.L. We must negate too. g_currentLightDirs[li][0] = -dl.Direction.x; g_currentLightDirs[li][1] = -dl.Direction.y; g_currentLightDirs[li][2] = -dl.Direction.z; @@ -2686,22 +2903,7 @@ void SubmitEngineDraw(unsigned short start_index, g_currentLightDirs[li][3] = 0.0f; } } - // Read scene ambient from D3DRS_AMBIENT (packed D3DCOLOR = ARGB) const unsigned ambientColor = DX8Wrapper::Get_DX8_Render_State(D3DRS_AMBIENT); - static bool s_loggedLightRead = false; - if (!s_loggedLightRead && rs.LightEnable[0]) - { - s_loggedLightRead = true; - WWDEBUG_SAY(("[BgfxBackend] LIGHT READ: amb=0x%08x lights=%d%d%d%d " - "dir0=%.2f,%.2f,%.2f dif0=%.2f,%.2f,%.2f", - ambientColor, - int(rs.LightEnable[0]), int(rs.LightEnable[1]), - int(rs.LightEnable[2]), int(rs.LightEnable[3]), - rs.Lights[0].Direction.x, rs.Lights[0].Direction.y, - rs.Lights[0].Direction.z, - rs.Lights[0].Diffuse.r, rs.Lights[0].Diffuse.g, - rs.Lights[0].Diffuse.b)); - } g_currentSceneAmbient[0] = ((ambientColor >> 16) & 0xFF) / 255.0f; g_currentSceneAmbient[1] = ((ambientColor >> 8) & 0xFF) / 255.0f; g_currentSceneAmbient[2] = ((ambientColor >> 0) & 0xFF) / 255.0f; @@ -2715,51 +2917,53 @@ void SubmitEngineDraw(unsigned short start_index, if (bgfx::isValid(g_uLightingEnabled)) bgfx::setUniform(g_uLightingEnabled, g_currentLightingEnabled); - // Use the cached state if it was built; otherwise fall back to a - // sane default that at least lets the draw run. Enable MSAA on all - // draws so the 4x MSAA framebuffer actually smooths edges. + if (bgfx::isValid(g_uTexcoordSelect)) + bgfx::setUniform(g_uTexcoordSelect, g_currentTexcoordSelect); + uint64_t state = (g_currentBgfxState != 0) ? g_currentBgfxState : (BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); state |= BGFX_STATE_MSAA; - // Sort view depth handling: particles are pre-transformed to view - // space with identity world/view, so their Z values don't match the - // opaque depth buffer — they need DEPTH_TEST_ALWAYS. But mesh-based - // sorted draws (water, rotors) have real world transforms and should - // use normal depth testing to avoid drawing on top of opaque geometry. - // Distinguish by checking if the sort world matrix is identity - // (particles) vs non-identity (meshes). - if (submitView == kBgfxEngineSortView) - { - const bool isIdentityWorld = - g_bgfxSortWorld[0] == 1.0f && g_bgfxSortWorld[5] == 1.0f && - g_bgfxSortWorld[10] == 1.0f && g_bgfxSortWorld[15] == 1.0f && - g_bgfxSortWorld[12] == 0.0f && g_bgfxSortWorld[13] == 0.0f && - g_bgfxSortWorld[14] == 0.0f; - if (isIdentityWorld) + // Apply post-ShaderClass blend override (terrain blend, W3DCustomEdging, etc.) + if (g_blendOverrideActive) + { + state &= ~BGFX_STATE_BLEND_MASK; + state |= g_blendOverrideBits; + } + // Apply post-ShaderClass alpha test override + if (g_atestOverrideActive) + { + if (bgfx::isValid(g_uAtestParams)) { - // Particles: view-space positions, skip depth test - state &= ~BGFX_STATE_DEPTH_TEST_MASK; - state |= BGFX_STATE_DEPTH_TEST_ALWAYS; + float atestParams[4] = { g_atestOverrideRef, 0.0f, 0.0f, 0.0f }; + bgfx::setUniform(g_uAtestParams, atestParams); } - // else: mesh-based sorted draw, use the shader's depth compare } - bgfx::setState(state); + // Sort view uses the same projection as the opaque view, so particle + // Z values (in view space) produce the same depth as opaque geometry + // at the same world position. Use the shader's normal depth compare + // (typically LEQUAL) for correct depth testing against opaque geometry. + // No DEPTH_TEST_ALWAYS override needed. - bgfx::submit(submitView, g_currentBgfxProgram); + // Apply color write mask override from Set_Color_Write_Enable. + // This handles both alpha-only writes (shoreline depth gradient) + // and RGB-only writes (terrain with alpha writes disabled). + if (g_colorWriteOverride >= 0) + { + state &= ~(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); + state |= static_cast(g_colorWriteOverride); + } - if (!g_loggedFirstBgfxSubmit) + if (g_waterOverrideActive) { - g_loggedFirstBgfxSubmit = true; - WWDEBUG_SAY(("[BgfxBackend] First engine bgfx::submit " - "(view=%u start=%u polys=%u min_v=%u count=%u state=0x%016llx)", - static_cast(submitView), - start_index, polygon_count, - min_vertex_index, vertex_count, - static_cast(state))); + g_waterOverrideActive = false; } + + bgfx::setState(state); + + bgfx::submit(submitView, g_currentBgfxProgram); } } @@ -2770,13 +2974,6 @@ void BgfxBackend::Draw_Triangles(unsigned short start_index, { DX8Backend::Draw_Triangles(start_index, polygon_count, min_vertex_index, vertex_count); - if (!g_loggedFirstDrawTriangles) - { - g_loggedFirstDrawTriangles = true; - WWDEBUG_SAY(("[BgfxBackend] Draw_Triangles first call (polys=%u verts=%u)", - polygon_count, vertex_count)); - } - // If DX8Wrapper::Draw_Sorting_IB_VB already submitted // the draw with correctly remapped args against its internal dynamic // buffers, skip the outer submit. @@ -2796,12 +2993,6 @@ void BgfxBackend::Draw_Triangles(unsigned int buffer_type, { DX8Backend::Draw_Triangles(buffer_type, start_index, polygon_count, min_vertex_index, vertex_count); - if (!g_loggedFirstDrawTriangles) - { - g_loggedFirstDrawTriangles = true; - WWDEBUG_SAY(("[BgfxBackend] Draw_Triangles first call typed (polys=%u verts=%u)", - polygon_count, vertex_count)); - } if (g_skipNextSubmitEngineDraw) { g_skipNextSubmitEngineDraw = false; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h index 18cd8ddf4c8..6d3a0b4a5ae 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h @@ -117,6 +117,17 @@ class BgfxBackend : public DX8Backend virtual void Set_Texture(unsigned int stage, TextureBaseClass * texture); virtual void Set_Light_Environment(LightEnvironmentClass * light_env); virtual void Set_Ambient(const Vector3 & color); + virtual void Override_Blend(unsigned srcBlend, unsigned dstBlend); + virtual void Override_Alpha_Test(bool enable, unsigned ref, unsigned func); + virtual void Override_Alpha_Blend_Enable(bool enable); + virtual void Override_Texcoord_Index(unsigned stage, unsigned uvIndex); + virtual void Override_Terrain_Blend(bool enable); + virtual void Override_Material_Opacity(float opacity); + virtual void Begin_Effect_Overlay(); + virtual void End_Effect_Overlay(); + virtual void Set_Color_Write_Enable(bool red, bool green, bool blue, bool alpha); + virtual void Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass * ztexture = nullptr); + virtual void Clear_State_Overrides(); // -- Transforms ----------------------------------------------------------- // @@ -154,6 +165,6 @@ class BgfxBackend : public DX8Backend // Set_Stencil_*, Get_Transform, Set_Light*, Set/Get_Ambient, // Set/Get_Fog*, Set/Get_Light_Environment, Draw_Strip, Set_Vertex_Shader, // Set_Pixel_Shader, *_Constant, Create_Render_Target, - // Set_Render_Target_With_Z, Is_Render_To_Texture, Set/Get_Shadow_Map) + // Is_Render_To_Texture, Set/Get_Shadow_Map) // is inherited from DX8Backend and forwards to DX8Wrapper unchanged. }; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp index 0fce7ce0153..42a59adad7b 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp @@ -540,7 +540,8 @@ void DX8Backend::Set_Pixel_Shader_Constant(int reg, const void * data, int count TextureClass * DX8Backend::Create_Render_Target(int width, int height, WW3DFormat format) { - return DX8Wrapper::Create_Render_Target(width, height, format); + TextureClass * tex = DX8Wrapper::Create_Render_Target(width, height, format); + return tex; } void DX8Backend::Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass * ztexture) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h index 3afdd15daba..fa912c82ee5 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h @@ -401,6 +401,22 @@ class IRenderBackend virtual void Set_Light_Environment(LightEnvironmentClass * light_env) = 0; virtual LightEnvironmentClass * Get_Light_Environment() const = 0; + // Post-ShaderClass render state overrides. The terrain edge blending + // and other systems set D3D blend/alpha-test state AFTER ShaderClass + // applies. These methods let the bgfx backend capture the overrides. + // Empty defaults = forward to DX8Wrapper only (DX8Backend behavior). + virtual void Override_Blend(unsigned srcBlend, unsigned dstBlend) {} + virtual void Override_Alpha_Test(bool enable, unsigned ref, unsigned func) {} + virtual void Override_Alpha_Blend_Enable(bool enable) {} + virtual void Override_Texcoord_Index(unsigned stage, unsigned uvIndex) {} + virtual void Override_Terrain_Blend(bool enable) {} + virtual void Override_Material_Opacity(float opacity) {} + // Route subsequent draws to the sort view instead of the opaque view. + // Used by dazzle/lens-flare effects that need to render on top of water. + virtual void Begin_Effect_Overlay() {} + virtual void End_Effect_Overlay() {} + virtual void Clear_State_Overrides() {} + // ------------------------------------------------------------------------- // Draw calls // ------------------------------------------------------------------------- diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp index 4c859c4e0be..41e27e9bb5a 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp @@ -1734,6 +1734,8 @@ void DX8TextureCategoryClass::Render() //DX8Wrapper::Set_Material(material); //REF_PTR_RELEASE(material); DX8Wrapper::Apply_Render_State_Changes(); + // Override SRCBLEND to DESTCOLOR for multiply mode. DESTBLEND + // stays at whatever ShaderClass set (typically SRCCOLOR). DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_DESTCOLOR); } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h index 52c254a84e4..1f38125f9e2 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h @@ -552,6 +552,7 @@ class DX8Wrapper static const char* Get_DX8_Render_State_Name(D3DRENDERSTATETYPE state); static const char* Get_DX8_Texture_Stage_State_Name(D3DTEXTURESTAGESTATETYPE state); static unsigned Get_DX8_Render_State(D3DRENDERSTATETYPE state) { return RenderStates[state]; } + static unsigned Get_DX8_Texture_Stage_State(unsigned stage, D3DTEXTURESTAGESTATETYPE state) { return TextureStageStates[stage][(unsigned)state]; } // Names of the specific values of render states and texture stage states static void Get_DX8_Texture_Stage_State_Value_Name(StringClass& name, D3DTEXTURESTAGESTATETYPE state, unsigned value); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc index 9c1c93bda2a..530773d999c 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc @@ -1,4 +1,4 @@ -$input v_color0, v_texcoord0, v_normal +$input v_color0, v_texcoord0, v_texcoord1, v_normal #include @@ -14,6 +14,7 @@ uniform vec4 u_lightDirs[4]; // per-light direction (xyz=toward light, w=enab uniform vec4 u_lightColors[4]; // per-light diffuse color (rgb) uniform vec4 u_sceneAmbient; // scene ambient color (rgb) uniform vec4 u_lightingEnabled; // .x > 0.5 = apply N.L lighting; else vertex is pre-lit +uniform vec4 u_texcoordSelect; // .x > 0.5 = use v_texcoord1 for stage 0 sampling // TSS operation IDs (must match BgfxBackend.cpp encoding) #define TSS_DISABLE 0.0 @@ -70,11 +71,36 @@ float applyAlphaOp(float op, float arg1, float arg2) void main() { + vec4 diffuse = v_color0; + + // --- Terrain pixel shader path --- + // The D3D8 terrain system uses a hardware pixel shader (terrain.nvp) + // that completely replaces the TSS pipeline: + // tex t0 ; sample tex0 with UV set 0 + // tex t1 ; sample tex1 with UV set 1 + // lrp r0, v0.a, t1, t0 ; mix(t0, t1, vertex_alpha) + // mul r0, r0, v0 ; multiply by diffuse (baked lighting) + if (u_texcoordSelect.y > 0.5) + { + vec4 baseTex = texture2D(s_tex0, v_texcoord0); + vec4 blendTex = texture2D(s_tex1, v_texcoord1); + float blendAlpha = diffuse.a; + vec3 blended = mix(baseTex.rgb, blendTex.rgb, blendAlpha); + vec4 result = vec4(blended * diffuse.rgb, 1.0); + + if (u_atestParams.x > 0.0 && result.a < u_atestParams.x) + { + discard; + } + + gl_FragColor = result; + return; + } + vec4 tex0 = texture2D(s_tex0, v_texcoord0); vec4 tex1 = texture2D(s_tex1, v_texcoord0); vec4 tex2 = texture2D(s_tex2, v_texcoord0); vec4 tex3 = texture2D(s_tex3, v_texcoord0); - vec4 diffuse = v_color0; // --- Stage 0: Primary (texture0 vs diffuse) --- float priColorOp = u_tssOps0.x; @@ -138,8 +164,14 @@ void main() current.a = applyAlphaOp(secAlphaOp, secAArg1, secAArg2); } - // --- Stages 2-3: simple multiply (engine rarely uses independent ops here) --- - current *= tex2 * tex3; + // --- Stages 2-3: only multiply when stage 1 is active (stages 2-3 + // piggyback on the secondary stage enable). When disabled, D3D8 + // skips these stages entirely. Multiplying by stale/unset textures + // would zero out the result (e.g. water draws that only use stage 0). + if (secColorOp > 0.5) + { + current *= tex2 * tex3; + } // --- Lighting --- if (priColorOp > 2.5 && priColorOp < 5.5 && u_lightingEnabled.x > 0.5) @@ -151,8 +183,11 @@ void main() // dividing it out, then apply proper lighting. // Simpler approach: recompute current as tex-only * lighting. vec4 texOnly = tex0; - if (secColorOp > 0.5) texOnly *= tex1; - texOnly *= tex2 * tex3; + if (secColorOp > 0.5) + { + texOnly *= tex1; + texOnly *= tex2 * tex3; + } vec3 nrm = normalize(v_normal); vec3 litColor = u_sceneAmbient.rgb * u_matDiffuse.rgb; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc index 8abd09099e4..25d682f7b8b 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc @@ -1,8 +1,10 @@ vec4 v_color0 : COLOR0 = vec4(1.0, 0.0, 0.0, 1.0); vec2 v_texcoord0 : TEXCOORD0 = vec2(0.0, 0.0); +vec2 v_texcoord1 : TEXCOORD1 = vec2(0.0, 0.0); vec3 v_normal : NORMAL = vec3(0.0, 0.0, 1.0); vec3 a_position : POSITION; vec3 a_normal : NORMAL; vec4 a_color0 : COLOR0; vec2 a_texcoord0 : TEXCOORD0; +vec2 a_texcoord1 : TEXCOORD1; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_uber.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_uber.sc index 71b74adda13..25a3a31b237 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_uber.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_uber.sc @@ -1,5 +1,5 @@ -$input a_position, a_normal, a_color0, a_texcoord0 -$output v_color0, v_texcoord0, v_normal +$input a_position, a_normal, a_color0, a_texcoord0, a_texcoord1 +$output v_color0, v_texcoord0, v_texcoord1, v_normal #include @@ -7,12 +7,6 @@ void main() { gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0)); - // D3D8 vertex diffuse is D3DCOLOR (BGRA byte order) but bgfx Color0 - // expects RGBA. Swap R and B to correct the channel mapping. - // Check RGB only (not alpha) for the zero-detection: D3D11 defaults - // missing Color0 to (0,0,0,1), so including alpha makes the sum > 0 - // and the white fallback never fires for meshes without a diffuse - // attribute (infantry, vehicles). vec4 c = a_color0.bgra; if (c.r + c.g + c.b < 0.001) { @@ -20,5 +14,6 @@ void main() } v_color0 = c; v_texcoord0 = a_texcoord0; + v_texcoord1 = a_texcoord1; v_normal = mul(u_model[0], vec4(a_normal, 0.0)).xyz; } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/texproject.cpp b/Core/Libraries/Source/WWVegas/WW3D2/texproject.cpp index c0d3b1fd71c..279abd8d207 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/texproject.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/texproject.cpp @@ -85,6 +85,7 @@ #include "bwrender.h" #include "WW3D2/assetmgr.h" #include "dx8wrapper.h" +#include "RenderBackend.h" // DEBUG DEBUG @@ -1138,7 +1139,7 @@ bool TexProjectClass::Compute_Texture /* ** Set the render target */ - DX8Wrapper::Set_Render_Target_With_Z (rtarget,ztarget); + g_renderBackend->Set_Render_Target_With_Z(rtarget, ztarget); /* ** Set up the camera diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DCustomEdging.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DCustomEdging.cpp index 1e275631658..9dcd32e7250 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DCustomEdging.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DCustomEdging.cpp @@ -367,9 +367,7 @@ void W3DCustomEdging::drawEdging(WorldHeightMap *pMap, Int minX, Int maxX, Int m g_renderBackend->Set_Texture(1,edgeTex); g_renderBackend->Apply_Render_State_Changes(); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF,0x7B); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC,D3DCMP_LESSEQUAL); //pass pixels who's alpha is not zero - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHATESTENABLE, true); //test pixels if transparent(clipped) before rendering. + g_renderBackend->Override_Alpha_Test(true, 0x7B, D3DCMP_LESSEQUAL); g_renderBackend->Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); g_renderBackend->Set_Texture(0,edgeTex); @@ -377,9 +375,7 @@ void W3DCustomEdging::drawEdging(WorldHeightMap *pMap, Int minX, Int maxX, Int m // Draw the custom edge. g_renderBackend->Apply_Render_State_Changes(); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF,0x84); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC,D3DCMP_GREATEREQUAL); //pass pixels who's alpha is not zero - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHATESTENABLE, true); //test pixels if transparent(clipped) before rendering. + g_renderBackend->Override_Alpha_Test(true, 0x84, D3DCMP_GREATEREQUAL); g_renderBackend->Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); #if 0 // Dumps out unmasked data. @@ -407,12 +403,8 @@ void W3DCustomEdging::drawEdging(WorldHeightMap *pMap, Int minX, Int maxX, Int m DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2 ); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXCOORDINDEX, 1 ); #endif - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF,0x80); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC,D3DCMP_NOTEQUAL); //pass pixels who's alpha is not zero - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHATESTENABLE, true); //test pixels if transparent(clipped) before rendering. - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,true); - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_DESTCOLOR); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_ZERO); + g_renderBackend->Override_Alpha_Test(true, 0x80, D3DCMP_NOTEQUAL); + g_renderBackend->Override_Blend(D3DBLEND_DESTCOLOR, D3DBLEND_ZERO); g_renderBackend->Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); } if (noiseTexture) { @@ -422,12 +414,8 @@ void W3DCustomEdging::drawEdging(WorldHeightMap *pMap, Int minX, Int maxX, Int m g_renderBackend->Set_Texture(1,edgeTex); g_renderBackend->Apply_Render_State_Changes(); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF,0x80); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC,D3DCMP_NOTEQUAL); //pass pixels who's alpha is not zero - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHATESTENABLE, true); //test pixels if transparent(clipped) before rendering. - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,true); - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_DESTCOLOR); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,D3DBLEND_ZERO); + g_renderBackend->Override_Alpha_Test(true, 0x80, D3DCMP_NOTEQUAL); + g_renderBackend->Override_Blend(D3DBLEND_DESTCOLOR, D3DBLEND_ZERO); g_renderBackend->Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); } } diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp index ff0d471f2d3..f1195fb4301 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp @@ -1225,6 +1225,11 @@ void DazzleRenderObjClass::Render_Dazzle(CameraClass* camera) g_renderBackend->Set_View_Identity(); g_renderBackend->Set_Transform(RB_TRANSFORM_PROJECTION,Matrix4x4(true)); + // Route dazzle draws to the sort view so they render AFTER water + // and other overlay effects. Otherwise they land on the opaque view + // and get hidden behind the DESTALPHA water pass. + g_renderBackend->Begin_Effect_Overlay(); + if (halo_poly_count) { g_renderBackend->Set_Index_Buffer(ib_access, dazzle_vertex_count); g_renderBackend->Set_Shader(default_halo_shader); @@ -1246,6 +1251,8 @@ void DazzleRenderObjClass::Render_Dazzle(CameraClass* camera) g_renderBackend->Draw_Triangles(0, lensflare_poly_count, 0, vertex_count); } + g_renderBackend->End_Effect_Overlay(); + g_renderBackend->Set_Transform(RB_TRANSFORM_PROJECTION,old_projection_transform); g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW,old_view_transform); g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,old_world_transform); From 64923c40d61af596bf7a8cbdd383a88e5e3c3367 Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 21 Apr 2026 14:02:47 -0400 Subject: [PATCH 051/523] feat(bgfx): port Trees.nvv vertex shader for grass sway through bgfx tree program (cherry picked from commit 4af5445265d597bc94f5beb34652f8fbd9651af2) --- .../W3DDevice/GameClient/W3DTreeBuffer.cpp | 33 +++++++ .../Source/WWVegas/WW3D2/BgfxBackend.cpp | 90 ++++++++++++++++++- .../Source/WWVegas/WW3D2/BgfxBackend.h | 6 ++ .../Source/WWVegas/WW3D2/CMakeLists.txt | 4 + .../Source/WWVegas/WW3D2/IRenderBackend.h | 14 +++ .../Source/WWVegas/WW3D2/shaders/vs_trees.sc | 52 +++++++++++ 6 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_trees.sc diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTreeBuffer.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTreeBuffer.cpp index bd31e2d8516..96ebe7e7f4c 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTreeBuffer.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTreeBuffer.cpp @@ -1693,6 +1693,38 @@ void W3DTreeBuffer::drawTrees(CameraClass * camera, RefRenderObjListIterator *pD } g_renderBackend->Set_Vertex_Shader(m_dwTreeVertexShader); + + // TheSuperHackers @refactor bobtista 14/04/2026 Push + // the same constants we just gave the DX8 vertex shader into + // the bgfx tree program. swayTable[0] is the no-sway slot + // (matches DX8 c8); [1..MAX_SWAY_TYPES] are the per-wave + // offsets (DX8 c9..c8+MAX_SWAY_TYPES). DX8Backend ignores this. + { + float swayTable[11][4]; + swayTable[0][0] = 0.0f; swayTable[0][1] = 0.0f; + swayTable[0][2] = 0.0f; swayTable[0][3] = 0.0f; + for (i = 0; i < MAX_SWAY_TYPES; ++i) { + swayTable[i + 1][0] = swayFactor[i].X; + swayTable[i + 1][1] = swayFactor[i].Y; + swayTable[i + 1][2] = swayFactor[i].Z; + swayTable[i + 1][3] = 0.0f; + } + float shroudOffsetVec[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float shroudScaleVec[4] = { 0.0f, 0.0f, 1.0f, 1.0f }; + W3DShroud * shroud2 = TheTerrainRenderObject + ? TheTerrainRenderObject->getShroud() : nullptr; + if (shroud2 != nullptr) { + const Real cw = shroud2->getCellWidth(); + const Real ch = shroud2->getCellHeight(); + shroudOffsetVec[0] = -(float)shroud2->getDrawOriginX() + cw; + shroudOffsetVec[1] = -(float)shroud2->getDrawOriginY() + ch; + shroudScaleVec[0] = 1.0f / (cw * shroud2->getTextureWidth()); + shroudScaleVec[1] = 1.0f / (ch * shroud2->getTextureHeight()); + } + g_renderBackend->Set_Tree_Shader_Constants(swayTable, shroudOffsetVec, shroudScaleVec); + g_renderBackend->Set_Tree_Vertex_Shader_Active(true); + } + #if 0 g_renderBackend->Set_Pixel_Shader(m_dwTreePixelShader); // a.c. 6/16 - allow switching between normal and 2X mode for terrain @@ -1727,6 +1759,7 @@ void W3DTreeBuffer::drawTrees(CameraClass * camera, RefRenderObjListIterator *pD g_renderBackend->Set_Vertex_Shader(DX8_FVF_XYZNDUV1); g_renderBackend->Set_Pixel_Shader(0); + g_renderBackend->Set_Tree_Vertex_Shader_Active(false); g_renderBackend->Invalidate_Cached_Render_States(); //code above mucks around with W3D states so make sure we reset } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp index a1ba10b9a43..d3067ef681f 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -77,6 +77,7 @@ // Single program handles all TSS combinations via uniforms. Replaces the // the earlier per-preset shader pairs. #include "vs_uber_dx11.bin.h" +#include "vs_trees_dx11.bin.h" #include "fs_uber_dx11.bin.h" namespace @@ -114,6 +115,20 @@ bgfx::ProgramHandle g_passthroughProgram = BGFX_INVALID_HANDLE; // program. Single program handles all TSS combinations via uniforms. bgfx::ProgramHandle g_uberProgram = BGFX_INVALID_HANDLE; +// TheSuperHackers @refactor bobtista 14/04/2026 tree / grass +// vertex shader program (port of Trees.nvv). Uses vs_trees + fs_uber. +// Activated by Set_Tree_Vertex_Shader_Active when W3DTreeBuffer wants +// to render swaying grass; reverts to g_uberProgram otherwise. +bgfx::ProgramHandle g_treeProgram = BGFX_INVALID_HANDLE; +bgfx::UniformHandle g_uSwayTable = BGFX_INVALID_HANDLE; +bgfx::UniformHandle g_uShroudOffset = BGFX_INVALID_HANDLE; +bgfx::UniformHandle g_uShroudScale = BGFX_INVALID_HANDLE; +// Sway table: 11 entries (no-sway at index 0, MAX_SWAY_TYPES=10 active). +float g_currentSwayTable[11][4] = {{0}}; +float g_currentShroudOffset[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; +float g_currentShroudScale[4] = { 0.0f, 0.0f, 1.0f, 1.0f }; +bool g_treeShaderActive = false; + // Sampler uniform shared by all textured fragment shaders. Bound to stage 0. bgfx::UniformHandle g_sTex0 = BGFX_INVALID_HANDLE; bgfx::UniformHandle g_sTex1 = BGFX_INVALID_HANDLE; @@ -1398,6 +1413,24 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) WWDEBUG_SAY(("[BgfxBackend] uber shader createShader FAILED.")); } + // Tree/grass sway vertex shader program. Reuses fs_uber. + const bgfx::Memory * vsTreesMem = bgfx::makeRef(vs_trees_dx11, sizeof(vs_trees_dx11)); + const bgfx::Memory * fsUberMem2 = bgfx::makeRef(fs_uber_dx11, sizeof(fs_uber_dx11)); + bgfx::ShaderHandle vsTrees = bgfx::createShader(vsTreesMem); + bgfx::ShaderHandle fsUber2 = bgfx::createShader(fsUberMem2); + if (bgfx::isValid(vsTrees) && bgfx::isValid(fsUber2)) + { + bgfx::setName(vsTrees, "vs_trees"); + g_treeProgram = bgfx::createProgram(vsTrees, fsUber2, true); + } + else + { + WWDEBUG_SAY(("[BgfxBackend] tree shader createShader FAILED.")); + } + g_uSwayTable = bgfx::createUniform("u_swayTable", bgfx::UniformType::Vec4, 11); + g_uShroudOffset = bgfx::createUniform("u_shroudOffset", bgfx::UniformType::Vec4); + g_uShroudScale = bgfx::createUniform("u_shroudScale", bgfx::UniformType::Vec4); + const bgfx::RendererType::Enum selected = bgfx::getRendererType(); const char * rendererName = bgfx::getRendererName(selected); const bgfx::Caps * caps = bgfx::getCaps(); @@ -1434,6 +1467,26 @@ void BgfxBackend::Shutdown() bgfx::destroy(g_uberProgram); g_uberProgram = BGFX_INVALID_HANDLE; } + if (bgfx::isValid(g_treeProgram)) + { + bgfx::destroy(g_treeProgram); + g_treeProgram = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(g_uSwayTable)) + { + bgfx::destroy(g_uSwayTable); + g_uSwayTable = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(g_uShroudOffset)) + { + bgfx::destroy(g_uShroudOffset); + g_uShroudOffset = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(g_uShroudScale)) + { + bgfx::destroy(g_uShroudScale); + g_uShroudScale = BGFX_INVALID_HANDLE; + } if (bgfx::isValid(g_sTex0)) { bgfx::destroy(g_sTex0); @@ -2281,6 +2334,13 @@ void BgfxBackend::Set_Material(const VertexMaterialClass * material) g_currentMatDiffuse[1] = 1.0f; g_currentMatDiffuse[2] = 1.0f; g_currentMatDiffuse[3] = 1.0f; + // Null material means no material-driven lighting. Dazzle and + // similar effect overlays call Set_Material(nullptr) and bake + // their per-vertex intensity into diffuse.rgb; the lit path in + // fs_uber would ignore that baked color and output tex * lit, + // producing bright flashes where the dazzle should be invisible. + // Force the unlit path so vertex diffuse modulates the output. + g_currentLightingEnabled[0] = 0.0f; } } @@ -2446,6 +2506,20 @@ void BgfxBackend::End_Effect_Overlay() g_effectOverlayActive = false; } +void BgfxBackend::Set_Tree_Shader_Constants(const float swayTable[11][4], + const float shroudOffset[4], + const float shroudScale[4]) +{ + std::memcpy(g_currentSwayTable, swayTable, sizeof(g_currentSwayTable)); + std::memcpy(g_currentShroudOffset, shroudOffset, sizeof(g_currentShroudOffset)); + std::memcpy(g_currentShroudScale, shroudScale, sizeof(g_currentShroudScale)); +} + +void BgfxBackend::Set_Tree_Vertex_Shader_Active(bool active) +{ + g_treeShaderActive = active; +} + void BgfxBackend::Set_Color_Write_Enable(bool red, bool green, bool blue, bool alpha) { DX8Backend::Set_Color_Write_Enable(red, green, blue, alpha); @@ -2963,7 +3037,21 @@ void SubmitEngineDraw(unsigned short start_index, bgfx::setState(state); - bgfx::submit(submitView, g_currentBgfxProgram); + // The tree / grass sway shader takes over the program slot + // and uploads its own constants when active. Otherwise fall back + // to whatever ShaderClass picked (g_currentBgfxProgram). + bgfx::ProgramHandle program = g_currentBgfxProgram; + if (g_treeShaderActive && bgfx::isValid(g_treeProgram)) + { + program = g_treeProgram; + if (bgfx::isValid(g_uSwayTable)) + bgfx::setUniform(g_uSwayTable, g_currentSwayTable, 11); + if (bgfx::isValid(g_uShroudOffset)) + bgfx::setUniform(g_uShroudOffset, g_currentShroudOffset); + if (bgfx::isValid(g_uShroudScale)) + bgfx::setUniform(g_uShroudScale, g_currentShroudScale); + } + bgfx::submit(submitView, program); } } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h index 6d3a0b4a5ae..70a13e6a77c 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h @@ -125,6 +125,12 @@ class BgfxBackend : public DX8Backend virtual void Override_Material_Opacity(float opacity); virtual void Begin_Effect_Overlay(); virtual void End_Effect_Overlay(); + + // Tree / grass sway shader hooks (see IRenderBackend.h). + virtual void Set_Tree_Shader_Constants(const float swayTable[11][4], + const float shroudOffset[4], + const float shroudScale[4]); + virtual void Set_Tree_Vertex_Shader_Active(bool active); virtual void Set_Color_Write_Enable(bool red, bool green, bool blue, bool alpha); virtual void Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass * ztexture = nullptr); virtual void Clear_State_Overrides(); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt index 4190721f827..4e919451fd3 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt @@ -263,6 +263,10 @@ if(GGC_RENDER_BACKEND STREQUAL "bgfx") # Replaces the earlier per-preset shader pairs. ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_uber.sc) ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_uber.sc) + # TheSuperHackers @refactor bobtista 14/04/2026 ported + # Trees.nvv (grass/blade sway vertex shader). Reuses fs_uber for + # the fragment side. + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_trees.sc) target_link_libraries(corei_ww3d2 INTERFACE ggc_bgfx_shaders) endif() diff --git a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h index fa912c82ee5..6e54c429590 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h @@ -375,6 +375,20 @@ class IRenderBackend virtual void Set_Lighting_Enable(bool enable) = 0; virtual void Set_Texture_Factor(unsigned argb) = 0; + // TheSuperHackers @refactor bobtista 14/04/2026 tree / + // grass sway vertex shader hooks. DX8 backends ignore these (they + // use the real DX8 vertex shader DWORD via Set_Vertex_Shader). bgfx + // backend uses them to drive its ported vs_trees program. Call + // order: Set_Tree_Shader_Constants first (per-frame constants), + // then Set_Tree_Vertex_Shader_Active(true) around the grass draws, + // then Set_Tree_Vertex_Shader_Active(false) after. + // swayTable must have 11 float4 entries: [0] = no-sway (0,0,0,0), + // [1..10] = per-wave offsets. + virtual void Set_Tree_Shader_Constants(const float swayTable[11][4], + const float shroudOffset[4], + const float shroudScale[4]) {} + virtual void Set_Tree_Vertex_Shader_Active(bool active) {} + // ------------------------------------------------------------------------- // Transforms // ------------------------------------------------------------------------- diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_trees.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_trees.sc new file mode 100644 index 00000000000..02efa23f70d --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_trees.sc @@ -0,0 +1,52 @@ +$input a_position, a_normal, a_color0, a_texcoord0 +$output v_color0, v_texcoord0, v_texcoord1, v_normal + +#include + +// TheSuperHackers @refactor bobtista 14/04/2026 port of +// Trees.nvv (DX8 vs_1_1). The engine repurposes the vertex normal +// slot to carry grass/tree animation data: +// a_normal.x = sway table index [0..MAX_SWAY_TYPES] +// a_normal.y = color scale factor (darkens vertex diffuse) +// a_normal.z = base Z coordinate (height=0 point of the blade) +// +// u_swayTable packs c[8..8+MAX_SWAY_TYPES] from the original shader +// (11 entries: index 0 = noSway, indices 1..10 = per-wave offsets). +// u_shroudOffset / u_shroudScale replace c32 / c33 — they compute +// UV1 so the shroud (fog-of-war) texture can be sampled. + +#define MAX_SWAY_TYPES_PLUS1 11 + +uniform vec4 u_swayTable[MAX_SWAY_TYPES_PLUS1]; +uniform vec4 u_shroudOffset; +uniform vec4 u_shroudScale; + +void main() +{ + // Height above the base of the blade — original: r2 = v0 - v1 (zzzw swizzle) + float height = a_position.z - a_normal.z; + + // Pick the sway vector for this blade. int() is safe: caller + // writes small non-negative integer values into the normal's x + // via a D3DCOLOR-style pack, so a_normal.x arrives as a float in + // [0, MAX_SWAY_TYPES]. + int waveIdx = int(a_normal.x + 0.5); + vec4 wave = u_swayTable[waveIdx]; + + // Scale the sway by height and add to original position. Tops + // sway most; bases don't move. + vec3 swayed = a_position + height * wave.xyz; + + gl_Position = mul(u_modelViewProj, vec4(swayed, 1.0)); + + // Original: oD0 = v2 * v1.yyyw (replicate color scale). In bgfx + // the diffuse comes in as BGRA on D3D paths — keep the same + // channel swap the uber shader uses. + v_color0 = a_color0.bgra * a_normal.y; + + v_texcoord0 = a_texcoord0; + // Shroud UV: (v0.xy + c32.xy) * c33.xy. + v_texcoord1 = (a_position.xy + u_shroudOffset.xy) * u_shroudScale.xy; + + v_normal = vec3(0.0, 0.0, 1.0); // grass billboards always face up +} From 591a656bdda4793686f95d1af7bcb9260c1a8825 Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 21 Apr 2026 14:02:47 -0400 Subject: [PATCH 052/523] feat(bgfx): shadow pipeline (stencil, CSM, shroud) and -loadsave CLI save loading (cherry picked from commit edf838ef4410610d90a4ebd0c672d7a6c6bfe16d) --- .../GameLogic/System/GameLogicDispatch.cpp | 2 +- .../W3DDevice/GameClient/Water/W3DWater.cpp | 6 +- .../Source/WWVegas/WW3D2/BgfxBackend.cpp | 1834 ++++++++++++++--- .../Source/WWVegas/WW3D2/BgfxBackend.h | 133 +- .../Source/WWVegas/WW3D2/CMakeLists.txt | 10 + .../Source/WWVegas/WW3D2/DX8Backend.cpp | 5 + .../Source/WWVegas/WW3D2/DX8Backend.h | 1 + .../Source/WWVegas/WW3D2/IRenderBackend.h | 95 + .../Source/WWVegas/WW3D2/dx8indexbuffer.cpp | 4 +- .../Source/WWVegas/WW3D2/dx8indexbuffer.h | 4 +- .../Source/WWVegas/WW3D2/dx8vertexbuffer.cpp | 4 +- .../Source/WWVegas/WW3D2/dx8vertexbuffer.h | 7 +- .../WWVegas/WW3D2/shaders/fs_shadow_apply.sc | 13 + .../WWVegas/WW3D2/shaders/fs_shadow_caster.sc | 11 + .../WWVegas/WW3D2/shaders/fs_shadow_volume.sc | 13 + .../Source/WWVegas/WW3D2/shaders/fs_uber.sc | 62 +- .../WWVegas/WW3D2/shaders/varying.def.sc | 1 + .../WWVegas/WW3D2/shaders/vs_passthrough.sc | 2 +- .../WWVegas/WW3D2/shaders/vs_shadow_apply.sc | 12 + .../WWVegas/WW3D2/shaders/vs_shadow_caster.sc | 16 + .../WWVegas/WW3D2/shaders/vs_shadow_volume.sc | 35 + .../Source/WWVegas/WW3D2/shaders/vs_trees.sc | 5 +- .../Source/WWVegas/WW3D2/shaders/vs_uber.sc | 31 +- .../Source/W3DDevice/GameClient/W3DShroud.cpp | 19 + .../W3DDevice/GameLogic/W3DTerrainLogic.cpp | 7 +- .../GameEngine/Include/Common/GlobalData.h | 1 + .../GameEngine/Source/Common/CommandLine.cpp | 11 + .../GameEngine/Source/Common/GameEngine.cpp | 42 + .../GameEngine/Source/Common/GlobalData.cpp | 1 + .../Source/GameClient/GUI/Shell/Shell.cpp | 4 +- .../Include/W3DDevice/GameClient/W3DShroud.h | 1 + .../GameClient/Shadow/W3DProjectedShadow.cpp | 660 +++--- .../W3DDevice/GameClient/Shadow/W3DShadow.cpp | 6 + .../GameClient/Shadow/W3DVolumetricShadow.cpp | 431 ++-- .../Source/W3DDevice/GameClient/W3DShroud.cpp | 33 +- .../W3DDevice/GameLogic/W3DTerrainLogic.cpp | 7 +- 36 files changed, 2571 insertions(+), 958 deletions(-) create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_shadow_apply.sc create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_shadow_caster.sc create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_shadow_volume.sc create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_shadow_apply.sc create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_shadow_caster.sc create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_shadow_volume.sc diff --git a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index c9c7cbb7602..514315ee6f0 100644 --- a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -290,7 +290,7 @@ void GameLogic::clearGameData( Bool showScoreScreen ) // if(shellGame) - if (TheGlobalData->m_initialFile.isEmpty() == FALSE || m_quitToDesktopAfterMatch) + if (TheGlobalData->m_initialFile.isEmpty() == FALSE || TheGlobalData->m_loadSaveGame.isEmpty() == FALSE || m_quitToDesktopAfterMatch) { TheGameEngine->setQuitting(TRUE); m_quitToDesktopAfterMatch = FALSE; diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp index e3f594a0e7d..453e561cfee 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp @@ -2925,7 +2925,7 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) if (m_riverWaterPixelShader) DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(m_riverWaterPixelShader); DWORD cull; DX8Wrapper::_Get_D3D_Device8()->GetRenderState(D3DRS_CULLMODE, &cull); - DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + g_renderBackend->Set_Cull_Mode(RB_CULL_NONE); @@ -3306,7 +3306,7 @@ void WaterRenderObjClass::drawTrapezoidWater(Vector3 points[4]) DWORD cull; DX8Wrapper::_Get_D3D_Device8()->GetRenderState(D3DRS_CULLMODE, &cull); - DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + g_renderBackend->Set_Cull_Mode(RB_CULL_NONE); { g_renderBackend->Draw_Triangles( 0,rectangleCount*2, 0, (rectangleCount+1)*2); @@ -3346,7 +3346,7 @@ void WaterRenderObjClass::drawTrapezoidWater(Vector3 points[4]) { //do second pass to apply the shroud on water plane for cards that can't do it in main pass. W3DShaderManager::setTexture(0,TheTerrainRenderObject->getShroud()->getShroudTexture()); W3DShaderManager::setShader(W3DShaderManager::ST_SHROUD_TEXTURE, 0); - DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + g_renderBackend->Set_Cull_Mode(RB_CULL_NONE); //Shroud shader uses z-compare of EQUAL which wouldn't work on water because it doesn't //write to the zbuffer. Change to LESSEQUAL. DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp index d3067ef681f..6672bc37ccb 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -55,6 +55,7 @@ // catch dependency problems. #include #include +#include // TheSuperHackers @refactor bobtista 11/04/2026 BgfxBackend // now creates its own top-level popup window and hands that HWND to bgfx::init. @@ -80,6 +81,18 @@ #include "vs_trees_dx11.bin.h" #include "fs_uber_dx11.bin.h" +// TheSuperHackers @refactor bobtista 15/04/2026 stencil shadow +// volume program. Vertex shader is a trivial XYZ->clip transform; fragment +// writes nothing visible because color writes are disabled for the pass. +#include "vs_shadow_volume_dx11.bin.h" +#include "fs_shadow_volume_dx11.bin.h" +#include "vs_shadow_apply_dx11.bin.h" +#include "fs_shadow_apply_dx11.bin.h" + +// CSM caster pass shaders. +#include "vs_shadow_caster_dx11.bin.h" +#include "fs_shadow_caster_dx11.bin.h" + namespace { // Anchor a reference to one bgfx symbol so the linker must resolve bgfx @@ -88,6 +101,43 @@ namespace // is misconfigured. [[maybe_unused]] const auto kBgfxLinkAnchor = &bgfx::getCaps; +// TheSuperHackers @refactor bobtista 15/04/2026 bgfx callback +// so fatal errors and debug trace messages land in DebugLogFileD.txt +// instead of silently firing bx::debugBreak. Without this, internal +// bgfx validation failures produce only a raw breakpoint with no text. +class BgfxLoggingCallback : public bgfx::CallbackI +{ +public: + ~BgfxLoggingCallback() override {} + + void fatal(const char * filePath, uint16_t line, bgfx::Fatal::Enum code, const char * str) override + { + WWDEBUG_SAY(("[BgfxBackend] FATAL code=%d at %s:%u: %s", + static_cast(code), filePath ? filePath : "?", line, str ? str : "?")); + } + void traceVargs(const char * filePath, uint16_t line, const char * format, va_list argList) override + { + char buf[512]; + std::vsnprintf(buf, sizeof(buf), format, argList); + // Strip trailing newline for single-line log output. + size_t len = std::strlen(buf); + while (len > 0 && (buf[len-1] == '\n' || buf[len-1] == '\r')) { buf[--len] = '\0'; } + WWDEBUG_SAY(("[bgfx] %s:%u: %s", filePath ? filePath : "?", line, buf)); + } + void profilerBegin(const char *, uint32_t, const char *, uint16_t) override {} + void profilerBeginLiteral(const char *, uint32_t, const char *, uint16_t) override {} + void profilerEnd() override {} + uint32_t cacheReadSize(uint64_t) override { return 0; } + bool cacheRead(uint64_t, void *, uint32_t) override { return false; } + void cacheWrite(uint64_t, const void *, uint32_t) override {} + void screenShot(const char *, uint32_t, uint32_t, uint32_t, bgfx::TextureFormat::Enum, + const void *, uint32_t, bool) override {} + void captureBegin(uint32_t, uint32_t, uint32_t, bgfx::TextureFormat::Enum, bool) override {} + void captureEnd() override {} + void captureFrame(const void *, uint32_t) override {} +}; +BgfxLoggingCallback g_bgfxCallback; + // TheSuperHackers @refactor bobtista 11/04/2026 Tracks // whether bgfx::init has been called successfully, so Begin_Scene / // End_Scene / Shutdown can skip bgfx calls if init was never reached. @@ -120,6 +170,140 @@ bgfx::ProgramHandle g_uberProgram = BGFX_INVALID_HANDLE; // Activated by Set_Tree_Vertex_Shader_Active when W3DTreeBuffer wants // to render swaying grass; reverts to g_uberProgram otherwise. bgfx::ProgramHandle g_treeProgram = BGFX_INVALID_HANDLE; + +// TheSuperHackers @refactor bobtista 15/04/2026 stencil shadow +// volume program + active flag + captured stencil state bits. When active +// the next SubmitEngineDraw uses g_shadowVolumeProgram with color writes +// off and g_shadowStencilState applied. +bgfx::ProgramHandle g_shadowVolumeProgram = BGFX_INVALID_HANDLE; +bgfx::ProgramHandle g_shadowApplyProgram = BGFX_INVALID_HANDLE; +bgfx::UniformHandle g_uShadowColor = BGFX_INVALID_HANDLE; + +// CSM caster pass program + render target. +bgfx::ProgramHandle g_shadowCasterProgram = BGFX_INVALID_HANDLE; +bgfx::FrameBufferHandle g_shadowMapFB = BGFX_INVALID_HANDLE; +bgfx::TextureHandle g_shadowMapDepth = BGFX_INVALID_HANDLE; +// Light-space view+proj for the shadow map view (updated per frame). +float g_shadowLightView[16]; +float g_shadowLightProj[16]; +bool g_shadowLightCaptured = false; +// World-space sun position for shadow casting, set by the engine's +// W3DShadowManager::setLightPosition via Set_Shadow_Light_Position. +// Differs from the N.L shading light (g_currentLightDirs). +float g_shadowSunPosX = 0.0f; +float g_shadowSunPosY = 0.0f; +float g_shadowSunPosZ = 1500.0f; +bool g_shadowSunPosSet = false; +// Uniform + sampler binding point for the main-pass shader to sample +// the shadow map (hooked into fs_uber). +bgfx::UniformHandle g_uShadowLightViewProj = BGFX_INVALID_HANDLE; +bgfx::UniformHandle g_sShadowMap = BGFX_INVALID_HANDLE; +// TheSuperHackers @refactor bobtista 15/04/2026 polygon-offset +// equivalent for stencil shadow volume passes. bgfx has no state bit +// for polygon offset, so the bias is applied in the volume vertex +// shader (vs_shadow_volume.sc). .x is the post-projection Z offset; +// negative values push toward the camera (closer to near plane). +bgfx::UniformHandle g_uShadowBias = BGFX_INVALID_HANDLE; +bool g_shadowVolumeActive = false; +uint32_t g_shadowStencilFront = BGFX_STENCIL_NONE; +uint32_t g_shadowStencilBack = BGFX_STENCIL_NONE; + +// Engine stencil state captured from Set_Stencil_* calls. Encoded lazily +// into g_shadowStencilFront every time a component changes. +bool g_stencilEnabled = false; +uint32_t g_stencilRef = 0; +uint32_t g_stencilReadMask = 0xFF; +uint32_t g_stencilFuncBits = BGFX_STENCIL_TEST_ALWAYS; +uint32_t g_stencilPassOpBits = BGFX_STENCIL_OP_PASS_Z_KEEP; +uint32_t g_stencilFailOpBits = BGFX_STENCIL_OP_FAIL_S_KEEP; +uint32_t g_stencilZFailOpBits = BGFX_STENCIL_OP_FAIL_Z_KEEP; + +// Current engine cull mode (RB_CULL_NONE / CW / CCW). Captured by +// BgfxBackend::Set_Cull_Mode; consumed by the shadow volume submit so +// the two-pass stencil algorithm (CW INCR pass + CCW DECR pass) writes +// stencil counts that don't cancel each other out. +int g_cullModeBits = 0; // 0 = RB_CULL_NONE, 1 = CW, 2 = CCW + +static uint32_t MapCmpFuncToBgfxStencilTest(int f) +{ + switch (f) + { + case 1: return BGFX_STENCIL_TEST_NEVER; + case 2: return BGFX_STENCIL_TEST_LESS; + case 3: return BGFX_STENCIL_TEST_EQUAL; + case 4: return BGFX_STENCIL_TEST_LEQUAL; + case 5: return BGFX_STENCIL_TEST_GREATER; + case 6: return BGFX_STENCIL_TEST_NOTEQUAL; + case 7: return BGFX_STENCIL_TEST_GEQUAL; + case 8: default: return BGFX_STENCIL_TEST_ALWAYS; + } +} + +static uint32_t MapStencilOpToBgfxPassZ(int op) +{ + switch (op) + { + case 1: return BGFX_STENCIL_OP_PASS_Z_KEEP; + case 2: return BGFX_STENCIL_OP_PASS_Z_ZERO; + case 3: return BGFX_STENCIL_OP_PASS_Z_REPLACE; + case 4: return BGFX_STENCIL_OP_PASS_Z_INCRSAT; + case 5: return BGFX_STENCIL_OP_PASS_Z_DECRSAT; + case 6: return BGFX_STENCIL_OP_PASS_Z_INVERT; + case 7: return BGFX_STENCIL_OP_PASS_Z_INCR; + case 8: return BGFX_STENCIL_OP_PASS_Z_DECR; + default: return BGFX_STENCIL_OP_PASS_Z_KEEP; + } +} + +static uint32_t MapStencilOpToBgfxFailS(int op) +{ + switch (op) + { + case 1: return BGFX_STENCIL_OP_FAIL_S_KEEP; + case 2: return BGFX_STENCIL_OP_FAIL_S_ZERO; + case 3: return BGFX_STENCIL_OP_FAIL_S_REPLACE; + case 4: return BGFX_STENCIL_OP_FAIL_S_INCRSAT; + case 5: return BGFX_STENCIL_OP_FAIL_S_DECRSAT; + case 6: return BGFX_STENCIL_OP_FAIL_S_INVERT; + case 7: return BGFX_STENCIL_OP_FAIL_S_INCR; + case 8: return BGFX_STENCIL_OP_FAIL_S_DECR; + default: return BGFX_STENCIL_OP_FAIL_S_KEEP; + } +} + +static uint32_t MapStencilOpToBgfxFailZ(int op) +{ + switch (op) + { + case 1: return BGFX_STENCIL_OP_FAIL_Z_KEEP; + case 2: return BGFX_STENCIL_OP_FAIL_Z_ZERO; + case 3: return BGFX_STENCIL_OP_FAIL_Z_REPLACE; + case 4: return BGFX_STENCIL_OP_FAIL_Z_INCRSAT; + case 5: return BGFX_STENCIL_OP_FAIL_Z_DECRSAT; + case 6: return BGFX_STENCIL_OP_FAIL_Z_INVERT; + case 7: return BGFX_STENCIL_OP_FAIL_Z_INCR; + case 8: return BGFX_STENCIL_OP_FAIL_Z_DECR; + default: return BGFX_STENCIL_OP_FAIL_Z_KEEP; + } +} + +static void UpdateShadowStencilState() +{ + if (!g_stencilEnabled) + { + g_shadowStencilFront = BGFX_STENCIL_NONE; + g_shadowStencilBack = BGFX_STENCIL_NONE; + return; + } + g_shadowStencilFront = g_stencilFuncBits + | BGFX_STENCIL_FUNC_REF(g_stencilRef & 0xFF) + | BGFX_STENCIL_FUNC_RMASK(g_stencilReadMask & 0xFF) + | g_stencilFailOpBits + | g_stencilZFailOpBits + | g_stencilPassOpBits; + g_shadowStencilBack = BGFX_STENCIL_NONE; +} + bgfx::UniformHandle g_uSwayTable = BGFX_INVALID_HANDLE; bgfx::UniformHandle g_uShroudOffset = BGFX_INVALID_HANDLE; bgfx::UniformHandle g_uShroudScale = BGFX_INVALID_HANDLE; @@ -170,6 +354,12 @@ bool g_effectOverlayActive = false; // when it changes D3DTSS_TEXCOORDINDEX to 1 for the blend pass. bgfx::UniformHandle g_uTexcoordSelect = BGFX_INVALID_HANDLE; float g_currentTexcoordSelect[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + +// Shroud pass: the D3D8 path uses D3DTSS_TCI_CAMERASPACEPOSITION to auto- +// generate UVs from camera-space vertex positions, combined with a texture +// transform matrix. bgfx has no equivalent, so we upload the full texture +// matrix and let the vertex shader compute the UVs explicitly. +bgfx::UniformHandle g_uShroudParams = BGFX_INVALID_HANDLE; // .y = terrain blend flag: when > 0, shader does lerp(tex0, tex1, vertex_alpha) // using UV set 0 for tex0 and UV set 1 for tex1 @@ -201,10 +391,6 @@ float g_currentLightColors[4][4] = { }; float g_currentSceneAmbient[4] = { 0.45f, 0.45f, 0.45f, 1.0f }; -// UV animation: texture transform for stage 0. -// Packed as (scaleU, scaleV, offsetU, offsetV). Default = identity (1,1,0,0). -bgfx::UniformHandle g_uTexTransform0 = BGFX_INVALID_HANDLE; -float g_currentTexTransform0[4] = { 1.0f, 1.0f, 0.0f, 0.0f }; // Tracks whether the current material has lighting enabled. // When false, the vertex color contains pre-baked lighting (terrain) // and the fragment shader should NOT apply N.L lighting on top. @@ -767,17 +953,28 @@ bool BuildBgfxLayoutForFVF(const FVFInfoClass & fvf, bgfx::VertexLayout & out) // support. Full-buffer writes (WriteLockClass) also go through the same // dynamic path - created once, updated with bgfx::update as the engine // rewrites the buffer. -std::unordered_map g_vbCache; -std::unordered_map g_ibCache; +// TheSuperHackers @refactor bobtista 15/04/2026 cache entries +// store (handle, num_verts, stride) so we can detect the case where the +// engine destroys a VertexBufferClass and reuses the memory address for +// a new VB with different dimensions — otherwise we'd hand back a stale +// handle and bgfx would truncate writes / crash on staging creation. +struct BgfxVbCacheEntry { + bgfx::DynamicVertexBufferHandle handle; + uint32_t num_verts; + uint32_t stride; +}; +struct BgfxIbCacheEntry { + bgfx::DynamicIndexBufferHandle handle; + uint32_t num_indices; +}; +std::unordered_map g_vbCache; +std::unordered_map g_ibCache; std::unordered_map g_textureCache; // The bgfx texture currently bound to stage 0 by Set_Texture. Used by // SubmitEngineDraw - falls back to g_defaultWhiteTexture if invalid. bgfx::TextureHandle g_currentBgfxTexture0 = BGFX_INVALID_HANDLE; bgfx::TextureHandle g_currentBgfxTexture1 = BGFX_INVALID_HANDLE; -// Track texture names for diagnostics -const char * g_currentTexName0 = ""; -const char * g_currentTexName1 = ""; bgfx::TextureHandle g_currentBgfxTexture2 = BGFX_INVALID_HANDLE; bgfx::TextureHandle g_currentBgfxTexture3 = BGFX_INVALID_HANDLE; @@ -856,6 +1053,7 @@ bool g_bgfxCameraCaptured = false; // the test triangle on view 0. View 0 keeps the test triangle for the // "is bgfx alive" sentinel; view 1 is engine geometry under engine // transforms. Both render to the popup back buffer. +const bgfx::ViewId kBgfxDebugView = 0; const bgfx::ViewId kBgfxEngineView = 1; // TheSuperHackers @refactor bobtista 11/04/2026 dedicated @@ -874,6 +1072,29 @@ const bgfx::ViewId kBgfxWaterView = 4; // through the sort view (which has the camera perspective projection) // re-projects their NDC coords and pushes them off-screen. const bgfx::ViewId kBgfxEffectOverlayView = 5; +// TheSuperHackers @refactor bobtista 15/04/2026 dedicated +// views for stencil shadow volumes + darken-apply pass. Running in +// sequential order on the same framebuffer as view 1 guarantees: +// - Opaque geometry in view 1 has populated the depth buffer before +// view 6 shadow-volume submits execute (the depth-pass algorithm +// requires this; default program+depth sort on view 1 could reorder +// the volume submits ahead of opaque draws that populate depth). +// - The two-pass volume algorithm (CW front INCR, then CCW back DECR) +// executes in submit order so paired increments and decrements land +// on the same stencil bit. +// - The apply pass on view 7 runs after all stencil writes are done. +// No color/depth/stencil clear on these views - they share the engine +// view's attachments and the engine view already cleared them. +const bgfx::ViewId kBgfxShadowVolumeView = 6; +const bgfx::ViewId kBgfxShadowApplyView = 7; +// CSM caster pass view. Depth-only render target, renders +// opaque casters from the sun's perspective into a D24 shadow map. +const bgfx::ViewId kBgfxShadowMapView = 8; +const uint16_t kShadowMapResolution = 4096; +const float kShadowOrthoSize = 1200.0f; +const float kShadowCameraDistance = 2000.0f; +const float kShadowOrthoFarMargin = 1000.0f; +const int kSwayTableEntries = 11; // Render-to-texture state. Set by Set_Render_Target_With_Z, cleared // when the back buffer is restored. SubmitEngineDraw routes to @@ -889,6 +1110,11 @@ bool g_inSortFlush = false; // sortView * sortWorld (in bgfx column-major form) captured from the // engine's render_state by Capture_Sorted_Batch_Transforms. float g_bgfxSortWorld[16]; +// CSM: raw model-to-world matrix for sorted draws, WITHOUT +// the camera view baked in. Used for shadow caster submissions where +// the light's view+proj replaces the camera's. g_bgfxSortWorld has +// model*cameraView which contaminates the light-space transform. +float g_bgfxSortWorldRaw[16]; // TheSuperHackers @refactor bobtista 11/04/2026 Set by // Submit_Sorted_Draw after it emits the bgfx submit for a sorting VB @@ -1107,23 +1333,85 @@ bgfx::TextureHandle EnsureBgfxTexture(TextureBaseClass * tex) return BGFX_INVALID_HANDLE; } - // Compute the byte size of the surface. For uncompressed formats it - // is height * pitch. For DXT it is height/4 * pitch (block-compressed). + // TheSuperHackers @bugfix bobtista 16/04/2026 Compute tightly-packed row + // pitch that bgfx expects and re-pack if the D3D8 locked pitch differs + // (drivers may add row padding for alignment). bgfx asserts that the + // memory size equals its internal storageSize, so we must match exactly. const bool isCompressed = bgfxFmt == bgfx::TextureFormat::BC1 || bgfxFmt == bgfx::TextureFormat::BC2 || bgfxFmt == bgfx::TextureFormat::BC3; - unsigned heightBlocks = desc.Height; + unsigned expectedPitch = 0; + unsigned numRows = 0; if (isCompressed) { - heightBlocks = (desc.Height + 3) / 4; + const unsigned blockWidth = 4; + const unsigned blockHeight = 4; + const unsigned blockSize = (bgfxFmt == bgfx::TextureFormat::BC1) ? 8 : 16; + expectedPitch = ((desc.Width + blockWidth - 1) / blockWidth) * blockSize; + numRows = (desc.Height + blockHeight - 1) / blockHeight; } - const unsigned totalBytes = heightBlocks * static_cast(locked.Pitch); + else + { + unsigned bpp = 0; + switch (bgfxFmt) + { + case bgfx::TextureFormat::BGRA8: bpp = 32; break; + case bgfx::TextureFormat::R5G6B5: bpp = 16; break; + case bgfx::TextureFormat::BGR5A1: bpp = 16; break; + case bgfx::TextureFormat::BGRA4: bpp = 16; break; + case bgfx::TextureFormat::A8: bpp = 8; break; + case bgfx::TextureFormat::R8: bpp = 8; break; + default: bpp = 32; break; + } + expectedPitch = desc.Width * bpp / 8; + numRows = desc.Height; + } + + const unsigned totalBytes = numRows * expectedPitch; + const unsigned srcPitch = static_cast(locked.Pitch); - const bgfx::Memory * mem = bgfx::copy(locked.pBits, totalBytes); + const bgfx::Memory * mem = bgfx::alloc(totalBytes); + if (srcPitch == expectedPitch) + { + std::memcpy(mem->data, locked.pBits, totalBytes); + } + else + { + const unsigned copyPitch = (srcPitch < expectedPitch) ? srcPitch : expectedPitch; + const uint8_t * src = static_cast(locked.pBits); + uint8_t * dst = mem->data; + for (unsigned row = 0; row < numRows; ++row) + { + std::memcpy(dst, src, copyPitch); + if (copyPitch < expectedPitch) + { + std::memset(dst + copyPitch, 0, expectedPitch - copyPitch); + } + src += srcPitch; + dst += expectedPitch; + } + } d3dTex->UnlockRect(0); + { + static int s_texLog = 0; + if (s_texLog++ < 200) + { + TextureClass * t = tex->As_TextureClass(); + WWDEBUG_SAY(("[TEX CREATE #%d] '%s' %ux%u d3dfmt=%d bgfxFmt=%d " + "srcPitch=%u expPitch=%u rows=%u bytes=%u comp=%d", + s_texLog, + t ? (const char*)t->Get_Full_Path() : "?", + desc.Width, desc.Height, + static_cast(desc.Format), + static_cast(bgfxFmt), + srcPitch, expectedPitch, numRows, totalBytes, + isCompressed ? 1 : 0)); + } + } + bgfx::TextureHandle h = bgfx::createTexture2D( static_cast(desc.Width), static_cast(desc.Height), @@ -1139,6 +1427,142 @@ bgfx::TextureHandle EnsureBgfxTexture(TextureBaseClass * tex) bgfx::ProgramHandle g_currentBgfxProgram = BGFX_INVALID_HANDLE; uint64_t g_currentBgfxState = 0; +// TheSuperHackers @refactor bobtista 15/04/2026 CSM light +// transform computation. Called once per frame to build an ortho +// projection from a hardcoded sun direction, fitted to a generous +// world-space box centered on the tactical view. Stores results in +// g_shadowLightView / g_shadowLightProj (bgfx column-major), then +// pushes them to the shadow map view. Not yet tied to the engine's +// actual sun direction — hooked up with the CSM work. +static void UpdateShadowLightTransform() +{ + // The engine's shadow sun position (from W3DShadowManager) is + // normalize(-terrainLightPos) * SUN_DISTANCE_FROM_GROUND (10000). + // Extract the DIRECTION by normalizing, then place the light eye + // at a reasonable distance along that direction from the look-at. + // Read the sun direction from the D3D device's light 0 each frame. + // This ensures the CSM matches the scene lighting even when + // Set_Shadow_Light_Position isn't called (e.g., save game loads + // that restore TOD without going through W3DTerrainLogic::loadMap). + // The D3D light direction points FROM sun TOWARD ground; negate + // to get the direction TOWARD the sun (matching the convention + // used by g_shadowSunPosX/Y/Z from setLightPosition). + float sunX = g_shadowSunPosX; + float sunY = g_shadowSunPosY; + float sunZ = g_shadowSunPosZ; + { + RenderStateStruct rs; + DX8Wrapper::Get_Render_State(rs); + if (rs.LightEnable[0]) + { + const float lx = -rs.Lights[0].Direction.x; + const float ly = -rs.Lights[0].Direction.y; + const float lz = -rs.Lights[0].Direction.z; + const float ll = std::sqrt(lx*lx + ly*ly + lz*lz); + if (ll > 0.001f) + { + sunX = lx * (10000.0f / ll); + sunY = ly * (10000.0f / ll); + sunZ = lz * (10000.0f / ll); + } + } + } + if (sunX == 0.0f && sunY == 0.0f && sunZ == 0.0f) + { + sunX = 500.0f; sunY = 800.0f; sunZ = 1500.0f; + } + const float sunLen = std::sqrt(sunX*sunX + sunY*sunY + sunZ*sunZ); + if (sunLen < 0.001f) return; + // Normalized direction FROM origin TOWARD sun. + const float sdx = sunX / sunLen; + const float sdy = sunY / sunLen; + const float sdz = sunZ / sunLen; + + // Extract camera position from the captured view matrix. + float viewTx = g_bgfxView[12]; + float viewTy = g_bgfxView[13]; + float viewTz = g_bgfxView[14]; + float camPosX = -(g_bgfxView[0]*viewTx + g_bgfxView[1]*viewTy + g_bgfxView[2]*viewTz); + float camPosY = -(g_bgfxView[4]*viewTx + g_bgfxView[5]*viewTy + g_bgfxView[6]*viewTz); + float camPosZ = -(g_bgfxView[8]*viewTx + g_bgfxView[9]*viewTy + g_bgfxView[10]*viewTz); + + // Compute where the camera looks at on the ground plane (Z=0). + // The 3rd row of the bgfx view matrix (column-major: indices + // 2, 6, 10) is the view-space Z axis in world coordinates. + // For this engine (Z-up, DX8 left-handed), it points BACKWARD + // (away from the scene), so negate to get FORWARD (toward scene). + float fwdX = -g_bgfxView[2]; + float fwdY = -g_bgfxView[6]; + float fwdZ = -g_bgfxView[10]; + // Ray-plane intersection: t = -camPosZ / fwdZ + float atX = camPosX; + float atY = camPosY; + const float atZ = 0.0f; + if (std::fabs(fwdZ) > 0.001f) + { + float t = -camPosZ / fwdZ; + if (t > 0.0f && t < 10000.0f) + { + atX = camPosX + fwdX * t; + atY = camPosY + fwdY * t; + } + } + + // Place light eye along the sun direction from the look-at point. + const float eyeX = atX + sdx * kShadowCameraDistance; + const float eyeY = atY + sdy * kShadowCameraDistance; + const float eyeZ = atZ + sdz * kShadowCameraDistance; + const float upX = 0.0f, upY = 0.0f, upZ = 1.0f; + + // Explicit left-handed to match bx::mtxOrtho's D3D convention + // (positive Z = forward, near/far > 0). Right-handed (bx default) + // flips Z, putting the scene at negative view-space Z which falls + // outside the [near, far] clip range → refZ ≈ 1.0 for everything. + bx::mtxLookAt(g_shadowLightView, + bx::Vec3(eyeX, eyeY, eyeZ), + bx::Vec3(atX, atY, atZ), + bx::Vec3(upX, upY, upZ), + bx::Handedness::Left); + + // Ortho in light-space coordinates. The visible scene spans wider + // in light-space than camera-space due to the oblique angle. + // 1200 units with 2048 resolution = ~1.2 units/texel. + const float orthoNear = 1.0f; + const float orthoFar = kShadowCameraDistance + kShadowOrthoFarMargin; + const bgfx::Caps * caps = bgfx::getCaps(); + bx::mtxOrtho(g_shadowLightProj, + -kShadowOrthoSize, +kShadowOrthoSize, + -kShadowOrthoSize, +kShadowOrthoSize, + orthoNear, orthoFar, + 0.0f, + caps ? caps->homogeneousDepth : false); + + // Shadow map stabilization: snap the combined view-projection to + // texel boundaries so the shadow map doesn't sub-pixel-shift as the + // camera pans (causes pixelated wobbling on shadow edges). + // Approach: transform the world origin through view*proj to get its + // shadow-map texel position, round to the nearest integer texel, and + // apply the fractional offset back into the projection matrix. + { + float lightVP[16]; + bx::mtxMul(lightVP, g_shadowLightView, g_shadowLightProj); + // Row-vector: (0,0,0,1) * VP = (VP[12], VP[13], VP[14], VP[15]) + // VP[12..13] are the clip-space position of the world origin. + const float halfRes = static_cast(kShadowMapResolution) * 0.5f; + const float texelX = lightVP[12] * halfRes; + const float texelY = lightVP[13] * halfRes; + const float snappedX = std::round(texelX); + const float snappedY = std::round(texelY); + const float offsetX = (snappedX - texelX) / halfRes; + const float offsetY = (snappedY - texelY) / halfRes; + g_shadowLightProj[12] += offsetX; + g_shadowLightProj[13] += offsetY; + } + + bgfx::setViewTransform(kBgfxShadowMapView, g_shadowLightView, g_shadowLightProj); + g_shadowLightCaptured = true; +} + // Create the bgfx debug popup window. Returns the HWND or nullptr on failure. HWND CreateBgfxDebugWindow() { @@ -1221,12 +1645,13 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) bgfx::Init initArgs; initArgs.type = bgfx::RendererType::Count; // auto-select (D3D11 on Windows) + initArgs.callback = &g_bgfxCallback; initArgs.resolution.width = static_cast(kBgfxWindowWidth); initArgs.resolution.height = static_cast(kBgfxWindowHeight); // enable 4x MSAA on the bgfx framebuffer. Without this, // polygon boundaries between terrain tiles, rock shorelines, and // unit silhouettes alias hard at the popup's lower resolution. - initArgs.resolution.reset = BGFX_RESET_MSAA_X4; + initArgs.resolution.reset = BGFX_RESET_NONE; initArgs.platformData = pd; if (!bgfx::init(initArgs)) @@ -1251,18 +1676,18 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) // is always 1.0, making destination-alpha blending ineffective. bgfx::reset(static_cast(kBgfxWindowWidth), static_cast(kBgfxWindowHeight), - BGFX_RESET_MSAA_X4, + BGFX_RESET_NONE, bgfx::TextureFormat::RGBA8); // Configure view 0 to clear the debug window to a dark teal so it's // visually obvious bgfx is running and alive. View 0 holds the test // triangle (bring-up sentinel). - bgfx::setViewClear(0, + bgfx::setViewClear(kBgfxDebugView, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x1a3b5cff, // dark teal, 0xRRGGBBAA 1.0f, 0); - bgfx::setViewRect(0, 0, 0, + bgfx::setViewRect(kBgfxDebugView, 0, 0, static_cast(kBgfxWindowWidth), static_cast(kBgfxWindowHeight)); @@ -1276,10 +1701,14 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) // (transparent) and the water polygon edge creates a visible zigzag. // The RGB clear is black; terrain overwrites it with its own color. bgfx::setViewClear(kBgfxEngineView, - BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, + BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH | BGFX_CLEAR_STENCIL, 0x000000ff, // Alpha=1.0 matches TransparentWaterMinOpacity=1.0 from INI 1.0f, 0); + // Sequential mode preserves the engine's draw order: terrain + // first, then shadow decals on top at equal depth. Without this, + // Default sort can place decals before terrain → overwritten. + bgfx::setViewMode(kBgfxEngineView, bgfx::ViewMode::Sequential); bgfx::setViewRect(kBgfxEngineView, 0, 0, static_cast(kBgfxWindowWidth), static_cast(kBgfxWindowHeight)); @@ -1315,6 +1744,29 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) bgfx::setViewTransform(kBgfxEffectOverlayView, identityMtx, identityMtx); } + // shadow-volume view. Sequential so the two-pass algorithm + // (front INCR / back DECR) runs in submit order. No clear. View + // transform is pushed per-frame from the engine camera via the + // dirty-flag logic alongside view 1. + bgfx::setViewClear(kBgfxShadowVolumeView, BGFX_CLEAR_NONE, 0, 1.0f, 0); + bgfx::setViewRect(kBgfxShadowVolumeView, 0, 0, + static_cast(kBgfxWindowWidth), + static_cast(kBgfxWindowHeight)); + bgfx::setViewMode(kBgfxShadowVolumeView, bgfx::ViewMode::Sequential); + + // shadow darken apply pass. Sequential, identity transforms + // (the fullscreen quad is authored in clip space). + bgfx::setViewClear(kBgfxShadowApplyView, BGFX_CLEAR_NONE, 0, 1.0f, 0); + bgfx::setViewRect(kBgfxShadowApplyView, 0, 0, + static_cast(kBgfxWindowWidth), + static_cast(kBgfxWindowHeight)); + bgfx::setViewMode(kBgfxShadowApplyView, bgfx::ViewMode::Sequential); + { + float identityMtx[16]; + IdentityMatrix(identityMtx); + bgfx::setViewTransform(kBgfxShadowApplyView, identityMtx, identityMtx); + } + // Default the cached transforms to identity until the engine writes // real values via Set_Transform. This keeps the first few engine // submits well-defined even if they fire before any matrices are @@ -1360,6 +1812,8 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) } else { + if (bgfx::isValid(vsHandle)) bgfx::destroy(vsHandle); + if (bgfx::isValid(fsHandle)) bgfx::destroy(fsHandle); WWDEBUG_SAY(("[BgfxBackend] passthrough shader createShader FAILED.")); } @@ -1376,7 +1830,7 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) g_uSceneAmbient = bgfx::createUniform("u_sceneAmbient", bgfx::UniformType::Vec4); g_uLightingEnabled = bgfx::createUniform("u_lightingEnabled", bgfx::UniformType::Vec4); g_uTexcoordSelect = bgfx::createUniform("u_texcoordSelect", bgfx::UniformType::Vec4); - g_uTexTransform0 = bgfx::createUniform("u_texTransform0", bgfx::UniformType::Vec4); + g_uShroudParams = bgfx::createUniform("u_shroudParams", bgfx::UniformType::Vec4); // Default 1x1 white texture. Used as fallback for missing textures. // Multiplying by white is the identity operation. @@ -1410,6 +1864,8 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) } else { + if (bgfx::isValid(vsUber)) bgfx::destroy(vsUber); + if (bgfx::isValid(fsUber)) bgfx::destroy(fsUber); WWDEBUG_SAY(("[BgfxBackend] uber shader createShader FAILED.")); } @@ -1425,9 +1881,127 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) } else { + if (bgfx::isValid(vsTrees)) bgfx::destroy(vsTrees); + if (bgfx::isValid(fsUber2)) bgfx::destroy(fsUber2); WWDEBUG_SAY(("[BgfxBackend] tree shader createShader FAILED.")); } - g_uSwayTable = bgfx::createUniform("u_swayTable", bgfx::UniformType::Vec4, 11); + // stencil shadow volume program. XYZ-only verts, no color output. + const bgfx::Memory * vsShadowMem = bgfx::makeRef(vs_shadow_volume_dx11, sizeof(vs_shadow_volume_dx11)); + const bgfx::Memory * fsShadowMem = bgfx::makeRef(fs_shadow_volume_dx11, sizeof(fs_shadow_volume_dx11)); + bgfx::ShaderHandle vsShadow = bgfx::createShader(vsShadowMem); + bgfx::ShaderHandle fsShadow = bgfx::createShader(fsShadowMem); + if (bgfx::isValid(vsShadow) && bgfx::isValid(fsShadow)) + { + bgfx::setName(vsShadow, "vs_shadow_volume"); + bgfx::setName(fsShadow, "fs_shadow_volume"); + g_shadowVolumeProgram = bgfx::createProgram(vsShadow, fsShadow, true); + } + else + { + if (bgfx::isValid(vsShadow)) bgfx::destroy(vsShadow); + if (bgfx::isValid(fsShadow)) bgfx::destroy(fsShadow); + WWDEBUG_SAY(("[BgfxBackend] shadow volume shader createShader FAILED.")); + } + + // shadow darken apply program. Fullscreen clip-space quad. + const bgfx::Memory * vsApplyMem = bgfx::makeRef(vs_shadow_apply_dx11, sizeof(vs_shadow_apply_dx11)); + const bgfx::Memory * fsApplyMem = bgfx::makeRef(fs_shadow_apply_dx11, sizeof(fs_shadow_apply_dx11)); + bgfx::ShaderHandle vsApply = bgfx::createShader(vsApplyMem); + bgfx::ShaderHandle fsApply = bgfx::createShader(fsApplyMem); + if (bgfx::isValid(vsApply) && bgfx::isValid(fsApply)) + { + bgfx::setName(vsApply, "vs_shadow_apply"); + bgfx::setName(fsApply, "fs_shadow_apply"); + g_shadowApplyProgram = bgfx::createProgram(vsApply, fsApply, true); + } + else + { + if (bgfx::isValid(vsApply)) bgfx::destroy(vsApply); + if (bgfx::isValid(fsApply)) bgfx::destroy(fsApply); + WWDEBUG_SAY(("[BgfxBackend] shadow apply shader createShader FAILED.")); + } + g_uShadowColor = bgfx::createUniform("u_shadowColor", bgfx::UniformType::Vec4); + g_uShadowBias = bgfx::createUniform("u_shadowBias", bgfx::UniformType::Vec4); + + // CSM caster program + shadow map render target. + const bgfx::Memory * vsCasterMem = bgfx::makeRef(vs_shadow_caster_dx11, sizeof(vs_shadow_caster_dx11)); + const bgfx::Memory * fsCasterMem = bgfx::makeRef(fs_shadow_caster_dx11, sizeof(fs_shadow_caster_dx11)); + bgfx::ShaderHandle vsCaster = bgfx::createShader(vsCasterMem); + bgfx::ShaderHandle fsCaster = bgfx::createShader(fsCasterMem); + if (bgfx::isValid(vsCaster) && bgfx::isValid(fsCaster)) + { + bgfx::setName(vsCaster, "vs_shadow_caster"); + bgfx::setName(fsCaster, "fs_shadow_caster"); + g_shadowCasterProgram = bgfx::createProgram(vsCaster, fsCaster, true); + } + else + { + if (bgfx::isValid(vsCaster)) bgfx::destroy(vsCaster); + if (bgfx::isValid(fsCaster)) bgfx::destroy(fsCaster); + WWDEBUG_SAY(("[BgfxBackend] CSM caster shader createShader FAILED.")); + } + + // Shadow map depth render target. 1024x1024 D24 with compare-less + // sampling for hardware PCF (sampler2DShadow in fs_uber). + // Shadow map: D32F depth texture with comparison sampler for + // hardware PCF (matches bgfx example 16). D32F gives full float + // precision, eliminating shadow acne from depth quantization. + const uint64_t depthFlags = BGFX_TEXTURE_RT + | BGFX_SAMPLER_COMPARE_LEQUAL + | BGFX_SAMPLER_U_CLAMP + | BGFX_SAMPLER_V_CLAMP; + g_shadowMapDepth = bgfx::createTexture2D( + kShadowMapResolution, kShadowMapResolution, false, 1, + bgfx::TextureFormat::D32F, depthFlags); + if (bgfx::isValid(g_shadowMapDepth)) + { + bgfx::setName(g_shadowMapDepth, "shadowMapD32F"); + g_shadowMapFB = bgfx::createFrameBuffer(1, &g_shadowMapDepth, true); + // Clear both color (R32F=1.0 = far plane) and depth for the + // caster pass's own depth test. 0x3f800000 = float 1.0 as RGBA. + bgfx::setViewClear(kBgfxShadowMapView, + BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, + 0xffffffffu, // RGBA all bits set ≈ max depth + 1.0f, 0); + bgfx::setViewRect(kBgfxShadowMapView, 0, 0, kShadowMapResolution, kShadowMapResolution); + bgfx::setViewFrameBuffer(kBgfxShadowMapView, g_shadowMapFB); + bgfx::setViewMode(kBgfxShadowMapView, bgfx::ViewMode::Sequential); + WWDEBUG_SAY(("[CSM] shadow map FB=%u tex=%u valid=%d", + g_shadowMapFB.idx, g_shadowMapDepth.idx, + bgfx::isValid(g_shadowMapFB) ? 1 : 0)); + + // View ORDER: shadow map (view 8) MUST render before + // the engine view (view 1) samples it in fs_uber. By default + // bgfx renders views in view-id order (0, 1, 2, ...) — view 8 + // would run AFTER view 1, and the scene would sample empty / + // stale shadow map. Explicit order: shadow map first, then the + // rest of the views in their natural ascending order. + const bgfx::ViewId order[] = { + kBgfxShadowMapView, // 8 — shadow caster depth pass + kBgfxDebugView, // test/passthrough + kBgfxRTTView, // 3 + kBgfxEngineView, // 1 + kBgfxShadowVolumeView, // 6 — stencil shadow volume fill + kBgfxShadowApplyView, // 7 — stencil shadow darken + kBgfxWaterView, // 4 + kBgfxEngineSortView, // 2 + kBgfxEffectOverlayView, // 5 + }; + bgfx::setViewOrder(kBgfxDebugView, BX_COUNTOF(order), order); + WWDEBUG_SAY(("[CSM] view order set: shadow map (view %u) runs first", + kBgfxShadowMapView)); + } + else + { + WWDEBUG_SAY(("[BgfxBackend] CSM shadow map texture creation FAILED.")); + } + + g_uShadowLightViewProj = bgfx::createUniform("u_shadowLightViewProj", + bgfx::UniformType::Mat4); + g_sShadowMap = bgfx::createUniform("s_shadowMap", + bgfx::UniformType::Sampler); + + g_uSwayTable = bgfx::createUniform("u_swayTable", bgfx::UniformType::Vec4, kSwayTableEntries); g_uShroudOffset = bgfx::createUniform("u_shroudOffset", bgfx::UniformType::Vec4); g_uShroudScale = bgfx::createUniform("u_shroudScale", bgfx::UniformType::Vec4); @@ -1453,120 +2027,51 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) caps->originBottomLeft ? 1 : 0)); } +template +static void DestroyBgfxHandle(H & h) +{ + if (bgfx::isValid(h)) + { + bgfx::destroy(h); + h = BGFX_INVALID_HANDLE; + } +} + void BgfxBackend::Shutdown() { if (g_bgfxInitialized) { - if (bgfx::isValid(g_passthroughProgram)) - { - bgfx::destroy(g_passthroughProgram); - g_passthroughProgram = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_uberProgram)) - { - bgfx::destroy(g_uberProgram); - g_uberProgram = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_treeProgram)) - { - bgfx::destroy(g_treeProgram); - g_treeProgram = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_uSwayTable)) - { - bgfx::destroy(g_uSwayTable); - g_uSwayTable = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_uShroudOffset)) - { - bgfx::destroy(g_uShroudOffset); - g_uShroudOffset = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_uShroudScale)) - { - bgfx::destroy(g_uShroudScale); - g_uShroudScale = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_sTex0)) - { - bgfx::destroy(g_sTex0); - g_sTex0 = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_sTex1)) - { - bgfx::destroy(g_sTex1); - g_sTex1 = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_sTex2)) - { - bgfx::destroy(g_sTex2); - g_sTex2 = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_sTex3)) - { - bgfx::destroy(g_sTex3); - g_sTex3 = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_uMatDiffuse)) - { - bgfx::destroy(g_uMatDiffuse); - g_uMatDiffuse = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_uAtestParams)) - { - bgfx::destroy(g_uAtestParams); - g_uAtestParams = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_uTssOps0)) - { - bgfx::destroy(g_uTssOps0); - g_uTssOps0 = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_uTssOps1)) - { - bgfx::destroy(g_uTssOps1); - g_uTssOps1 = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_uLightDirs)) - { - bgfx::destroy(g_uLightDirs); - g_uLightDirs = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_uLightColors)) - { - bgfx::destroy(g_uLightColors); - g_uLightColors = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_uSceneAmbient)) - { - bgfx::destroy(g_uSceneAmbient); - g_uSceneAmbient = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_uLightingEnabled)) - { - bgfx::destroy(g_uLightingEnabled); - g_uLightingEnabled = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_uTexTransform0)) - { - bgfx::destroy(g_uTexTransform0); - g_uTexTransform0 = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_uTexcoordSelect)) - { - bgfx::destroy(g_uTexcoordSelect); - g_uTexcoordSelect = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_defaultWhiteTexture)) - { - bgfx::destroy(g_defaultWhiteTexture); - g_defaultWhiteTexture = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(g_defaultTransparentTexture)) - { - bgfx::destroy(g_defaultTransparentTexture); - g_defaultTransparentTexture = BGFX_INVALID_HANDLE; - } + DestroyBgfxHandle(g_passthroughProgram); + DestroyBgfxHandle(g_uberProgram); + DestroyBgfxHandle(g_treeProgram); + DestroyBgfxHandle(g_uSwayTable); + DestroyBgfxHandle(g_uShroudOffset); + DestroyBgfxHandle(g_uShroudScale); + DestroyBgfxHandle(g_sTex0); + DestroyBgfxHandle(g_sTex1); + DestroyBgfxHandle(g_sTex2); + DestroyBgfxHandle(g_sTex3); + DestroyBgfxHandle(g_uMatDiffuse); + DestroyBgfxHandle(g_uAtestParams); + DestroyBgfxHandle(g_uTssOps0); + DestroyBgfxHandle(g_uTssOps1); + DestroyBgfxHandle(g_uLightDirs); + DestroyBgfxHandle(g_uLightColors); + DestroyBgfxHandle(g_uSceneAmbient); + DestroyBgfxHandle(g_uLightingEnabled); + DestroyBgfxHandle(g_uTexcoordSelect); + DestroyBgfxHandle(g_uShroudParams); + DestroyBgfxHandle(g_shadowVolumeProgram); + DestroyBgfxHandle(g_shadowApplyProgram); + DestroyBgfxHandle(g_shadowCasterProgram); + DestroyBgfxHandle(g_shadowMapFB); + DestroyBgfxHandle(g_shadowMapDepth); + DestroyBgfxHandle(g_uShadowColor); + DestroyBgfxHandle(g_uShadowBias); + DestroyBgfxHandle(g_uShadowLightViewProj); + DestroyBgfxHandle(g_sShadowMap); + DestroyBgfxHandle(g_defaultWhiteTexture); + DestroyBgfxHandle(g_defaultTransparentTexture); g_renderTargetSet.clear(); for (auto & kv : g_framebufferCache) { @@ -1580,17 +2085,17 @@ void BgfxBackend::Shutdown() // so the handles outlive nothing. for (auto & kv : g_vbCache) { - if (bgfx::isValid(kv.second)) + if (bgfx::isValid(kv.second.handle)) { - bgfx::destroy(kv.second); + bgfx::destroy(kv.second.handle); } } g_vbCache.clear(); for (auto & kv : g_ibCache) { - if (bgfx::isValid(kv.second)) + if (bgfx::isValid(kv.second.handle)) { - bgfx::destroy(kv.second); + bgfx::destroy(kv.second.handle); } } g_ibCache.clear(); @@ -1636,11 +2141,18 @@ void BgfxBackend::Begin_Scene() { return; } - bgfx::touch(0); + bgfx::touch(kBgfxDebugView); bgfx::touch(kBgfxEngineView); bgfx::touch(kBgfxEngineSortView); bgfx::touch(kBgfxWaterView); bgfx::touch(kBgfxEffectOverlayView); + bgfx::touch(kBgfxShadowVolumeView); + bgfx::touch(kBgfxShadowApplyView); + bgfx::touch(kBgfxShadowMapView); + // CSM: light transform updated lazily at first draw + // (see SubmitEngineDraw), not here, because g_bgfxView is still + // identity at Begin_Scene time. + g_shadowLightCaptured = false; bgfx::setViewRect(kBgfxWaterView, 0, 0, kBgfxWindowWidth, kBgfxWindowHeight); // No clear on water view — it composites over the opaque scene. bgfx::setViewClear(kBgfxWaterView, BGFX_CLEAR_NONE, 0, 1.0f, 0); @@ -1664,6 +2176,7 @@ void BgfxBackend::End_Scene(bool /*flip_frame*/) { bgfx::setViewTransform(kBgfxEngineView, g_bgfxCameraView, g_bgfxCameraProj); bgfx::setViewTransform(kBgfxWaterView, g_bgfxCameraView, g_bgfxCameraProj); + bgfx::setViewTransform(kBgfxShadowVolumeView, g_bgfxCameraView, g_bgfxCameraProj); g_bgfxCameraCaptured = false; } if (g_bgfxSortProjCaptured) @@ -1674,12 +2187,21 @@ void BgfxBackend::End_Scene(bool /*flip_frame*/) g_bgfxSortProjCaptured = false; } - // View 3 (RTT) renders first. View 1 draws opaque scene. - // View 4 (water) draws DESTALPHA overlay. View 2 (sort). - // View 5 (effect overlay / dazzle) last so lens flares sit on top. - bgfx::ViewId viewOrder[] = { kBgfxRTTView, kBgfxEngineView, kBgfxWaterView, - kBgfxEngineSortView, kBgfxEffectOverlayView }; - bgfx::setViewOrder(0, 5, viewOrder); + // The shadow map (view 8) renders FIRST so the depth + // texture is populated before the scene samples it. Then RTT (3), + // engine opaque (1), shadow volume fill (6), shadow darken (7), + // water (4), sort (2), effect overlay (5). + bgfx::ViewId viewOrder[] = { + kBgfxShadowMapView, // 8 — shadow caster depth pass + kBgfxRTTView, // 3 + kBgfxEngineView, // 1 + kBgfxShadowVolumeView, // 6 — stencil shadow volume fill + kBgfxShadowApplyView, // 7 — stencil shadow darken + kBgfxWaterView, // 4 + kBgfxEngineSortView, // 2 + kBgfxEffectOverlayView, // 5 + }; + bgfx::setViewOrder(kBgfxDebugView, BX_COUNTOF(viewOrder), viewOrder); bgfx::frame(); @@ -1709,7 +2231,7 @@ void BgfxBackend::Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int s auto it = g_vbCache.find(vb); if (it != g_vbCache.end()) { - g_currentBgfxVB = it->second; + g_currentBgfxVB = it->second.handle; } else { @@ -1740,7 +2262,7 @@ void BgfxBackend::Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int s auto it2 = g_vbCache.find(vb); if (it2 != g_vbCache.end()) { - g_currentBgfxVB = it2->second; + g_currentBgfxVB = it2->second.handle; } } } @@ -1776,7 +2298,7 @@ void BgfxBackend::Set_Index_Buffer(const IndexBufferClass * ib, unsigned short i auto it = g_ibCache.find(ib); if (it != g_ibCache.end()) { - g_currentBgfxIB = it->second; + g_currentBgfxIB = it->second.handle; } else { @@ -1802,7 +2324,7 @@ void BgfxBackend::Set_Index_Buffer(const IndexBufferClass * ib, unsigned short i auto it2 = g_ibCache.find(ib); if (it2 != g_ibCache.end()) { - g_currentBgfxIB = it2->second; + g_currentBgfxIB = it2->second.handle; } } } @@ -1868,44 +2390,199 @@ namespace // (AppendLockClass) capture paths. bgfx::DynamicVertexBufferHandle EnsureDynamicVertexBuffer(const VertexBufferClass * vb) { + const uint32_t num_verts = static_cast(vb->Get_Vertex_Count()); + const uint32_t engine_stride = vb->FVF_Info().Get_FVF_Size(); + auto it = g_vbCache.find(vb); - if (it != g_vbCache.end() && bgfx::isValid(it->second)) + if (it != g_vbCache.end()) { - return it->second; + // Stale pointer reuse detection: engine can destroy a VB and + // allocate a new one at the same address with different + // dimensions. If dimensions changed, drop the stale handle. + if (it->second.num_verts == num_verts && it->second.stride == engine_stride) + { + return it->second.handle; + } + if (bgfx::isValid(it->second.handle)) + { + bgfx::destroy(it->second.handle); + } + g_vbCache.erase(it); + } + + if (num_verts == 0) + { + return BGFX_INVALID_HANDLE; } bgfx::VertexLayout layout; if (!BuildBgfxLayoutForFVF(vb->FVF_Info(), layout)) { return BGFX_INVALID_HANDLE; } - const uint32_t num_verts = static_cast(vb->Get_Vertex_Count()); - if (num_verts == 0) - { + // Mismatched stride (typically for skinned-mesh FVFs like + // D3DFVF_XYZB* that BuildBgfxLayoutForFVF does not fully cover) + // would create a too-small bgfx buffer and cause truncation + // warnings + D3D11 CreateBuffer crashes when the engine writes + // a larger per-vertex stride than bgfx allocated. + const uint32_t layout_stride = layout.getStride(); + if (layout_stride == 0 || layout_stride != engine_stride) + { + WWDEBUG_SAY(("[BgfxBackend] skip VB cache: layout stride=%u != " + "engine stride=%u (fvf=0x%x num_verts=%u) - unsupported FVF", + layout_stride, engine_stride, + vb->FVF_Info().Get_FVF(), num_verts)); + BgfxVbCacheEntry e{ BGFX_INVALID_HANDLE, num_verts, engine_stride }; + g_vbCache[vb] = e; return BGFX_INVALID_HANDLE; } bgfx::DynamicVertexBufferHandle h = bgfx::createDynamicVertexBuffer(num_verts, layout); - g_vbCache[vb] = h; + BgfxVbCacheEntry e{ h, num_verts, engine_stride }; + g_vbCache[vb] = e; return h; } bgfx::DynamicIndexBufferHandle EnsureDynamicIndexBuffer(const IndexBufferClass * ib) { + const uint32_t num_indices = static_cast(ib->Get_Index_Count()); + auto it = g_ibCache.find(ib); - if (it != g_ibCache.end() && bgfx::isValid(it->second)) + if (it != g_ibCache.end()) { - return it->second; + if (it->second.num_indices == num_indices && bgfx::isValid(it->second.handle)) + { + return it->second.handle; + } + if (bgfx::isValid(it->second.handle)) + { + bgfx::destroy(it->second.handle); + } + g_ibCache.erase(it); } - const uint32_t num_indices = static_cast(ib->Get_Index_Count()); if (num_indices == 0) { return BGFX_INVALID_HANDLE; } bgfx::DynamicIndexBufferHandle h = bgfx::createDynamicIndexBuffer(num_indices); - g_ibCache[ib] = h; + BgfxIbCacheEntry e{ h, num_indices }; + g_ibCache[ib] = e; return h; } } +// TheSuperHackers @feature bobtista 17/04/2026 Shroud texture capture for +// bgfx. The shroud destination texture is POOL_DEFAULT, which the bgfx +// texture-upload path in EnsureBgfxTexture skips (cannot lock). Instead, +// the shroud system pushes its system-memory pixel data here every frame +// after CopyRects. We create a bgfx texture on first call and updateTexture2D +// on subsequent frames, storing the handle in g_textureCache keyed by the +// engine's destination TextureClass so EnsureBgfxTexture finds it on lookup +// before reaching the POOL_DEFAULT early-out. +void BgfxBackend::Capture_Shroud_Texture(TextureClass * dst_texture, + const void * pixel_data, + unsigned dst_width, + unsigned dst_height, + unsigned src_width, + unsigned src_height, + unsigned dst_x, + unsigned dst_y, + unsigned pitch, + WW3DFormat format) +{ + if (!g_bgfxInitialized || dst_texture == nullptr || pixel_data == nullptr) + { + return; + } + + const bgfx::TextureFormat::Enum bgfxFmt = TranslateWW3DFormat(format); + if (bgfxFmt == bgfx::TextureFormat::Unknown) + { + return; + } + + const unsigned bpp = (bgfxFmt == bgfx::TextureFormat::BGRA4 || + bgfxFmt == bgfx::TextureFormat::R5G6B5) ? 2 : 4; + + // TheSuperHackers @bugfix bobtista 17/04/2026 invalidate stale cache + // entries when the shroud texture pointer changes (save/load destroys + // and recreates m_pDstTexture). Without this, the old bgfx handle + // stays in g_textureCache keyed by the freed pointer, and if the + // address is reused, updateTexture2D crashes with mismatched dimensions. + static TextureClass * s_lastShroudDst = nullptr; + static unsigned s_lastShroudW = 0; + static unsigned s_lastShroudH = 0; + if (dst_texture != s_lastShroudDst + || dst_width != s_lastShroudW + || dst_height != s_lastShroudH) + { + if (s_lastShroudDst != nullptr) + { + auto oldIt = g_textureCache.find(s_lastShroudDst); + if (oldIt != g_textureCache.end()) + { + if (bgfx::isValid(oldIt->second)) + bgfx::destroy(oldIt->second); + g_textureCache.erase(oldIt); + } + g_renderTargetSet.erase(s_lastShroudDst); + } + s_lastShroudDst = dst_texture; + s_lastShroudW = dst_width; + s_lastShroudH = dst_height; + } + + auto it = g_textureCache.find(dst_texture); + if (it == g_textureCache.end() || !bgfx::isValid(it->second)) + { + bgfx::TextureHandle h = bgfx::createTexture2D( + static_cast(dst_width), + static_cast(dst_height), + false, 1, + bgfxFmt, + BGFX_TEXTURE_NONE | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + g_textureCache[dst_texture] = h; + + WWDEBUG_SAY(("[BgfxBackend] Shroud texture created: dst=%ux%u src=%ux%u " + "fmt=%d bgfxFmt=%d bpp=%u off=(%u,%u)", + dst_width, dst_height, src_width, src_height, + static_cast(format), static_cast(bgfxFmt), + bpp, dst_x, dst_y)); + } + + bgfx::TextureHandle h = g_textureCache[dst_texture]; + if (!bgfx::isValid(h)) + { + return; + } + + // Build the shroud image into a persistent buffer and use makeRef + // to avoid copying into bgfx's command buffer (which has a 64KB + // default limit and overflows when combined with texture creation + // bursts like the satellite reveal). The persistent buffer stays + // valid until the next frame's update overwrites it. + const unsigned fullSize = dst_width * dst_height * bpp; + if (fullSize == 0 || src_width == 0 || src_height == 0 + || dst_width > 0xFFFF || dst_height > 0xFFFF) + { + return; + } + const bgfx::Memory * mem = bgfx::alloc(fullSize); + std::memset(mem->data, 0xFF, fullSize); + const unsigned rowBytes = src_width * bpp; + for (unsigned row = 0; row < src_height; ++row) + { + const unsigned dstOffset = ((dst_y + row) * dst_width + dst_x) * bpp; + const unsigned srcOffset = row * pitch; + if (dstOffset + rowBytes <= fullSize && srcOffset + rowBytes <= src_height * pitch) + { + std::memcpy(mem->data + dstOffset, static_cast(pixel_data) + srcOffset, rowBytes); + } + } + bgfx::updateTexture2D(h, 0, 0, 0, 0, + static_cast(dst_width), + static_cast(dst_height), + mem, static_cast(dst_width * bpp)); +} + void BgfxBackend::Capture_Vertex_Data(const VertexBufferClass * vb, const void * data, unsigned int size_bytes) @@ -1914,6 +2591,18 @@ void BgfxBackend::Capture_Vertex_Data(const VertexBufferClass * vb, { return; } + // Guard: clamp write to the dynamic VB allocation so bgfx's + // E_INVALIDARG path at renderer_d3d11.cpp:4038 (CreateBuffer for + // staging) never fires. + const uint32_t stride = vb->FVF_Info().Get_FVF_Size(); + const uint32_t buffer_bytes = static_cast(vb->Get_Vertex_Count()) * stride; + if (stride == 0 || buffer_bytes == 0 || size_bytes > buffer_bytes) + { + WWDEBUG_SAY(("[BgfxBackend] skip VB full-capture: " + "size_bytes=%u stride=%u total=%u", + size_bytes, stride, buffer_bytes)); + return; + } bgfx::DynamicVertexBufferHandle h = EnsureDynamicVertexBuffer(vb); if (!bgfx::isValid(h)) @@ -1932,6 +2621,13 @@ void BgfxBackend::Capture_Index_Data(const IndexBufferClass * ib, { return; } + const uint32_t buffer_bytes = static_cast(ib->Get_Index_Count()) * sizeof(uint16_t); + if (buffer_bytes == 0 || size_bytes > buffer_bytes) + { + WWDEBUG_SAY(("[BgfxBackend] skip IB full-capture: " + "size_bytes=%u total=%u", size_bytes, buffer_bytes)); + return; + } bgfx::DynamicIndexBufferHandle h = EnsureDynamicIndexBuffer(ib); if (!bgfx::isValid(h)) { @@ -1950,6 +2646,22 @@ void BgfxBackend::Capture_Vertex_Sub_Range(const VertexBufferClass * vb, { return; } + // Guard: total update must fit in the dynamic VB allocation. + const uint32_t stride = vb->FVF_Info().Get_FVF_Size(); + if (stride == 0) + { + WWDEBUG_SAY(("[BgfxBackend] skip VB capture: stride=0 vb=%p", vb)); + return; + } + const uint32_t buffer_bytes = static_cast(vb->Get_Vertex_Count()) * stride; + const uint64_t end_byte = static_cast(start_vertex) * stride + size_bytes; + if (end_byte > buffer_bytes) + { + WWDEBUG_SAY(("[BgfxBackend] skip VB capture: out-of-range " + "start_vert=%u size_bytes=%u stride=%u total=%u", + start_vertex, size_bytes, stride, buffer_bytes)); + return; + } bgfx::DynamicVertexBufferHandle h = EnsureDynamicVertexBuffer(vb); if (!bgfx::isValid(h)) { @@ -1969,6 +2681,15 @@ void BgfxBackend::Capture_Index_Sub_Range(const IndexBufferClass * ib, { return; } + const uint32_t buffer_bytes = static_cast(ib->Get_Index_Count()) * sizeof(uint16_t); + const uint64_t end_byte = static_cast(start_index) * sizeof(uint16_t) + size_bytes; + if (end_byte > buffer_bytes) + { + WWDEBUG_SAY(("[BgfxBackend] skip IB capture: out-of-range " + "start_idx=%u size_bytes=%u total=%u", + start_index, size_bytes, buffer_bytes)); + return; + } bgfx::DynamicIndexBufferHandle h = EnsureDynamicIndexBuffer(ib); if (!bgfx::isValid(h)) { @@ -2030,9 +2751,18 @@ void BgfxBackend::Capture_Sorted_Batch_Transforms(const Matrix4x4 & sortWorld, s += sortWorld[r][k] * sortView[k][c]; } g_bgfxSortWorld[r * 4 + c] = s; + // Store raw model (no camera view baked in) + // for shadow caster submissions. + } + } + // Store raw sortWorld (model-to-world only) for CSM caster use. + for (int r = 0; r < 4; ++r) + { + for (int c = 0; c < 4; ++c) + { + g_bgfxSortWorldRaw[r * 4 + c] = sortWorld[r][c]; } } - } void BgfxBackend::Capture_Sorted_Batch_Light(const D3DLIGHT8 & light, bool enabled) @@ -2056,20 +2786,82 @@ void BgfxBackend::Capture_Sorted_Batch_Light(const D3DLIGHT8 & light, bool enabl } } -// TheSuperHackers @refactor bobtista 11/04/2026 sorted VB -// direct-draw submit. Called from DX8Wrapper::Draw_Sorting_IB_VB after -// it populates an internal dynamic VB and dynamic IB by copying a slice -// of the sorting VB/IB with the correct vba_offset / iba_offset / -// index_base_offset / min_vertex_index arithmetic. The engine's lock -// destructors already fired Capture_Dynamic_* for those inner buffers, -// so g_pendingDynVB / g_pendingDynIB hold their transients keyed by -// the exact access-class pointers we were just handed. -// -// We pull those transients into local draw state, emit a single bgfx -// submit to the sorted view with the inner buffers and remapped args -// (start_index=0, min_vertex_index=0, count relative to the inner -// buffers), and set g_skipNextSubmitEngineDraw so the outer Draw_Triangles -// does not emit a second, incorrect submit using the old sorting-VB +static void BindTextureStages() +{ + if (bgfx::isValid(g_sTex0)) + { + const bgfx::TextureHandle bound = + bgfx::isValid(g_currentBgfxTexture0) ? g_currentBgfxTexture0 : g_defaultWhiteTexture; + if (bgfx::isValid(bound)) + { + bgfx::setTexture(0, g_sTex0, bound, g_currentSamplerFlags0); + } + } + if (bgfx::isValid(g_sTex1)) + { + const bgfx::TextureHandle bound = + bgfx::isValid(g_currentBgfxTexture1) ? g_currentBgfxTexture1 : g_defaultWhiteTexture; + if (bgfx::isValid(bound)) + { + bgfx::setTexture(1, g_sTex1, bound, g_currentSamplerFlags1); + } + } + if (bgfx::isValid(g_sTex2)) + { + const bgfx::TextureHandle bound = + bgfx::isValid(g_currentBgfxTexture2) ? g_currentBgfxTexture2 : g_defaultWhiteTexture; + if (bgfx::isValid(bound)) + { + bgfx::setTexture(2, g_sTex2, bound, g_currentSamplerFlags2); + } + } + if (bgfx::isValid(g_sTex3)) + { + const bgfx::TextureHandle bound = + bgfx::isValid(g_currentBgfxTexture3) ? g_currentBgfxTexture3 : g_defaultWhiteTexture; + if (bgfx::isValid(bound)) + { + bgfx::setTexture(3, g_sTex3, bound, g_currentSamplerFlags3); + } + } +} + +static void UploadMaterialUniforms() +{ + if (bgfx::isValid(g_uMatDiffuse)) + { + bgfx::setUniform(g_uMatDiffuse, g_currentMatDiffuse); + } + + if (bgfx::isValid(g_uTssOps0)) + { + bgfx::setUniform(g_uTssOps0, g_currentTssOps0); + } + if (bgfx::isValid(g_uTssOps1)) + { + bgfx::setUniform(g_uTssOps1, g_currentTssOps1); + } + if (bgfx::isValid(g_uAtestParams)) + { + float atestParams[4] = { g_currentAtestRef, 0.0f, 0.0f, 0.0f }; + bgfx::setUniform(g_uAtestParams, atestParams); + } +} + +// TheSuperHackers @refactor bobtista 11/04/2026 sorted VB +// direct-draw submit. Called from DX8Wrapper::Draw_Sorting_IB_VB after +// it populates an internal dynamic VB and dynamic IB by copying a slice +// of the sorting VB/IB with the correct vba_offset / iba_offset / +// index_base_offset / min_vertex_index arithmetic. The engine's lock +// destructors already fired Capture_Dynamic_* for those inner buffers, +// so g_pendingDynVB / g_pendingDynIB hold their transients keyed by +// the exact access-class pointers we were just handed. +// +// We pull those transients into local draw state, emit a single bgfx +// submit to the sorted view with the inner buffers and remapped args +// (start_index=0, min_vertex_index=0, count relative to the inner +// buffers), and set g_skipNextSubmitEngineDraw so the outer Draw_Triangles +// does not emit a second, incorrect submit using the old sorting-VB // args. void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, @@ -2135,61 +2927,8 @@ void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, bgfx::setVertexBuffer(0, &vb, 0, vertex_count); bgfx::setIndexBuffer(&ib, 0, static_cast(polygon_count) * 3); - if (bgfx::isValid(g_sTex0)) - { - const bgfx::TextureHandle bound = - bgfx::isValid(g_currentBgfxTexture0) ? g_currentBgfxTexture0 : g_defaultWhiteTexture; - if (bgfx::isValid(bound)) - { - bgfx::setTexture(0, g_sTex0, bound, g_currentSamplerFlags0); - } - } - if (bgfx::isValid(g_sTex1)) - { - const bgfx::TextureHandle bound = - bgfx::isValid(g_currentBgfxTexture1) ? g_currentBgfxTexture1 : g_defaultWhiteTexture; - if (bgfx::isValid(bound)) - { - bgfx::setTexture(1, g_sTex1, bound, g_currentSamplerFlags1); - } - } - if (bgfx::isValid(g_sTex2)) - { - const bgfx::TextureHandle bound = - bgfx::isValid(g_currentBgfxTexture2) ? g_currentBgfxTexture2 : g_defaultWhiteTexture; - if (bgfx::isValid(bound)) - { - bgfx::setTexture(2, g_sTex2, bound, g_currentSamplerFlags2); - } - } - if (bgfx::isValid(g_sTex3)) - { - const bgfx::TextureHandle bound = - bgfx::isValid(g_currentBgfxTexture3) ? g_currentBgfxTexture3 : g_defaultWhiteTexture; - if (bgfx::isValid(bound)) - { - bgfx::setTexture(3, g_sTex3, bound, g_currentSamplerFlags3); - } - } - - if (bgfx::isValid(g_uMatDiffuse)) - { - bgfx::setUniform(g_uMatDiffuse, g_currentMatDiffuse); - } - - if (bgfx::isValid(g_uTssOps0)) - { - bgfx::setUniform(g_uTssOps0, g_currentTssOps0); - } - if (bgfx::isValid(g_uTssOps1)) - { - bgfx::setUniform(g_uTssOps1, g_currentTssOps1); - } - if (bgfx::isValid(g_uAtestParams)) - { - float atestParams[4] = { g_currentAtestRef, 0.0f, 0.0f, 0.0f }; - bgfx::setUniform(g_uAtestParams, atestParams); - } + BindTextureStages(); + UploadMaterialUniforms(); uint64_t state = (g_currentBgfxState != 0) ? g_currentBgfxState @@ -2207,6 +2946,22 @@ void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, bgfx::setState(state); bgfx::submit(kBgfxEngineSortView, g_currentBgfxProgram); + // CSM: also submit sorted geometry as shadow caster. + if (bgfx::isValid(g_shadowCasterProgram) + && bgfx::isValid(g_shadowMapFB)) + { + bgfx::setVertexBuffer(0, &vb); + bgfx::setIndexBuffer(&ib, 0, + static_cast(polygon_count) * 3); + bgfx::setTransform(g_bgfxSortWorldRaw); + const uint64_t casterState = + BGFX_STATE_WRITE_Z + | BGFX_STATE_DEPTH_TEST_LESS + | BGFX_STATE_CULL_CW; + bgfx::setState(casterState); + bgfx::submit(kBgfxShadowMapView, g_shadowCasterProgram); + } + g_skipNextSubmitEngineDraw = true; } @@ -2320,13 +3075,6 @@ void BgfxBackend::Set_Material(const VertexMaterialClass * material) // (terrain, pre-lit meshes), vertex colors already contain the // lit result and the shader must NOT apply N.L on top. g_currentLightingEnabled[0] = material->Get_Lighting() ? 1.0f : 0.0f; - // Reset texture transform to identity. The mapper's Apply() will - // set it if UV animation is needed. Without this reset, a previous - // draw's UV scroll would persist into the next draw. - g_currentTexTransform0[0] = 1.0f; - g_currentTexTransform0[1] = 1.0f; - g_currentTexTransform0[2] = 0.0f; - g_currentTexTransform0[3] = 0.0f; } else { @@ -2354,18 +3102,23 @@ void BgfxBackend::Set_Texture(unsigned int stage, TextureBaseClass * texture) // through unmigrated. { bgfx::TextureHandle h = EnsureBgfxTexture(texture); - // If the texture is INVALID and it's a render target, use the - // water-colored fallback instead of white. + // TheSuperHackers @bugfix bobtista 16/04/2026 use white + // fallback for render target textures instead of dark blue. The + // blue fallback was intended for water reflections but applies to + // ALL unresolved RT textures, tinting the entire world blue. + // White is the multiplicative identity — it passes through the + // vertex/material color and lets the scene look correct until the + // RT texture is properly captured. if (!bgfx::isValid(h) && texture != nullptr && g_renderTargetSet.count(texture) > 0) { - h = g_defaultTransparentTexture; + h = g_defaultWhiteTexture; static int s_loggedRTFallback = 0; if (s_loggedRTFallback < 5) { ++s_loggedRTFallback; TextureClass * t2d_fb = texture->As_TextureClass(); - WWDEBUG_SAY(("[BgfxBackend] RT FALLBACK: stage=%u using dark blue fallback for %s", + WWDEBUG_SAY(("[BgfxBackend] RT FALLBACK: stage=%u using white fallback for %s", stage, t2d_fb ? t2d_fb->Get_Full_Path().str() : "(null)")); } @@ -2386,7 +3139,6 @@ void BgfxBackend::Set_Texture(unsigned int stage, TextureBaseClass * texture) } } TextureClass * t2d_name = texture ? texture->As_TextureClass() : nullptr; - const char * texName = t2d_name ? t2d_name->Get_Full_Path().str() : ""; // Capture the source texture's wrap mode into bgfx sampler flags // so we can pass it at bind time. Without this, WRAP is the // default and ramp/LUT textures with CLAMP semantics produce @@ -2402,9 +3154,9 @@ void BgfxBackend::Set_Texture(unsigned int stage, TextureBaseClass * texture) } switch (stage) { - case 0: g_currentBgfxTexture0 = h; g_currentTexName0 = texName; + case 0: g_currentBgfxTexture0 = h; g_currentSamplerFlags0 = samplerFlags; break; - case 1: g_currentBgfxTexture1 = h; g_currentTexName1 = texName; + case 1: g_currentBgfxTexture1 = h; g_currentSamplerFlags1 = samplerFlags; break; case 2: g_currentBgfxTexture2 = h; g_currentSamplerFlags2 = samplerFlags; break; case 3: g_currentBgfxTexture3 = h; g_currentSamplerFlags3 = samplerFlags; break; @@ -2532,6 +3284,340 @@ void BgfxBackend::Set_Color_Write_Enable(bool red, bool green, bool blue, bool a g_suppressBgfxDraw = false; } +// TheSuperHackers @refactor bobtista 15/04/2026 mirror the +// DWORD variant into g_colorWriteOverride so stencil shadow volume +// passes that call Set_Color_Write_Mask(0) actually disable bgfx color +// writes. Previously the call fell through to DX8Backend only, leaving +// shadow volumes drawing as solid black geometry. +void BgfxBackend::Set_Color_Write_Mask(unsigned mask) +{ + DX8Backend::Set_Color_Write_Mask(mask); + uint64_t bgfxMask = 0; + if (mask & D3DCOLORWRITEENABLE_RED) bgfxMask |= BGFX_STATE_WRITE_R; + if (mask & D3DCOLORWRITEENABLE_GREEN) bgfxMask |= BGFX_STATE_WRITE_G; + if (mask & D3DCOLORWRITEENABLE_BLUE) bgfxMask |= BGFX_STATE_WRITE_B; + if (mask & D3DCOLORWRITEENABLE_ALPHA) bgfxMask |= BGFX_STATE_WRITE_A; + g_colorWriteOverride = static_cast(bgfxMask); + g_suppressBgfxDraw = false; +} + +void BgfxBackend::Skip_Next_Bgfx_Submit() +{ + g_skipNextSubmitEngineDraw = true; +} + +// TheSuperHackers @fix bobtista 16/04/2026 Remove g_currentMatDiffuse aliasing from texture factor. +// The shadow decal draw is now skipped via Skip_Next_Bgfx_Submit so the aliasing +// is unnecessary and it clobbers team colors. +void BgfxBackend::Set_Texture_Factor(unsigned argb) +{ + DX8Backend::Set_Texture_Factor(argb); +} + +void BgfxBackend::Set_Shadow_Light_Position(float x, float y, float z) +{ + g_shadowSunPosX = x; + g_shadowSunPosY = y; + g_shadowSunPosZ = z; + g_shadowSunPosSet = true; + g_shadowLightCaptured = false; + static int s_slpLog = 0; + if (s_slpLog++ < 5) + { + WWDEBUG_SAY(("[CSM] Set_Shadow_Light_Position(%.1f, %.1f, %.1f)", + x, y, z)); + } +} + +void BgfxBackend::Set_Shadow_Volume_Shader_Active(bool active) +{ + g_shadowVolumeActive = active; + UpdateShadowStencilState(); +} + +void BgfxBackend::Submit_Shadow_Volume_Caps(unsigned strip_start_vertex, + unsigned num_silhouette_verts) +{ + // Called after the engine's side-wall Draw_Triangles for a volume. + // Generates a front cap (fan of caster-level verts, preserving the + // engine's silhouette winding) and a back cap (fan of extruded verts, + // reversed winding so outward normals point away from the light) + // as a transient index buffer referencing the already-bound dynamic + // vertex buffer. Submits to view 6 with the same stencil/cull state + // the side walls used — the caller is mid-pass (INCR or DECRSAT). + + if (!g_bgfxInitialized + || !g_shadowVolumeActive + || !bgfx::isValid(g_shadowVolumeProgram) + || !bgfx::isValid(g_currentBgfxVB) + || num_silhouette_verts < 3) + { + return; + } + + const unsigned tris_per_cap = num_silhouette_verts - 2; + const unsigned total_indices = 2 * tris_per_cap * 3; + + if (bgfx::getAvailTransientIndexBuffer(total_indices) < total_indices) + { + return; + } + + bgfx::TransientIndexBuffer tib; + bgfx::allocTransientIndexBuffer(&tib, total_indices); + uint16_t * idx = reinterpret_cast(tib.data); + + // Front cap: fan around caster-level verts. Winding FLIPPED vs + // initial guess — the engine's silhouette traversal direction may + // not match our assumed "outward = up" for the front cap. + for (unsigned i = 1; i < num_silhouette_verts - 1; ++i) + { + *idx++ = static_cast(strip_start_vertex + 0); + *idx++ = static_cast(strip_start_vertex + 2 * (i + 1)); + *idx++ = static_cast(strip_start_vertex + 2 * i); + } + // Back cap: fan around extruded verts (odd offsets), winding opposite + // of front cap (so opposite outward normal). + for (unsigned i = 1; i < num_silhouette_verts - 1; ++i) + { + *idx++ = static_cast(strip_start_vertex + 1); + *idx++ = static_cast(strip_start_vertex + 2 * i + 1); + *idx++ = static_cast(strip_start_vertex + 2 * (i + 1) + 1); + } + + // Skip second pass — single-pass two-sided handles both faces. + if (g_stencilPassOpBits == BGFX_STENCIL_OP_PASS_Z_DECR + || g_stencilPassOpBits == BGFX_STENCIL_OP_PASS_Z_DECRSAT) + { + return; + } + + // No face culling, two-sided stencil matching the side-wall submit. + uint64_t state = BGFX_STATE_DEPTH_TEST_LEQUAL; + bgfx::setState(state); + const uint32_t commonBits = g_stencilFuncBits + | BGFX_STENCIL_FUNC_REF(g_stencilRef & 0xFF) + | BGFX_STENCIL_FUNC_RMASK(g_stencilReadMask & 0xFF) + | g_stencilFailOpBits + | g_stencilZFailOpBits; + bgfx::setStencil(commonBits | BGFX_STENCIL_OP_PASS_Z_DECRSAT, + commonBits | BGFX_STENCIL_OP_PASS_Z_INCRSAT); + + bgfx::setVertexBuffer(0, g_currentBgfxVB); + bgfx::setIndexBuffer(&tib); + bgfx::setTransform(g_bgfxWorld); + + bgfx::submit(kBgfxShadowVolumeView, g_shadowVolumeProgram); +} + +void BgfxBackend::Submit_Shadow_Volume_Triangulated_Caps( + unsigned strip_start_vertex, + const short * local_cap_indices, + unsigned cap_index_count) +{ + if (!g_bgfxInitialized + || !g_shadowVolumeActive + || !bgfx::isValid(g_shadowVolumeProgram) + || !bgfx::isValid(g_currentBgfxVB) + || local_cap_indices == nullptr + || cap_index_count < 3) + { + return; + } + + // front cap + back cap (reversed winding) + const unsigned total_indices = cap_index_count * 2; + + if (bgfx::getAvailTransientIndexBuffer(total_indices) < total_indices) + { + return; + } + + bgfx::TransientIndexBuffer tib; + bgfx::allocTransientIndexBuffer(&tib, total_indices); + uint16_t * idx = reinterpret_cast(tib.data); + + // Front cap: local indices map to caster-level verts at + // strip_start + 2*local (even offsets). Winding preserved. + for (unsigned i = 0; i + 2 < cap_index_count; i += 3) + { + *idx++ = static_cast( + strip_start_vertex + 2 * static_cast(local_cap_indices[i + 0])); + *idx++ = static_cast( + strip_start_vertex + 2 * static_cast(local_cap_indices[i + 1])); + *idx++ = static_cast( + strip_start_vertex + 2 * static_cast(local_cap_indices[i + 2])); + } + // Back cap: local indices map to extruded verts at + // strip_start + 2*local + 1 (odd offsets). Winding REVERSED so + // outward normal points away from light (opposite of front cap). + for (unsigned i = 0; i + 2 < cap_index_count; i += 3) + { + *idx++ = static_cast( + strip_start_vertex + 2 * static_cast(local_cap_indices[i + 0]) + 1); + *idx++ = static_cast( + strip_start_vertex + 2 * static_cast(local_cap_indices[i + 2]) + 1); + *idx++ = static_cast( + strip_start_vertex + 2 * static_cast(local_cap_indices[i + 1]) + 1); + } + + // Mirror the side-wall submit's state. + uint64_t state = BGFX_STATE_DEPTH_TEST_LEQUAL; + if (g_cullModeBits == 1) state |= BGFX_STATE_CULL_CW; + else if (g_cullModeBits == 2) state |= BGFX_STATE_CULL_CCW; + bgfx::setState(state); + bgfx::setStencil(g_shadowStencilFront, g_shadowStencilBack); + + bgfx::setVertexBuffer(0, g_currentBgfxVB); + bgfx::setIndexBuffer(&tib); + bgfx::setTransform(g_bgfxWorld); + + bgfx::submit(kBgfxShadowVolumeView, g_shadowVolumeProgram); +} + +void BgfxBackend::Apply_Stencil_Shadow_Darken(unsigned shadow_color, + unsigned stencil_read_mask, + unsigned stencil_ref) +{ + DX8Backend::Apply_Stencil_Shadow_Darken(shadow_color, stencil_read_mask, stencil_ref); + + if (!g_bgfxInitialized || !bgfx::isValid(g_shadowApplyProgram)) + return; + + bgfx::TransientVertexBuffer tvb; + bgfx::TransientIndexBuffer tib; + bgfx::VertexLayout layout; + layout.begin().add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float).end(); + + if (!bgfx::allocTransientBuffers(&tvb, layout, 4, &tib, 6)) + return; + + float * verts = (float *)tvb.data; + verts[0] = -1.0f; verts[1] = -1.0f; verts[2] = 0.0f; + verts[3] = 1.0f; verts[4] = -1.0f; verts[5] = 0.0f; + verts[6] = -1.0f; verts[7] = 1.0f; verts[8] = 0.0f; + verts[9] = 1.0f; verts[10] = 1.0f; verts[11] = 0.0f; + + uint16_t * idx = (uint16_t *)tib.data; + idx[0] = 0; idx[1] = 1; idx[2] = 2; + idx[3] = 2; idx[4] = 1; idx[5] = 3; + + bgfx::setVertexBuffer(0, &tvb); + bgfx::setIndexBuffer(&tib); + + float color[4]; + color[0] = static_cast((shadow_color >> 16) & 0xFF) / 255.0f; + color[1] = static_cast((shadow_color >> 8) & 0xFF) / 255.0f; + color[2] = static_cast((shadow_color ) & 0xFF) / 255.0f; + color[3] = static_cast((shadow_color >> 24) & 0xFF) / 255.0f; + if (bgfx::isValid(g_uShadowColor)) + bgfx::setUniform(g_uShadowColor, color); + + uint64_t state = BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A + | BGFX_STATE_DEPTH_TEST_ALWAYS + | BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_DST_COLOR, BGFX_STATE_BLEND_ZERO); + bgfx::setState(state); + + uint32_t stencil = BGFX_STENCIL_TEST_LEQUAL + | BGFX_STENCIL_FUNC_REF(stencil_ref & 0xFF) + | BGFX_STENCIL_FUNC_RMASK(stencil_read_mask & 0xFF) + | BGFX_STENCIL_OP_FAIL_S_KEEP + | BGFX_STENCIL_OP_FAIL_Z_KEEP + | BGFX_STENCIL_OP_PASS_Z_KEEP; + bgfx::setStencil(stencil); + + bgfx::submit(kBgfxShadowApplyView, g_shadowApplyProgram); +} + +void BgfxBackend::Set_Stencil_Enable(bool enable) +{ + DX8Backend::Set_Stencil_Enable(enable); + g_stencilEnabled = enable; + UpdateShadowStencilState(); +} + +void BgfxBackend::Set_Stencil_Func(CompareFunc f) +{ + DX8Backend::Set_Stencil_Func(f); + g_stencilFuncBits = MapCmpFuncToBgfxStencilTest(static_cast(f)); + UpdateShadowStencilState(); +} + +void BgfxBackend::Set_Stencil_Ref(unsigned ref) +{ + DX8Backend::Set_Stencil_Ref(ref); + g_stencilRef = ref; + UpdateShadowStencilState(); +} + +void BgfxBackend::Set_Stencil_Mask(unsigned mask) +{ + DX8Backend::Set_Stencil_Mask(mask); + g_stencilReadMask = mask; + UpdateShadowStencilState(); +} + +void BgfxBackend::Set_Stencil_Write_Mask(unsigned mask) +{ + DX8Backend::Set_Stencil_Write_Mask(mask); + UpdateShadowStencilState(); +} + +void BgfxBackend::Set_Stencil_Pass_Op(StencilOp op) +{ + DX8Backend::Set_Stencil_Pass_Op(op); + g_stencilPassOpBits = MapStencilOpToBgfxPassZ(static_cast(op)); + UpdateShadowStencilState(); +} + +void BgfxBackend::Set_Stencil_Fail_Op(StencilOp op) +{ + DX8Backend::Set_Stencil_Fail_Op(op); + g_stencilFailOpBits = MapStencilOpToBgfxFailS(static_cast(op)); + UpdateShadowStencilState(); +} + +void BgfxBackend::Set_Stencil_ZFail_Op(StencilOp op) +{ + DX8Backend::Set_Stencil_ZFail_Op(op); + g_stencilZFailOpBits = MapStencilOpToBgfxFailZ(static_cast(op)); + UpdateShadowStencilState(); +} + +void BgfxBackend::Set_Cull_Mode(CullMode mode) +{ + DX8Backend::Set_Cull_Mode(mode); + switch (mode) + { + case RB_CULL_CW: g_cullModeBits = 1; break; + case RB_CULL_CCW: g_cullModeBits = 2; break; + case RB_CULL_NONE: + default: g_cullModeBits = 0; break; + } +} + +void BgfxBackend::Set_Depth_Func(CompareFunc func) +{ + DX8Backend::Set_Depth_Func(func); + static const uint64_t kDepthMap[] = { + 0, // 0 (unused) + BGFX_STATE_DEPTH_TEST_NEVER, // RB_CMP_NEVER = 1 + BGFX_STATE_DEPTH_TEST_LESS, // RB_CMP_LESS = 2 + BGFX_STATE_DEPTH_TEST_EQUAL, // RB_CMP_EQUAL = 3 + BGFX_STATE_DEPTH_TEST_LEQUAL, // RB_CMP_LESS_EQUAL = 4 + BGFX_STATE_DEPTH_TEST_GREATER, // RB_CMP_GREATER = 5 + BGFX_STATE_DEPTH_TEST_NOTEQUAL, // RB_CMP_NOT_EQUAL = 6 + BGFX_STATE_DEPTH_TEST_GEQUAL, // RB_CMP_GREATER_EQUAL = 7 + BGFX_STATE_DEPTH_TEST_ALWAYS, // RB_CMP_ALWAYS = 8 + }; + const unsigned idx = static_cast(func); + if (idx < 9) + { + g_currentBgfxState &= ~BGFX_STATE_DEPTH_TEST_MASK; + g_currentBgfxState |= kDepthMap[idx]; + } +} + void BgfxBackend::Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass * ztexture) { DX8Backend::Set_Render_Target_With_Z(texture, ztexture); @@ -2584,6 +3670,7 @@ void BgfxBackend::Clear_State_Overrides() g_blendOverrideActive = false; g_atestOverrideActive = false; g_suppressBgfxDraw = false; + g_colorWriteOverride = -1; g_currentTexcoordSelect[0] = 0.0f; g_currentTexcoordSelect[1] = 0.0f; } @@ -2727,6 +3814,7 @@ void SubmitEngineDraw(unsigned short start_index, { if (g_suppressBgfxDraw) { + bgfx::discard(BGFX_DISCARD_ALL); return; } // Detect when the engine has restored the back buffer. The water/shadow @@ -2795,7 +3883,17 @@ void SubmitEngineDraw(unsigned short start_index, std::memcpy(g_bgfxCameraProj, g_bgfxProj, sizeof(g_bgfxCameraProj)); g_bgfxCameraCaptured = true; } + // CSM: update light transform every frame so + // shadows follow the camera as it pans/zooms. + if (!g_shadowLightCaptured) + { + UpdateShadowLightTransform(); + } bgfx::setViewTransform(kBgfxEngineView, g_bgfxView, g_bgfxProj); + // Shadow-volume view shares the engine camera; push the same + // view+proj so the extrusion geometry lands where the opaque + // geometry in view 1 landed. + bgfx::setViewTransform(kBgfxShadowVolumeView, g_bgfxView, g_bgfxProj); g_bgfxViewProjDirty = false; } // During RTT, push the current (reflected/shadow) view+proj to the RTT view. @@ -2881,75 +3979,8 @@ void SubmitEngineDraw(unsigned short start_index, // neighbor mip selection, breaking mipmap smoothing and making // distant terrain patches look blocky. Let bgfx pick the best // filter it can for each texture. - if (bgfx::isValid(g_sTex0)) - { - const bgfx::TextureHandle bound = - bgfx::isValid(g_currentBgfxTexture0) - ? g_currentBgfxTexture0 - : g_defaultWhiteTexture; - if (bgfx::isValid(bound)) - { - bgfx::setTexture(0, g_sTex0, bound, g_currentSamplerFlags0); - } - } - if (bgfx::isValid(g_sTex1)) - { - const bgfx::TextureHandle bound = - bgfx::isValid(g_currentBgfxTexture1) - ? g_currentBgfxTexture1 - : g_defaultWhiteTexture; - if (bgfx::isValid(bound)) - { - bgfx::setTexture(1, g_sTex1, bound, g_currentSamplerFlags1); - } - } - if (bgfx::isValid(g_sTex2)) - { - const bgfx::TextureHandle bound = - bgfx::isValid(g_currentBgfxTexture2) - ? g_currentBgfxTexture2 - : g_defaultWhiteTexture; - if (bgfx::isValid(bound)) - { - bgfx::setTexture(2, g_sTex2, bound, g_currentSamplerFlags2); - } - } - if (bgfx::isValid(g_sTex3)) - { - const bgfx::TextureHandle bound = - bgfx::isValid(g_currentBgfxTexture3) - ? g_currentBgfxTexture3 - : g_defaultWhiteTexture; - if (bgfx::isValid(bound)) - { - bgfx::setTexture(3, g_sTex3, bound, g_currentSamplerFlags3); - } - } - - // push the material diffuse (team color + opacity) so - // the fragment shader can tint output. Updated by Set_Material. - if (bgfx::isValid(g_uMatDiffuse)) - { - bgfx::setUniform(g_uMatDiffuse, g_currentMatDiffuse); - } - - // Push TSS operation uniforms so the uber fragment shader - // knows how to combine texture stages. - if (bgfx::isValid(g_uTssOps0)) - { - bgfx::setUniform(g_uTssOps0, g_currentTssOps0); - } - if (bgfx::isValid(g_uTssOps1)) - { - bgfx::setUniform(g_uTssOps1, g_currentTssOps1); - } - // Push alpha test reference. Previously created but never - // set per-draw — now extracted from ShaderClass in Set_Shader. - if (bgfx::isValid(g_uAtestParams)) - { - float atestParams[4] = { g_currentAtestRef, 0.0f, 0.0f, 0.0f }; - bgfx::setUniform(g_uAtestParams, atestParams); - } + BindTextureStages(); + UploadMaterialUniforms(); // Read current D3D light state per-draw. Set_Light_Environment and // Set_Ambient capture some paths, but many callers set lights via // DX8Wrapper::Set_DX8_Light or Apply_Render_State_Changes directly, @@ -2989,7 +4020,85 @@ void SubmitEngineDraw(unsigned short start_index, if (bgfx::isValid(g_uSceneAmbient)) bgfx::setUniform(g_uSceneAmbient, g_currentSceneAmbient); if (bgfx::isValid(g_uLightingEnabled)) - bgfx::setUniform(g_uLightingEnabled, g_currentLightingEnabled); + { + // TheSuperHackers @bugfix bobtista 15/04/2026 force lighting off + // for additive-blend draws. Particle / dazzle / scanner sprites + // bake their per-vertex intensity attenuation into vertex diffuse + // (DazzleRenderObjClass and the particle system do this). The + // uber-shader's lit branch ignores vertex diffuse and outputs + // tex * lit_color, which renders these effects at full intensity + // even when they should be invisible — producing bright white + // halos around things like the microwave-scanner dome. + const uint64_t blendBitsForLight = g_currentBgfxState & BGFX_STATE_BLEND_MASK; + const uint64_t kAddONE_ONE = BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_ONE, + BGFX_STATE_BLEND_ONE); + const uint64_t kAddSA_ONE = BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_SRC_ALPHA, + BGFX_STATE_BLEND_ONE); + // TheSuperHackers @fix bobtista 16/04/2026 Also force lighting off for alpha-blend + // particles (SRC_ALPHA/INV_SRC_ALPHA) when priColorOp is SELECTARG1 (GRADIENT_DISABLE), + // so the lit path does not ignore vertex color on particles that bake intensity into it. + const uint64_t kAlphaSA_ISA = BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_SRC_ALPHA, + BGFX_STATE_BLEND_INV_SRC_ALPHA); + if (blendBitsForLight == kAddONE_ONE || blendBitsForLight == kAddSA_ONE + || (blendBitsForLight == kAlphaSA_ISA && g_currentTssOps0[0] < 1.5f)) + { + float forced[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + bgfx::setUniform(g_uLightingEnabled, forced); + } + else + { + bgfx::setUniform(g_uLightingEnabled, g_currentLightingEnabled); + } + } + + // Detect shroud pass: D3D8 uses TCI_CAMERASPACEPOSITION + depth func + // EQUAL to render a multiplicative shroud overlay. Both conditions must + // be true to avoid false positives from other effects that set TCI bits. + { + bool shroudDetected = false; + unsigned depthFunc = DX8Wrapper::Get_DX8_Render_State(D3DRS_ZFUNC); + if (depthFunc == D3DCMP_EQUAL) + { + for (unsigned stg = 0; stg < 4 && !shroudDetected; ++stg) + { + unsigned tci = DX8Wrapper::Get_DX8_Texture_Stage_State(stg, D3DTSS_TEXCOORDINDEX); + if (tci & D3DTSS_TCI_CAMERASPACEPOSITION) + { + shroudDetected = true; + g_currentTexcoordSelect[2] = 1.0f; + // Extract shroud offset+scale by cancelling inv(view) + // from the texture matrix: view * texMtx = T * S. + D3DMATRIX texMtx; + DX8Wrapper::_Get_DX8_Transform( + static_cast(D3DTS_TEXTURE0 + stg), texMtx); + D3DMATRIX viewMtx; + DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, viewMtx); + // Manual 4x4 multiply: ts = view * texMtx (D3D row-major) + D3DMATRIX ts; + for (int rr = 0; rr < 4; rr++) + for (int cc = 0; cc < 4; cc++) + { + ts.m[rr][cc] = 0; + for (int k = 0; k < 4; k++) + ts.m[rr][cc] += viewMtx.m[rr][k] * texMtx.m[k][cc]; + } + // ts = T * S: scale on diagonal, translated scale in row 3 + float shroudParams[4] = { + (ts.m[0][0] != 0.0f) ? ts.m[3][0] / ts.m[0][0] : 0.0f, + (ts.m[1][1] != 0.0f) ? ts.m[3][1] / ts.m[1][1] : 0.0f, + ts.m[0][0], + ts.m[1][1] + }; + if (bgfx::isValid(g_uShroudParams)) + bgfx::setUniform(g_uShroudParams, shroudParams); + } + } + } + if (!shroudDetected) + { + g_currentTexcoordSelect[2] = 0.0f; + } + } if (bgfx::isValid(g_uTexcoordSelect)) bgfx::setUniform(g_uTexcoordSelect, g_currentTexcoordSelect); @@ -3035,8 +4144,33 @@ void SubmitEngineDraw(unsigned short start_index, g_waterOverrideActive = false; } + if (g_shadowVolumeActive && bgfx::isValid(g_shadowVolumeProgram)) + { + bgfx::discard(BGFX_DISCARD_ALL); + return; + } + bgfx::setState(state); + // CSM: compute light view-projection once for both + // receiver uniform and caster submit. + float lightVP[16]; + bx::mtxMul(lightVP, g_shadowLightView, g_shadowLightProj); + + if (bgfx::isValid(g_uShadowLightViewProj) + && bgfx::isValid(g_shadowMapDepth) + && bgfx::isValid(g_sShadowMap)) + { + // Compose model * lightView * lightProj per-draw. + // Use the CORRECT model matrix: g_bgfxSortWorldRaw for sorted + // draws (no camera view baked in), g_bgfxWorld for opaque. + float shadowMVP[16]; + const float * modelMtx = g_inSortFlush ? g_bgfxSortWorldRaw : worldMtx; + bx::mtxMul(shadowMVP, modelMtx, lightVP); + bgfx::setUniform(g_uShadowLightViewProj, shadowMVP); + bgfx::setTexture(4, g_sShadowMap, g_shadowMapDepth); + } + // The tree / grass sway shader takes over the program slot // and uploads its own constants when active. Otherwise fall back // to whatever ShaderClass picked (g_currentBgfxProgram). @@ -3045,13 +4179,68 @@ void SubmitEngineDraw(unsigned short start_index, { program = g_treeProgram; if (bgfx::isValid(g_uSwayTable)) - bgfx::setUniform(g_uSwayTable, g_currentSwayTable, 11); + bgfx::setUniform(g_uSwayTable, g_currentSwayTable, kSwayTableEntries); if (bgfx::isValid(g_uShroudOffset)) bgfx::setUniform(g_uShroudOffset, g_currentShroudOffset); if (bgfx::isValid(g_uShroudScale)) bgfx::setUniform(g_uShroudScale, g_currentShroudScale); } bgfx::submit(submitView, program); + + // CSM caster pass. Submit opaque and sort-flush geometry + // to the shadow map view (view 8). Skip RTT, water, and effect + // overlay (those are non-shadow-casters by design). bgfx preserves + // the per-view transform (set in UpdateShadowLightTransform) so + // the caster rasterizes from the light's POV. + const bool isShadowCaster = + (submitView == kBgfxEngineView || submitView == kBgfxEngineSortView); + const bool hasVB = g_currentUseTransientVB || bgfx::isValid(g_currentBgfxVB); + if (isShadowCaster + && bgfx::isValid(g_shadowCasterProgram) + && hasVB) + { + // Re-bind VB/IB/transform (bgfx consumes them per-submit). + if (g_currentUseTransientVB) + { + if (g_inSortFlush) + bgfx::setVertexBuffer(0, &g_currentTransientVB); + else + bgfx::setVertexBuffer(0, &g_currentTransientVB, + static_cast(g_currentIBOffset), + vertex_count); + } + else + { + bgfx::setVertexBuffer(0, g_currentBgfxVB, + static_cast(g_currentIBOffset), + vertex_count); + } + if (g_currentUseTransientIB) + bgfx::setIndexBuffer(&g_currentTransientIB, + start_index, + static_cast(polygon_count) * 3); + else + bgfx::setIndexBuffer(g_currentBgfxIB, + start_index, + static_cast(polygon_count) * 3); + // Use the world transform used by the just-issued engine + // submit. For sort-flush draws that's g_bgfxSortWorld (which + // has the sort's batch transform baked in). For regular + // opaque it's g_bgfxWorld. + bgfx::setTransform(worldMtx); + { + float casterMVP[16]; + const float * casterModel = g_inSortFlush ? g_bgfxSortWorldRaw : worldMtx; + bx::mtxMul(casterMVP, casterModel, lightVP); + bgfx::setUniform(g_uShadowLightViewProj, casterMVP); + } + const uint64_t casterState = + BGFX_STATE_WRITE_Z + | BGFX_STATE_DEPTH_TEST_LESS + | BGFX_STATE_CULL_CW; + bgfx::setState(casterState); + bgfx::submit(kBgfxShadowMapView, g_shadowCasterProgram); + } } } @@ -3089,7 +4278,26 @@ void BgfxBackend::Draw_Triangles(unsigned int buffer_type, SubmitEngineDraw(start_index, polygon_count, min_vertex_index, vertex_count); } -// Draw_Strip, the programmable pipeline (Set_Vertex_Shader, Set_Pixel_Shader, +// TheSuperHackers @feature bobtista 16/04/2026 Draw_Strip override so strip-based +// geometry (e.g. water tracks) goes through bgfx instead of silently falling back +// to the DX8-only base class. +void BgfxBackend::Draw_Strip(unsigned short start_index, + unsigned short index_count, + unsigned short min_vertex_index, + unsigned short vertex_count) +{ + DX8Backend::Draw_Strip(start_index, index_count, min_vertex_index, vertex_count); + + if (g_skipNextSubmitEngineDraw) + { + g_skipNextSubmitEngineDraw = false; + return; + } + + SubmitEngineDraw(start_index, index_count, min_vertex_index, vertex_count); +} + +// The programmable pipeline (Set_Vertex_Shader, Set_Pixel_Shader, // Set_Vertex_Shader_Constant, Set_Pixel_Shader_Constant), and render targets // (Create_Render_Target, Set_Render_Target_With_Z, Is_Render_To_Texture, // Set_Shadow_Map, Get_Shadow_Map) are inherited from DX8Backend. diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h index 70a13e6a77c..470b2b5e1b4 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h @@ -48,15 +48,15 @@ class BgfxBackend : public DX8Backend // Shutdown tears down all bgfx resources before bgfx::shutdown. // Both override DX8Backend's empty stubs. - virtual void Initialize(void * hwnd, int width, int height); - virtual void Shutdown(); + virtual void Initialize(void * hwnd, int width, int height) override; + virtual void Shutdown() override; // -- Frame lifecycle ------------------------------------------------------ // // Begin_Scene touches the bgfx views; End_Scene calls bgfx::frame to advance the swap chain. - virtual void Begin_Scene(); - virtual void End_Scene(bool flip_frame); + virtual void Begin_Scene() override; + virtual void End_Scene(bool flip_frame) override; // -- Vertex / index buffers ----------------------------------------------- // @@ -64,11 +64,11 @@ class BgfxBackend : public DX8Backend // hit (or miss) for the current draw. The base DX8Backend forwards // are still called so the d3d8 device sees the binding too. - virtual void Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int stream); - virtual void Set_Vertex_Buffer(const DynamicVBAccessClass & vba); - virtual void Set_Index_Buffer(const IndexBufferClass * ib, unsigned short index_base_offset); - virtual void Set_Index_Buffer(const DynamicIBAccessClass & iba, unsigned short index_base_offset); - virtual void Set_Index_Buffer_Index_Offset(unsigned int offset); + virtual void Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int stream) override; + virtual void Set_Vertex_Buffer(const DynamicVBAccessClass & vba) override; + virtual void Set_Index_Buffer(const IndexBufferClass * ib, unsigned short index_base_offset) override; + virtual void Set_Index_Buffer(const DynamicIBAccessClass & iba, unsigned short index_base_offset) override; + virtual void Set_Index_Buffer_Index_Offset(unsigned int offset) override; // write-side capture hooks. DX8Backend inherits the // empty default from IRenderBackend; BgfxBackend captures the data @@ -78,33 +78,33 @@ class BgfxBackend : public DX8Backend virtual void Capture_Vertex_Data(const VertexBufferClass * vb, const void * data, - unsigned int size_bytes); + unsigned int size_bytes) override; virtual void Capture_Index_Data(const IndexBufferClass * ib, const void * data, - unsigned int size_bytes); + unsigned int size_bytes) override; virtual void Capture_Dynamic_Vertex_Data(const DynamicVBAccessClass * vba, const void * data, - unsigned int size_bytes); + unsigned int size_bytes) override; virtual void Capture_Dynamic_Index_Data(const DynamicIBAccessClass * iba, const void * data, - unsigned int size_bytes); + unsigned int size_bytes) override; virtual void Capture_Vertex_Sub_Range(const VertexBufferClass * vb, const void * data, unsigned int start_vertex, - unsigned int size_bytes); + unsigned int size_bytes) override; virtual void Capture_Index_Sub_Range(const IndexBufferClass * ib, const void * data, unsigned int start_index, - unsigned int size_bytes); - virtual void Begin_Sorted_Batch_Pass(); - virtual void End_Sorted_Batch_Pass(); + unsigned int size_bytes) override; + virtual void Begin_Sorted_Batch_Pass() override; + virtual void End_Sorted_Batch_Pass() override; virtual void Capture_Sorted_Batch_Transforms(const Matrix4x4 & world, - const Matrix4x4 & view); - virtual void Capture_Sorted_Batch_Light(const D3DLIGHT8 & light, bool enabled); + const Matrix4x4 & view) override; + virtual void Capture_Sorted_Batch_Light(const D3DLIGHT8 & light, bool enabled) override; virtual void Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, const DynamicIBAccessClass & dyn_ib, unsigned short polygon_count, - unsigned short vertex_count); + unsigned short vertex_count) override; // -- State: shaders, materials, textures --------------------------------- // @@ -112,28 +112,65 @@ class BgfxBackend : public DX8Backend // Set_Texture caches the texture handle. Both call the base DX8Backend // forward so the dx8 device also sees the change. - virtual void Set_Shader(const ShaderClass & shader); - virtual void Set_Material(const VertexMaterialClass * material); - virtual void Set_Texture(unsigned int stage, TextureBaseClass * texture); - virtual void Set_Light_Environment(LightEnvironmentClass * light_env); - virtual void Set_Ambient(const Vector3 & color); - virtual void Override_Blend(unsigned srcBlend, unsigned dstBlend); - virtual void Override_Alpha_Test(bool enable, unsigned ref, unsigned func); - virtual void Override_Alpha_Blend_Enable(bool enable); - virtual void Override_Texcoord_Index(unsigned stage, unsigned uvIndex); - virtual void Override_Terrain_Blend(bool enable); - virtual void Override_Material_Opacity(float opacity); - virtual void Begin_Effect_Overlay(); - virtual void End_Effect_Overlay(); + virtual void Set_Shader(const ShaderClass & shader) override; + virtual void Set_Material(const VertexMaterialClass * material) override; + virtual void Set_Texture(unsigned int stage, TextureBaseClass * texture) override; + virtual void Set_Light_Environment(LightEnvironmentClass * light_env) override; + virtual void Set_Ambient(const Vector3 & color) override; + virtual void Override_Blend(unsigned srcBlend, unsigned dstBlend) override; + virtual void Override_Alpha_Test(bool enable, unsigned ref, unsigned func) override; + virtual void Override_Alpha_Blend_Enable(bool enable) override; + virtual void Override_Texcoord_Index(unsigned stage, unsigned uvIndex) override; + virtual void Override_Terrain_Blend(bool enable) override; + virtual void Override_Material_Opacity(float opacity) override; + virtual void Begin_Effect_Overlay() override; + virtual void End_Effect_Overlay() override; // Tree / grass sway shader hooks (see IRenderBackend.h). virtual void Set_Tree_Shader_Constants(const float swayTable[11][4], const float shroudOffset[4], - const float shroudScale[4]); - virtual void Set_Tree_Vertex_Shader_Active(bool active); - virtual void Set_Color_Write_Enable(bool red, bool green, bool blue, bool alpha); - virtual void Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass * ztexture = nullptr); - virtual void Clear_State_Overrides(); + const float shroudScale[4]) override; + virtual void Set_Tree_Vertex_Shader_Active(bool active) override; + virtual void Set_Color_Write_Enable(bool red, bool green, bool blue, bool alpha) override; + virtual void Set_Color_Write_Mask(unsigned mask) override; + virtual void Skip_Next_Bgfx_Submit() override; + virtual void Set_Shadow_Volume_Shader_Active(bool active) override; + virtual void Apply_Stencil_Shadow_Darken(unsigned shadow_color, + unsigned stencil_read_mask, + unsigned stencil_ref) override; + virtual void Submit_Shadow_Volume_Caps(unsigned strip_start_vertex, + unsigned num_silhouette_verts) override; + virtual void Submit_Shadow_Volume_Triangulated_Caps( + unsigned strip_start_vertex, + const short * local_cap_indices, + unsigned cap_index_count) override; + virtual void Set_Shadow_Light_Position(float x, float y, float z) override; + virtual void Capture_Shroud_Texture(TextureClass * dst_texture, + const void * pixel_data, + unsigned dst_width, + unsigned dst_height, + unsigned src_width, + unsigned src_height, + unsigned dst_x, + unsigned dst_y, + unsigned pitch, + WW3DFormat format) override; + virtual void Set_Texture_Factor(unsigned argb) override; + + virtual void Set_Depth_Func(CompareFunc func) override; + + // bgfx stencil state capture. + virtual void Set_Stencil_Enable(bool enable) override; + virtual void Set_Stencil_Func(CompareFunc f) override; + virtual void Set_Stencil_Ref(unsigned ref) override; + virtual void Set_Stencil_Mask(unsigned mask) override; + virtual void Set_Stencil_Write_Mask(unsigned mask) override; + virtual void Set_Stencil_Pass_Op(StencilOp op) override; + virtual void Set_Stencil_Fail_Op(StencilOp op) override; + virtual void Set_Stencil_ZFail_Op(StencilOp op) override; + virtual void Set_Cull_Mode(CullMode mode) override; + virtual void Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass * ztexture = nullptr) override; + virtual void Clear_State_Overrides() override; // -- Transforms ----------------------------------------------------------- // @@ -141,11 +178,11 @@ class BgfxBackend : public DX8Backend // column-major form, then forwards to the base DX8Backend so the dx8 // device also gets the matrices set. - virtual void Set_Transform(TransformKind transform, const Matrix4x4 & m); - virtual void Set_Transform(TransformKind transform, const Matrix3D & m); - virtual void Set_World_Identity(); - virtual void Set_View_Identity(); - virtual void Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & matrix, float znear, float zfar); + virtual void Set_Transform(TransformKind transform, const Matrix4x4 & m) override; + virtual void Set_Transform(TransformKind transform, const Matrix3D & m) override; + virtual void Set_World_Identity() override; + virtual void Set_View_Identity() override; + virtual void Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & matrix, float znear, float zfar) override; // -- Draw calls ----------------------------------------------------------- // @@ -156,12 +193,16 @@ class BgfxBackend : public DX8Backend virtual void Draw_Triangles(unsigned short start_index, unsigned short polygon_count, unsigned short min_vertex_index, - unsigned short vertex_count); + unsigned short vertex_count) override; virtual void Draw_Triangles(unsigned int buffer_type, unsigned short start_index, unsigned short polygon_count, unsigned short min_vertex_index, - unsigned short vertex_count); + unsigned short vertex_count) override; + virtual void Draw_Strip(unsigned short start_index, + unsigned short index_count, + unsigned short min_vertex_index, + unsigned short vertex_count) override; // Everything else (Is_Device_Lost, Has_Stencil, Get_Back_Buffer*, // Set_Gamma, Flip_To_Primary, Clear, Set_Viewport, @@ -169,7 +210,7 @@ class BgfxBackend : public DX8Backend // Apply_Default_State, Invalidate_Cached_Render_States, Set_Blend_*, // Set_Color_Write_Enable, Set_Alpha_Blend_Enable, Show/Set_Hardware_Cursor*, // Set_Stencil_*, Get_Transform, Set_Light*, Set/Get_Ambient, - // Set/Get_Fog*, Set/Get_Light_Environment, Draw_Strip, Set_Vertex_Shader, + // Set/Get_Fog*, Set/Get_Light_Environment, Set_Vertex_Shader, // Set_Pixel_Shader, *_Constant, Create_Render_Target, // Is_Render_To_Texture, Set/Get_Shadow_Map) // is inherited from DX8Backend and forwards to DX8Wrapper unchanged. diff --git a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt index 4e919451fd3..f72c7fe2caf 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt @@ -267,6 +267,16 @@ if(GGC_RENDER_BACKEND STREQUAL "bgfx") # Trees.nvv (grass/blade sway vertex shader). Reuses fs_uber for # the fragment side. ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_trees.sc) + # TheSuperHackers @refactor bobtista 15/04/2026 stencil + # shadow volume shader pair. XYZ-only verts, no color output. + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_shadow_volume.sc) + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_shadow_volume.sc) + # shadow darkening apply pass (fullscreen quad blend). + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_shadow_apply.sc) + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_shadow_apply.sc) + # CSM (cascaded shadow maps) — depth-only caster pass. + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_shadow_caster.sc) + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_shadow_caster.sc) target_link_libraries(corei_ww3d2 INTERFACE ggc_bgfx_shaders) endif() diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp index 42a59adad7b..2d4a90c32d5 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp @@ -408,6 +408,11 @@ void DX8Backend::Set_Texture_Factor(unsigned argb) DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR, argb); } +void DX8Backend::Set_Cull_Mode(CullMode mode) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_CULLMODE, static_cast(mode)); +} + // -- Transforms -------------------------------------------------------------- void DX8Backend::Set_Transform(TransformKind transform, const Matrix4x4 & m) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h index b8e9418ec08..66902916c68 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h @@ -106,6 +106,7 @@ class DX8Backend : public IRenderBackend virtual void Set_Color_Write_Mask(unsigned mask); virtual void Set_Lighting_Enable(bool enable); virtual void Set_Texture_Factor(unsigned argb); + virtual void Set_Cull_Mode(CullMode mode); // -- Transforms ----------------------------------------------------------- diff --git a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h index 6e54c429590..e3cd0f23905 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h @@ -126,6 +126,14 @@ enum FillMode RB_FILL_SOLID = 3 // D3DFILL_SOLID }; +// Values match D3DCULL_* so DX8Backend can cast directly. +enum CullMode +{ + RB_CULL_NONE = 1, // D3DCULL_NONE + RB_CULL_CW = 2, // D3DCULL_CW + RB_CULL_CCW = 3 // D3DCULL_CCW +}; + enum StencilOp { // Values match D3DSTENCILOP_* 1..8 directly so DX8Backend can cast. @@ -374,6 +382,7 @@ class IRenderBackend virtual void Set_Color_Write_Mask(unsigned mask) = 0; virtual void Set_Lighting_Enable(bool enable) = 0; virtual void Set_Texture_Factor(unsigned argb) = 0; + virtual void Set_Cull_Mode(CullMode mode) = 0; // TheSuperHackers @refactor bobtista 14/04/2026 tree / // grass sway vertex shader hooks. DX8 backends ignore these (they @@ -451,6 +460,92 @@ class IRenderBackend unsigned short min_vertex_index, unsigned short vertex_count) = 0; + // TheSuperHackers @refactor bobtista 15/04/2026 lets the + // caller request that the very next Draw_Triangles dispatches only + // to the DX8 reference path, skipping the bgfx submit. Used by + // stencil shadow volume passes that have no bgfx shader/state yet + // and would otherwise render as garbage geometry on the bgfx view. + // No-op in non-bgfx backends. + virtual void Skip_Next_Bgfx_Submit() {} + + // TheSuperHackers @refactor bobtista 15/04/2026 toggles the + // stencil shadow volume program + state override. When active, bgfx + // submits shadow extrusion geometry using vs_shadow_volume/ + // fs_shadow_volume with color writes disabled and the engine's + // stencil state mapped into bgfx state bits. No-op in non-bgfx + // backends. The caller sets active before the shadow volume draw + // and clears it immediately after. + virtual void Set_Shadow_Volume_Shader_Active(bool /*active*/) {} + + // TheSuperHackers @refactor bobtista 15/04/2026 fullscreen + // shadow darkening pass. Draws a screen-space quad with stencil + // test ref<=stencil && (stencil & read_mask) and DEST_COLOR*SRC + // blend so stenciled pixels multiply against the shadow color. + // shadow_color is ARGB like the engine's getShadowColor() return. + // No-op in non-bgfx backends (DX8 already draws this via raw m_pDev + // calls in W3DVolumetricShadow::renderStencilShadows). + virtual void Apply_Stencil_Shadow_Darken(unsigned /*shadow_color*/, + unsigned /*stencil_read_mask*/, + unsigned /*stencil_ref*/) {} + + // TheSuperHackers @refactor bobtista 15/04/2026 close the + // shadow volume for bgfx rendering. The engine constructs shadow + // volumes as OPEN TUBES (silhouette side walls only, no caps). DX8 + // tolerates this; bgfx/D3D11 doesn't because the stencil algorithm + // on open volumes depends on sub-pixel rasterizer rules that differ + // between the APIs. This hook is called AFTER the side-wall + // Draw_Triangles for a shadow volume, and lets the backend generate + // and submit front+back caps (fan-triangulated silhouette at caster + // level + reversed-winding extruded level) so bgfx's stencil algo + // sees a closed 2-manifold. Single-strip assumption: silhouette + // vertices are at absolute buffer offsets strip_start_vertex + 2*i + // (caster level) and strip_start_vertex + 2*i + 1 (extruded). + // No-op in the DX8 backend — DX8 rendering is unchanged. + virtual void Submit_Shadow_Volume_Caps(unsigned /*strip_start_vertex*/, + unsigned /*num_silhouette_verts*/) {} + + // TheSuperHackers @refactor bobtista 15/04/2026 close the + // shadow volume with caller-provided triangulation. cap_indices[] + // contains N triangles worth of LOCAL silhouette-vertex indices + // (each triangle is 3 shorts, values in [0, num_silhouette_verts)). + // BgfxBackend converts them to absolute VB indices: front cap at + // strip_start_vertex + 2*i, back cap at strip_start_vertex + 2*i + 1 + // with reversed winding. The caller (W3DVolumetricShadow) performs + // ear-clipping on the silhouette projected to 2D perpendicular to + // light, producing a correct triangulation for non-convex casters. + // No-op in DX8 backend. + virtual void Submit_Shadow_Volume_Triangulated_Caps( + unsigned /*strip_start_vertex*/, + const short * /*local_cap_indices*/, + unsigned /*cap_index_count*/) {} + + // TheSuperHackers @refactor bobtista 16/04/2026 CSM: + // the engine's shadow system places the sun at a world-space + // position for shadow casting (from TerrainLighting data). This + // differs from the N.L shading light direction. BgfxBackend uses + // this position for its shadow map ortho projection. + virtual void Set_Shadow_Light_Position(float /*x*/, float /*y*/, float /*z*/) {} + + // TheSuperHackers @feature bobtista 17/04/2026 Shroud texture capture + // for bgfx. The shroud system's destination texture is POOL_DEFAULT + // which bgfx cannot lock. This hook lets W3DShroud push the system- + // memory pixel data and the destination TextureClass pointer so the + // bgfx backend can create/update a matching bgfx texture and wire it + // into the texture cache. dst_width/dst_height are the full destination + // texture dimensions; src_width/src_height are the actual pixel data + // extents; dst_x/dst_y are the offset within the destination where the + // source rect is placed. No-op on DX8Backend. + virtual void Capture_Shroud_Texture(TextureClass * /*dst_texture*/, + const void * /*pixel_data*/, + unsigned /*dst_width*/, + unsigned /*dst_height*/, + unsigned /*src_width*/, + unsigned /*src_height*/, + unsigned /*dst_x*/, + unsigned /*dst_y*/, + unsigned /*pitch*/, + WW3DFormat /*format*/) {} + // ------------------------------------------------------------------------- // Programmable pipeline (GPU vertex / pixel shaders) // ------------------------------------------------------------------------- diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.cpp index 0dccbdb0c12..5299e42bbd1 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.cpp @@ -243,7 +243,7 @@ IndexBufferClass::WriteLockClass::~WriteLockClass() // ---------------------------------------------------------------------------- -IndexBufferClass::AppendLockClass::AppendLockClass(IndexBufferClass* index_buffer_,unsigned start_index, unsigned index_range) +IndexBufferClass::AppendLockClass::AppendLockClass(IndexBufferClass* index_buffer_,unsigned start_index, unsigned index_range, unsigned flags) : index_buffer(index_buffer_), AppendStartIndex(start_index), @@ -261,7 +261,7 @@ IndexBufferClass::AppendLockClass::AppendLockClass(IndexBufferClass* index_buffe start_index*sizeof(unsigned short), index_range*sizeof(unsigned short), (unsigned char**)&indices, - 0)); + flags)); break; case BUFFER_TYPE_SORTING: indices=static_cast(index_buffer)->index_buffer+start_index; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.h index 72bf68fabb5..ac7d82ccacf 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.h @@ -89,7 +89,9 @@ class IndexBufferClass : public RefCountClass unsigned AppendStartIndex; unsigned AppendIndexRange; public: - AppendLockClass(IndexBufferClass* index_buffer,unsigned start_index, unsigned index_range); + // TheSuperHackers @refactor bobtista 15/04/2026 added + // optional `flags` (D3DLOCK_DISCARD / D3DLOCK_NOOVERWRITE). + AppendLockClass(IndexBufferClass* index_buffer,unsigned start_index, unsigned index_range, unsigned flags=0); ~AppendLockClass(); unsigned short* Get_Index_Array() { return indices; } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp index d5e0e12910a..78a95c4b290 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp @@ -232,7 +232,7 @@ VertexBufferClass::WriteLockClass::~WriteLockClass() // // ---------------------------------------------------------------------------- -VertexBufferClass::AppendLockClass::AppendLockClass(VertexBufferClass* VertexBuffer,unsigned start_index, unsigned index_range) +VertexBufferClass::AppendLockClass::AppendLockClass(VertexBufferClass* VertexBuffer,unsigned start_index, unsigned index_range, unsigned flags) : VertexBufferLockClass(VertexBuffer), AppendStartIndex(start_index), @@ -261,7 +261,7 @@ VertexBufferClass::AppendLockClass::AppendLockClass(VertexBufferClass* VertexBuf start_index*VertexBuffer->FVF_Info().Get_FVF_Size(), index_range*VertexBuffer->FVF_Info().Get_FVF_Size(), (unsigned char**)&Vertices, - 0)); // Default (no) flags + flags)); break; case BUFFER_TYPE_SORTING: Vertices=static_cast(VertexBuffer)->VertexBuffer+start_index; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.h index 754a9e75326..f783f7cb139 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.h @@ -98,7 +98,12 @@ class VertexBufferClass : public RefCountClass class AppendLockClass : public VertexBufferLockClass { public: - AppendLockClass(VertexBufferClass* vertex_buffer,unsigned start_index, unsigned index_range); + // TheSuperHackers @refactor bobtista 15/04/2026 added + // optional `flags` (e.g. D3DLOCK_DISCARD / D3DLOCK_NOOVERWRITE) + // for the dynamic shadow buffer's per-batch append pattern. Default + // of 0 keeps existing one-shot DX8VertexBufferClass::Copy callers + // unchanged. + AppendLockClass(VertexBufferClass* vertex_buffer,unsigned start_index, unsigned index_range, unsigned flags=0); ~AppendLockClass(); protected: // TheSuperHackers @refactor bobtista 11/04/2026 diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_shadow_apply.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_shadow_apply.sc new file mode 100644 index 00000000000..f90ed4fe8ca --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_shadow_apply.sc @@ -0,0 +1,13 @@ +// TheSuperHackers @refactor bobtista 15/04/2026 stencil shadow +// apply fragment shader. Emits u_shadowColor straight out; the blend +// state set by the engine (SRC=DEST_COLOR, DEST=ZERO) multiplies this +// against the framebuffer, which darkens stenciled pixels uniformly. + +#include + +uniform vec4 u_shadowColor; + +void main() +{ + gl_FragColor = u_shadowColor; +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_shadow_caster.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_shadow_caster.sc new file mode 100644 index 00000000000..7e8261ba999 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_shadow_caster.sc @@ -0,0 +1,11 @@ +// TheSuperHackers @refactor bobtista 16/04/2026 shadow map +// caster fragment shader. Depth-only pass: the D16 depth buffer gets +// written by the rasterizer from gl_Position.z. Fragment shader just +// needs to exist for bgfx but writes nothing meaningful. + +#include + +void main() +{ + gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_shadow_volume.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_shadow_volume.sc new file mode 100644 index 00000000000..2c7ee248733 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_shadow_volume.sc @@ -0,0 +1,13 @@ +// TheSuperHackers @refactor bobtista 15/04/2026 stencil shadow +// volume fragment shader. The engine disables color writes for the +// volume pass (Set_Color_Write_Mask(0)) so this output is discarded at +// the output-merger; what matters is stencil increment/decrement, which +// is driven by pipeline state, not the shader. We still need to emit +// something for bgfx. + +#include + +void main() +{ + gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc index 530773d999c..ee18b475b86 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc @@ -1,4 +1,4 @@ -$input v_color0, v_texcoord0, v_texcoord1, v_normal +$input v_color0, v_texcoord0, v_texcoord1, v_normal, v_lightspace #include @@ -6,6 +6,8 @@ SAMPLER2D(s_tex0, 0); SAMPLER2D(s_tex1, 1); SAMPLER2D(s_tex2, 2); SAMPLER2D(s_tex3, 3); +// CSM: D16 shadow map with hardware PCF comparison. +SAMPLER2DSHADOW(s_shadowMap, 4); uniform vec4 u_matDiffuse; uniform vec4 u_atestParams; uniform vec4 u_tssOps0; // (priColorOp, priAlphaOp, secColorOp, secAlphaOp) @@ -43,7 +45,7 @@ vec3 applyColorOp(float op, vec3 arg1, vec3 arg2) // MODULATE if (op < 3.5) return arg1 * arg2; // MODULATE2X - if (op < 4.5) return arg1 * arg2 * 2.0; + if (op < 4.5) return min(arg1 * arg2 * 2.0, vec3_splat(1.0)); // ADD if (op < 5.5) return arg1 + arg2; // ADDSIGNED @@ -53,7 +55,7 @@ vec3 applyColorOp(float op, vec3 arg1, vec3 arg2) // BLENDTEXALPHA — handled specially by caller // BLENDCURALPHA — handled specially by caller // ADDSMOOTH - if (op < 10.5) return arg1 + arg2 - arg1 * arg2; + if (op > 9.5 && op < 10.5) return arg1 + arg2 - arg1 * arg2; return arg1; } @@ -62,7 +64,7 @@ float applyAlphaOp(float op, float arg1, float arg2) if (op < 1.5) return arg1; if (op < 2.5) return arg2; if (op < 3.5) return arg1 * arg2; - if (op < 4.5) return arg1 * arg2 * 2.0; + if (op < 4.5) return min(arg1 * arg2 * 2.0, 1.0); if (op < 5.5) return arg1 + arg2; if (op < 6.5) return arg1 + arg2 - 0.5; if (op < 7.5) return arg1 - arg2; @@ -93,6 +95,29 @@ void main() discard; } + vec3 tlsNDC = v_lightspace.xyz / v_lightspace.w; + vec2 tshadowUV = tlsNDC.xy * 0.5 + 0.5; +#if BGFX_SHADER_LANGUAGE_HLSL + tshadowUV.y = 1.0 - tshadowUV.y; + float trefZ = tlsNDC.z; +#else + float trefZ = tlsNDC.z * 0.5 + 0.5; +#endif + if (tshadowUV.x >= 0.0 && tshadowUV.x <= 1.0 + && tshadowUV.y >= 0.0 && tshadowUV.y <= 1.0 + && trefZ >= 0.0 && trefZ <= 1.0) + { + float tbiasedZ = trefZ - 0.005; + float texelSize = 1.0 / 4096.0; + float s0 = shadow2D(s_shadowMap, vec3(tshadowUV + vec2(-0.5, -0.5) * texelSize, tbiasedZ)); + float s1 = shadow2D(s_shadowMap, vec3(tshadowUV + vec2( 0.5, -0.5) * texelSize, tbiasedZ)); + float s2 = shadow2D(s_shadowMap, vec3(tshadowUV + vec2(-0.5, 0.5) * texelSize, tbiasedZ)); + float s3 = shadow2D(s_shadowMap, vec3(tshadowUV + vec2( 0.5, 0.5) * texelSize, tbiasedZ)); + float tvisibility = (s0 + s1 + s2 + s3) * 0.25; + float tshadowFactor = mix(0.6, 1.0, tvisibility); + result.rgb *= tshadowFactor; + } + gl_FragColor = result; return; } @@ -215,5 +240,34 @@ void main() discard; } + // CSM shadow lookup. v_lightspace is the fragment's + // position in light clip space. Perspective divide to NDC, map to + // [0,1] texcoords, compare against shadow map depth via + // sampler2DShadow (hardware PCF). Darken by shadow color if + // occluded. Skip if the fragment is outside the shadow map bounds. + vec3 lsNDC = v_lightspace.xyz / v_lightspace.w; + vec2 shadowUV = lsNDC.xy * 0.5 + 0.5; +#if BGFX_SHADER_LANGUAGE_HLSL + // D3D's NDC Y is flipped vs texture Y. + shadowUV.y = 1.0 - shadowUV.y; + float refZ = lsNDC.z; +#else + float refZ = lsNDC.z * 0.5 + 0.5; +#endif + if (shadowUV.x >= 0.0 && shadowUV.x <= 1.0 + && shadowUV.y >= 0.0 && shadowUV.y <= 1.0 + && refZ >= 0.0 && refZ <= 1.0) + { + float biasedZ = refZ - 0.002; + float texelSize = 1.0 / 4096.0; + float s0 = shadow2D(s_shadowMap, vec3(shadowUV + vec2(-0.5, -0.5) * texelSize, biasedZ)); + float s1 = shadow2D(s_shadowMap, vec3(shadowUV + vec2( 0.5, -0.5) * texelSize, biasedZ)); + float s2 = shadow2D(s_shadowMap, vec3(shadowUV + vec2(-0.5, 0.5) * texelSize, biasedZ)); + float s3 = shadow2D(s_shadowMap, vec3(shadowUV + vec2( 0.5, 0.5) * texelSize, biasedZ)); + float visibility = (s0 + s1 + s2 + s3) * 0.25; + float shadowFactor = mix(0.6, 1.0, visibility); + current.rgb *= shadowFactor; + } + gl_FragColor = current; } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc index 25d682f7b8b..cd209d5254f 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc @@ -2,6 +2,7 @@ vec4 v_color0 : COLOR0 = vec4(1.0, 0.0, 0.0, 1.0); vec2 v_texcoord0 : TEXCOORD0 = vec2(0.0, 0.0); vec2 v_texcoord1 : TEXCOORD1 = vec2(0.0, 0.0); vec3 v_normal : NORMAL = vec3(0.0, 0.0, 1.0); +vec4 v_lightspace: TEXCOORD2 = vec4(0.0, 0.0, 0.0, 1.0); vec3 a_position : POSITION; vec3 a_normal : NORMAL; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_passthrough.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_passthrough.sc index 47b7fe85a3c..5090d3571b4 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_passthrough.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_passthrough.sc @@ -10,5 +10,5 @@ $output v_color0 void main() { gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0)); - v_color0 = a_color0; + v_color0 = a_color0.bgra; } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_shadow_apply.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_shadow_apply.sc new file mode 100644 index 00000000000..3c032fd6f2a --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_shadow_apply.sc @@ -0,0 +1,12 @@ +$input a_position + +// TheSuperHackers @refactor bobtista 15/04/2026 stencil shadow +// apply vertex shader. Draws a fullscreen quad in clip space; the engine +// feeds pre-baked clip-space XYZ verts (-1..1) so we skip MVP entirely. + +#include + +void main() +{ + gl_Position = vec4(a_position, 1.0); +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_shadow_caster.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_shadow_caster.sc new file mode 100644 index 00000000000..9dd34575d2b --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_shadow_caster.sc @@ -0,0 +1,16 @@ +$input a_position + +// TheSuperHackers @refactor bobtista 16/04/2026 shadow map +// caster pass. Uses the SAME u_shadowLightViewProj uniform as the +// receiver (vs_uber) to guarantee bit-identical depth values — +// bgfx's auto-composed u_modelViewProj differs due to float +// matrix multiplication associativity, causing self-shadow. + +#include + +uniform mat4 u_shadowLightViewProj; + +void main() +{ + gl_Position = mul(u_shadowLightViewProj, vec4(a_position, 1.0)); +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_shadow_volume.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_shadow_volume.sc new file mode 100644 index 00000000000..8a6cff25f2b --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_shadow_volume.sc @@ -0,0 +1,35 @@ +$input a_position + +// TheSuperHackers @refactor bobtista 15/04/2026 stencil shadow +// volume vertex shader. Matches the DX8 reference path's SHADOW_VOLUME_FVF +// (XYZ only, 12 bytes). Transforms position through MVP, with a safety +// clamp on clip-space Z. +// +// Why clamp: RenderDoc investigation showed that certain shadow volume +// meshes (per-mesh camera-angle-dependent, not per-frame) have +// extrusion vertices placed far beyond the caster. After MVP these can +// fall outside the view frustum's depth range. D3D8 implicitly handles +// this gracefully (tight tank-shadow shapes); D3D11's strict depth +// clipping produces giant thin triangles in screen space instead. +// Clamping clip.z to the far plane (z <= w, i.e. NDC z <= 1.0) keeps +// the extruded vertex at the far plane rather than outside it, +// matching D3D8's effective behavior. +// +// u_shadowBias.x: small per-pass Z offset (unused currently, kept for +// future polygon-offset experiments). + +#include + +uniform vec4 u_shadowBias; + +void main() +{ + vec4 clip = mul(u_modelViewProj, vec4(a_position, 1.0)); + clip.z += u_shadowBias.x * clip.w; + // Safety clamp: keep extruded verts at/before the far plane so + // the rasterizer sees consistent triangles regardless of how far + // the extrusion overshoots the frustum. Preserves x/y/w so + // screen-space XY positioning is unchanged. + clip.z = min(clip.z, clip.w); + gl_Position = clip; +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_trees.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_trees.sc index 02efa23f70d..206453e10b9 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_trees.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_trees.sc @@ -1,5 +1,5 @@ $input a_position, a_normal, a_color0, a_texcoord0 -$output v_color0, v_texcoord0, v_texcoord1, v_normal +$output v_color0, v_texcoord0, v_texcoord1, v_normal, v_lightspace #include @@ -20,6 +20,7 @@ $output v_color0, v_texcoord0, v_texcoord1, v_normal uniform vec4 u_swayTable[MAX_SWAY_TYPES_PLUS1]; uniform vec4 u_shroudOffset; uniform vec4 u_shroudScale; +uniform mat4 u_shadowLightViewProj; void main() { @@ -49,4 +50,6 @@ void main() v_texcoord1 = (a_position.xy + u_shroudOffset.xy) * u_shroudScale.xy; v_normal = vec3(0.0, 0.0, 1.0); // grass billboards always face up + + v_lightspace = mul(u_shadowLightViewProj, vec4(a_position, 1.0)); } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_uber.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_uber.sc index 25a3a31b237..467663e8e0b 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_uber.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_uber.sc @@ -1,19 +1,36 @@ $input a_position, a_normal, a_color0, a_texcoord0, a_texcoord1 -$output v_color0, v_texcoord0, v_texcoord1, v_normal +$output v_color0, v_texcoord0, v_texcoord1, v_normal, v_lightspace #include +// CSM: per-draw model*lightView*lightProj, pre-composed +// by the backend with the CORRECT model matrix (raw world for sorted +// draws, regular world for opaque). Avoids u_model contamination +// where sorted draws bake camera view into u_model. +uniform mat4 u_shadowLightViewProj; +uniform vec4 u_texcoordSelect; +uniform vec4 u_shroudParams; // xy = offset, zw = scale + void main() { gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0)); - vec4 c = a_color0.bgra; - if (c.r + c.g + c.b < 0.001) - { - c = vec4(1.0, 1.0, 1.0, 1.0); - } - v_color0 = c; + v_color0 = a_color0.bgra; v_texcoord0 = a_texcoord0; v_texcoord1 = a_texcoord1; v_normal = mul(u_model[0], vec4(a_normal, 0.0)).xyz; + + // Shroud pass: compute UV from world position using offset+scale. + // The D3D8 path uses TCI_CAMERASPACEPOSITION with a texture matrix + // inv(view)*offset*scale, but view cancels with camera-space input. + // The net result is UV = (worldPos.xy + offset) * scale. + if (u_texcoordSelect.z > 0.5) + { + vec4 worldPos = mul(u_model[0], vec4(a_position, 1.0)); + v_texcoord0 = (worldPos.xy + u_shroudParams.xy) * u_shroudParams.zw; + } + + // Light-space position using the pre-composed matrix + // that already includes the correct model-to-world transform. + v_lightspace = mul(u_shadowLightViewProj, vec4(a_position, 1.0)); } diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp index 27d3d112b0e..3ec844e30ea 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp @@ -727,6 +727,25 @@ void W3DShroud::render(CameraClass *cam) m_pSrcTexture); } + // TheSuperHackers @feature bobtista 17/04/2026 Push shroud pixel data to + // the bgfx backend so it can mirror the POOL_DEFAULT destination texture. + // m_srcTextureData is the persistently-mapped system-memory surface that + // the shroud system writes into; we read from it after the CopyRects above + // has pushed the same data to the DX8 video-memory copy. + if (g_renderBackend != nullptr && m_pSrcTexture != nullptr && m_pDstTexture != nullptr) + { + SurfaceClass::SurfaceDescription srcDesc; + m_pSrcTexture->Get_Description(srcDesc); + g_renderBackend->Capture_Shroud_Texture( + m_pDstTexture, + m_srcTextureData, + m_dstTextureWidth, m_dstTextureHeight, + visEndX - visStartX, visEndY - visStartY, + dstPoint.x, dstPoint.y, + m_srcTexturePitch, + srcDesc.Format); + } + REF_PTR_RELEASE (pDestSurface); } diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameLogic/W3DTerrainLogic.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameLogic/W3DTerrainLogic.cpp index 1d453b36329..ce6476ca5c6 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameLogic/W3DTerrainLogic.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameLogic/W3DTerrainLogic.cpp @@ -164,9 +164,10 @@ Bool W3DTerrainLogic::loadMap( AsciiString filename , Bool query ) if( TerrainLogic::loadMap( filename, query ) == false ) return FALSE; - // Map file now contains lighting & time of day info. - if( TheWritableGlobalData->setTimeOfDay( TheGlobalData->m_timeOfDay ) ) - TheGameClient->setTimeOfDay( TheGlobalData->m_timeOfDay ); + // TheSuperHackers @fix bobtista 16/04/2026 Always re-propagate sun direction on map load + // so the bgfx shadow light position is set even when TOD does not change between maps. + TheWritableGlobalData->setTimeOfDay( TheGlobalData->m_timeOfDay ); + TheGameClient->setTimeOfDay( TheGlobalData->m_timeOfDay ); return TRUE; // success diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h index 7f484111672..89a5fa08f9d 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h @@ -351,6 +351,7 @@ class GlobalData : public SubsystemInterface Bool m_buildMapCache; AsciiString m_initialFile; ///< If this is specified, load a specific map from the command-line AsciiString m_pendingFile; ///< If this is specified, use this map at the next game start + AsciiString m_loadSaveGame; ///< If this is specified, load a save game file from the command-line std::vector m_simulateReplays; ///< If not empty, simulate this list of replays and exit. Int m_simulateReplayJobs; ///< Maximum number of processes to use for simulation, or SIMULATE_REPLAYS_SEQUENTIAL for sequential simulation diff --git a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp index f37ed99f4aa..be7bca1f800 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp @@ -689,6 +689,16 @@ Int parseFile(char *args[], int num) return 2; } +// TheSuperHackers @feature bobtista 17/04/2026 Load a save game file from the command line +Int parseLoadSave(char *args[], int num) +{ + if (num > 1) + { + TheWritableGlobalData->m_loadSaveGame = args[1]; + } + return 2; +} + Int parsePreloadEverything( char *args[], int num ) { @@ -1253,6 +1263,7 @@ static CommandLineParam paramsForEngineInit[] = { "-munkee", parseMunkee }, { "-displayDebug", parseDisplayDebug }, { "-file", parseFile }, + { "-loadsave", parseLoadSave }, // { "-preload", parsePreload }, diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp index 3bb8d09f78f..7e6a8408456 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp @@ -739,6 +739,17 @@ void GameEngine::init() } } + // TheSuperHackers @feature bobtista 17/04/2026 Load a save game file + // from the command line. Deferred to the first update tick via + // MSG_NEW_GAME so the game loop and UI systems are fully initialized + // before the load occurs. The actual loadGame() call happens in the + // update() method when m_loadSaveGame is non-empty. + if (TheGlobalData->m_loadSaveGame.isEmpty() == FALSE) + { + TheWritableGlobalData->m_shellMapOn = FALSE; + TheWritableGlobalData->m_playIntro = FALSE; + } + // if (TheMapCache && TheGlobalData->m_shellMapOn) { @@ -952,6 +963,37 @@ void GameEngine::execute() DWORD startTime = timeGetTime() / 1000; #endif + // TheSuperHackers @feature bobtista 17/04/2026 Deferred save game load. + // Load before the main loop. The shell/intro are already suppressed by + // m_shellMapOn=FALSE and m_playIntro=FALSE set in init(). After loading, + // hide all shell UI so the game is immediately playable. + if (TheGlobalData->m_loadSaveGame.isEmpty() == FALSE) + { + AvailableGameInfo gameInfo; + gameInfo.filename = TheGlobalData->m_loadSaveGame; + gameInfo.next = nullptr; + gameInfo.prev = nullptr; + + AsciiString fullPath = TheGameState->getFilePathInSaveDirectory(gameInfo.filename); + TheGameState->getSaveGameInfoFromFile(fullPath, &gameInfo.saveGameInfo); + + TheGameLogic->prepareNewGame(GAME_SINGLE_PLAYER, DIFFICULTY_NORMAL, 0); + + if (TheGameState->loadGame(gameInfo) == SC_OK) + { + if (TheShell) + { + TheShell->hideShell(); + } + } + else + { + DEBUG_LOG(("Failed to load save game '%s'", TheGlobalData->m_loadSaveGame.str())); + TheWritableGlobalData->m_loadSaveGame.clear(); + m_quitting = TRUE; + } + } + // pretty basic for now while( !m_quitting ) { diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp index 3f37c4d3f06..818cf227e37 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp @@ -998,6 +998,7 @@ GlobalData::GlobalData() m_buildMapCache = FALSE; m_initialFile.clear(); m_pendingFile.clear(); + m_loadSaveGame.clear(); m_simulateReplays.clear(); m_simulateReplayJobs = SIMULATE_REPLAYS_SEQUENTIAL; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp index e7d1333905b..409ec2f6bf1 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp @@ -466,7 +466,7 @@ void Shell::showShell( Bool runInit ) { DEBUG_LOG(("Shell:showShell() - %s (%s)", TheGlobalData->m_initialFile.str(), (top())?top()->getFilename().str():"no top screen")); - if(!TheGlobalData->m_initialFile.isEmpty() || !TheGlobalData->m_simulateReplays.empty()) + if(!TheGlobalData->m_initialFile.isEmpty() || !TheGlobalData->m_simulateReplays.empty() || !TheGlobalData->m_loadSaveGame.isEmpty()) { return; } @@ -528,7 +528,7 @@ void Shell::showShell( Bool runInit ) void Shell::showShellMap(Bool useShellMap ) { // we don't want any of this to show if we're loading straight into a file - if (TheGlobalData->m_initialFile.isNotEmpty() || !TheGameLogic || !TheGlobalData->m_simulateReplays.empty()) + if (TheGlobalData->m_initialFile.isNotEmpty() || !TheGameLogic || !TheGlobalData->m_simulateReplays.empty() || TheGlobalData->m_loadSaveGame.isNotEmpty()) return; if(useShellMap && TheGlobalData->m_shellMapOn) { diff --git a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DShroud.h b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DShroud.h index 4dba0c2db4b..6e706506b51 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DShroud.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DShroud.h @@ -121,6 +121,7 @@ class W3DShroud Real m_drawOriginY; Bool m_drawFogOfWar; ///CreateIndexBuffer / CreateVertexBuffer calls below stay on - // the IDirect3DDevice8 device pointer because they need a substantial - // allocation abstraction we don't have yet. m_renderTargetHasAlpha=TRUE; if ((m_dynamicRenderTarget=g_renderBackend->Create_Render_Target (DEFAULT_RENDER_TARGET_WIDTH, DEFAULT_RENDER_TARGET_HEIGHT, WW3D_FORMAT_A8R8G8B8)) == nullptr) { @@ -276,32 +275,16 @@ Bool W3DProjectedShadowManager::ReAcquireResources() m_dynamicRenderTarget=g_renderBackend->Create_Render_Target (DEFAULT_RENDER_TARGET_WIDTH, DEFAULT_RENDER_TARGET_HEIGHT, WW3D_FORMAT_UNKNOWN); } - LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); + DEBUG_ASSERTCRASH(shadowDecalIndexBuffer == nullptr && shadowDecalVertexBuffer == nullptr, ("ReAcquireResources not released in W3DProjectedShadowManager")); - DEBUG_ASSERTCRASH(m_pDev, ("Trying to ReAcquireResources on W3DProjectedShadowManager without device")); - DEBUG_ASSERTCRASH(shadowDecalIndexBufferD3D == nullptr && shadowDecalIndexBufferD3D == nullptr, ("ReAcquireResources not released in W3DProjectedShadowManager")); - - if (FAILED(m_pDev->CreateIndexBuffer - ( - SHADOW_DECAL_INDEX_SIZE*sizeof(WORD), - D3DUSAGE_WRITEONLY|D3DUSAGE_DYNAMIC, - D3DFMT_INDEX16, - D3DPOOL_DEFAULT, - &shadowDecalIndexBufferD3D - ))) + shadowDecalIndexBuffer = NEW_REF(DX8IndexBufferClass, (SHADOW_DECAL_INDEX_SIZE, DX8IndexBufferClass::USAGE_DYNAMIC)); + if (shadowDecalIndexBuffer == nullptr) return FALSE; - if (shadowDecalVertexBufferD3D == nullptr) - { // Create vertex buffer - - if (FAILED(m_pDev->CreateVertexBuffer - ( - SHADOW_DECAL_VERTEX_SIZE*sizeof(SHADOW_DECAL_VERTEX), - D3DUSAGE_WRITEONLY|D3DUSAGE_DYNAMIC, - 0, - D3DPOOL_DEFAULT, - &shadowDecalVertexBufferD3D - ))) + if (shadowDecalVertexBuffer == nullptr) + { + shadowDecalVertexBuffer = NEW_REF(DX8VertexBufferClass, (SHADOW_DECAL_FVF, SHADOW_DECAL_VERTEX_SIZE, DX8VertexBufferClass::USAGE_DYNAMIC)); + if (shadowDecalVertexBuffer == nullptr) return FALSE; } @@ -312,12 +295,8 @@ void W3DProjectedShadowManager::ReleaseResources() { invalidateCachedLightPositions(); //textures need to be updated REF_PTR_RELEASE(m_dynamicRenderTarget); //need to create a new render target - if (shadowDecalIndexBufferD3D) - shadowDecalIndexBufferD3D->Release(); - if (shadowDecalVertexBufferD3D) - shadowDecalVertexBufferD3D->Release(); - shadowDecalIndexBufferD3D=nullptr; - shadowDecalVertexBufferD3D=nullptr; + REF_PTR_RELEASE(shadowDecalIndexBuffer); + REF_PTR_RELEASE(shadowDecalVertexBuffer); } void W3DProjectedShadowManager::invalidateCachedLightPositions() @@ -398,53 +377,53 @@ Int W3DProjectedShadowManager::renderProjectedTerrainShadow(W3DProjectedShadow * Int numVerts = vertsPerRow *vertsPerColumn; //number of terrain vertices - if (nShadowVertsInBuf > (SHADOW_VERTEX_SIZE-numVerts)) //check if room for model verts - { //flush the buffer by drawing the contents and re-locking again - if (shadowVertexBufferD3D->Lock(0,numVerts*sizeof(SHADOW_VOLUME_VERTEX),(unsigned char**)&pvVertices,D3DLOCK_DISCARD) != D3D_OK) - return 0; + // TheSuperHackers @refactor bobtista 15/04/2026 route + // projected terrain shadow buffers through W3D classes so bgfx + // capture hooks fire. DISCARD on wrap, NOOVERWRITE on append. + const bool wrapVerts = (nShadowVertsInBuf > (SHADOW_VERTEX_SIZE-numVerts)); + if (wrapVerts) + { nShadowVertsInBuf=0; nShadowStartBatchVertex=0; } - else - { if (shadowVertexBufferD3D->Lock(nShadowVertsInBuf*sizeof(SHADOW_VOLUME_VERTEX),numVerts*sizeof(SHADOW_VOLUME_VERTEX), (unsigned char**)&pvVertices,D3DLOCK_NOOVERWRITE) != D3D_OK) - return 0; - } - - if(pvVertices) { - //insert each cell's bottom/left edge vertex - for (j=startY; j <= endY; j++) - { - float ycoord = (float)j * MAP_XY_FACTOR; + const unsigned vbFlags = wrapVerts ? D3DLOCK_DISCARD : D3DLOCK_NOOVERWRITE; + VertexBufferClass::AppendLockClass vbLock(shadowVertexBuffer, nShadowVertsInBuf, numVerts, vbFlags); + pvVertices = (SHADOW_VOLUME_VERTEX *)vbLock.Get_Vertex_Array(); - for (i=startX; i <= endX; i++) + if(pvVertices) + { + //insert each cell's bottom/left edge vertex + for (j=startY; j <= endY; j++) { - pvVertices->x=(float)i*MAP_XY_FACTOR; - pvVertices->y=ycoord; - pvVertices->z=(float)hmap->getHeight(i,j)*MAP_HEIGHT_SCALE; - pvVertices++; + float ycoord = (float)j * MAP_XY_FACTOR; + + for (i=startX; i <= endX; i++) + { + pvVertices->x=(float)i*MAP_XY_FACTOR; + pvVertices->y=ycoord; + pvVertices->z=(float)hmap->getHeight(i,j)*MAP_HEIGHT_SCALE; + pvVertices++; + } } } } - shadowVertexBufferD3D->Unlock(); - Int numIndex=(endX - startX) * (endY-startY)*6; //6 indices per terrain cell (2 triangles). - if (nShadowIndicesInBuf > (SHADOW_INDEX_SIZE-numIndex)) //check if room for model verts - { //flush the buffer by drawing the contents and re-locking again - if (shadowIndexBufferD3D->Lock(0,numIndex*sizeof(short),(unsigned char**)&pvIndices,D3DLOCK_DISCARD) != D3D_OK) - return 0; + const bool wrapIndices = (nShadowIndicesInBuf > (SHADOW_INDEX_SIZE-numIndex)); + if (wrapIndices) + { nShadowIndicesInBuf=0; nShadowStartBatchIndex=0; } - else - { if (shadowIndexBufferD3D->Lock(nShadowIndicesInBuf*sizeof(short),numIndex*sizeof(short), (unsigned char**)&pvIndices,D3DLOCK_NOOVERWRITE) != D3D_OK) - return 0; - } + { + const unsigned ibFlags = wrapIndices ? D3DLOCK_DISCARD : D3DLOCK_NOOVERWRITE; + IndexBufferClass::AppendLockClass ibLock(shadowIndexBuffer, nShadowIndicesInBuf, numIndex, ibFlags); + pvIndices = ibLock.Get_Index_Array(); - if(pvIndices) - { //fill each cell's vertex indices + if(pvIndices) + { //fill each cell's vertex indices Int rowStart; for (j=startY,rowStart=0; jUnlock(); - - m_pDev->SetIndices(shadowIndexBufferD3D,nShadowStartBatchVertex); - - m_pDev->SetTransform(D3DTS_WORLD,(_D3DMATRIX *)&mWorld); - - m_pDev->SetStreamSource(0,shadowVertexBufferD3D,sizeof(SHADOW_VOLUME_VERTEX)); - m_pDev->SetVertexShader(SHADOW_VOLUME_FVF); + // TheSuperHackers @refactor bobtista 15/04/2026 route + // buffers/shader through DX8Wrapper cache so cached stencil/blend + // state flushes on Draw_Triangles. Explicitly clear texture stages + // so the previous terrain draw's textures don't bleed through. + g_renderBackend->Set_Texture(0, nullptr); + g_renderBackend->Set_Texture(1, nullptr); + g_renderBackend->Set_Index_Buffer(shadowIndexBuffer, nShadowStartBatchVertex); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD, reinterpret_cast(mWorld)); + g_renderBackend->Set_Vertex_Buffer(shadowVertexBuffer, 0); + g_renderBackend->Set_Vertex_Shader(SHADOW_VOLUME_FVF); Int numPolys = (endX - startX)*(endY - startY)*2; //2 triangles per cell - m_pDev->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE); //should reject background pixels - m_pDev->SetRenderState( D3DRS_STENCILENABLE, TRUE ); - m_pDev->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_ALWAYS ); - m_pDev->SetRenderState( D3DRS_STENCILREF, 0x1 ); - m_pDev->SetRenderState( D3DRS_STENCILMASK, 0xffffffff ); - m_pDev->SetRenderState( D3DRS_STENCILWRITEMASK,0xffffffff ); - m_pDev->SetRenderState( D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP ); - m_pDev->SetRenderState( D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ); - m_pDev->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_INCR ); + g_renderBackend->Override_Alpha_Test(true, 0, 8); //should reject background pixels + g_renderBackend->Set_Stencil_Enable(true); + g_renderBackend->Set_Stencil_Func(RB_CMP_ALWAYS); + g_renderBackend->Set_Stencil_Ref(0x1); + g_renderBackend->Set_Stencil_Mask(0xffffffff); + g_renderBackend->Set_Stencil_Write_Mask(0xffffffff); + g_renderBackend->Set_Stencil_ZFail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Fail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Pass_Op(RB_STENCIL_OP_INCR); // m_pDev->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ); //useful to see bounds - m_pDev->SetRenderState( D3DRS_LIGHTING, FALSE); - m_pDev->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR); - m_pDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ZERO ); + g_renderBackend->Set_Lighting_Enable(false); + g_renderBackend->Set_Blend_Factors(RB_BLEND_DEST_COLOR, RB_BLEND_ZERO); if (DX8Wrapper::_Is_Triangle_Draw_Enabled()) { Debug_Statistics::Record_DX8_Polys_And_Vertices(numPolys,numVerts,ShaderClass::_PresetOpaqueShader); - m_pDev->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,numVerts,nShadowStartBatchIndex,numPolys); + // TheSuperHackers @bugfix bobtista 17/04/2026 skip the bgfx submit + // for the projected terrain shadow pass. This DX8-specific stencil + // darkening technique routes through Set_Shadow_Volume_Shader_Active + // which submits terrain grid quads to the stencil shadow view, + // corrupting the stencil buffer and producing dark lines on terrain. + g_renderBackend->Skip_Next_Bgfx_Submit(); + g_renderBackend->Set_Shadow_Volume_Shader_Active(true); + g_renderBackend->Draw_Triangles(nShadowStartBatchIndex, numPolys, 0, numVerts); + g_renderBackend->Set_Shadow_Volume_Shader_Active(false); } - m_pDev->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE); //should reject background pixels - m_pDev->SetRenderState( D3DRS_STENCILENABLE, FALSE ); + g_renderBackend->Override_Alpha_Test(false, 0, 0); //should reject background pixels + g_renderBackend->Set_Stencil_Enable(false); // m_pDev->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); - m_pDev->SetRenderState( D3DRS_LIGHTING, TRUE); + g_renderBackend->Set_Lighting_Enable(true); nShadowVertsInBuf += numVerts; nShadowStartBatchVertex=nShadowVertsInBuf; @@ -535,150 +524,6 @@ Int W3DProjectedShadowManager::renderProjectedTerrainShadow(W3DProjectedShadow * return 0; } -#if 0 - -TextureClass *snow=nullptr; -TextureClass *grass=nullptr; -TextureClass *ground=nullptr; - -#define V_COUNT (4*4) //4 vertices per cell -#define I_COUNT (4*6) //6 indices per cell -#define TILE_HEIGHT 10.1f -#define TILE_DIFFUSE 0x00b4b0a5 - -enum BlendDirection CPP_11(: Int) -{ B_A, //visible on all sides - B_R, //visible on right - B_L, //visible on left - B_T, //visible on top - B_B, //visble on bottom - B_TL, //visible on top/left - B_BR, //visible on bottom/right - B_TR, //visible on top/right - B_BL //visilbe on bottom/left -}; - -//Vertex alpha values for each blend direction assuming tile vertices -//start at top left corner and continue counter-clockwise -DWORD BDToVA[9][4]= -{ - {0xff000000,0xff000000,0xff000000,0xff000000}, - {0,0,0xff000000,0xff000000}, - {0xff000000,0xff000000,0,0}, - {0xff000000,0,0,0xff000000}, - {0,0xff000000,0xff000000,0}, - {0xff000000,0,0,0}, - {0,0,0xff000000,0}, - {0,0,0,0xff000000}, - {0,0xff000000,0,0} -}; - -static void RenderVBTile(TextureClass *text, Real ox, Real oy, Real ou, Real ov, BlendDirection bd=B_A) -{ - DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC_DX8,DX8_FVF_XYZNDUV2,4); - DynamicIBAccessClass ib_access(BUFFER_TYPE_DYNAMIC_DX8,6); - - DynamicVBAccessClass::WriteLockClass lock(&vb_access); - VertexFormatXYZNDUV2* vb= lock.Get_Formatted_Vertex_Array(); - DynamicIBAccessClass::WriteLockClass lockib(&ib_access); - if (!vb) return; - - UnsignedShort *ib=lockib.Get_Index_Array(); - - vb->x=ox; - vb->y=oy; - vb->z=TILE_HEIGHT; - vb->diffuse=TILE_DIFFUSE|BDToVA[bd][0]; - vb->u1=ou; - vb->v1=ov; - vb++; - - vb->x=ox; - vb->y=oy-10.0f; - vb->z=TILE_HEIGHT; - vb->diffuse=TILE_DIFFUSE|BDToVA[bd][1]; - vb->u1=ou; - vb->v1=ov+0.25f; - vb++; - - vb->x=ox+10.0f; - vb->y=oy-10.0f; - vb->z=TILE_HEIGHT; - vb->diffuse=TILE_DIFFUSE|BDToVA[bd][2]; - vb->u1=ou+0.25f; - vb->v1=ov+0.25f; - vb++; - - vb->x=ox+10.0f; - vb->y=oy; - vb->z=TILE_HEIGHT; - vb->diffuse=TILE_DIFFUSE|BDToVA[bd][3]; - vb->u1=ou+0.25f; - vb->v1=ov; - vb++; - - ib[0]=0; - ib[1]=1; - ib[2]=3; - ib[3]=3; - ib[4]=1; - ib[5]=2; - - if (bd == B_TR || bd == B_BL) - { //need to flip triangles so alpha gradient doesn't follow diagonal edge - ib[2]=2; - ib[4]=0; - } - - // TheSuperHackers @refactor bobtista 10/04/2026 route the - // high-level binding/draw calls and the blend state through g_renderBackend. - g_renderBackend->Set_Index_Buffer(ib_access,0); - g_renderBackend->Set_Vertex_Buffer(vb_access); - g_renderBackend->Set_Texture(0, text); - g_renderBackend->Set_Blend_Factors(RB_BLEND_SRC_ALPHA, RB_BLEND_INV_SRC_ALPHA); - g_renderBackend->Set_Alpha_Blend_Enable(true); - ShaderClass::Invalidate(); //invalidate to force shader to reset since we directly changed states - g_renderBackend->Draw_Triangles( 0,2, 0, 4); //draw a quad, 2 triangles, 4 verts -} - -//Debug code used to draw some dummy polygons. -void TestBlendRender(RenderInfoClass & rinfo) -{ - static Int doInit=1; - - if (doInit) - { doInit = 0; - snow = WW3DAssetManager::Get_Instance()->Get_Texture("TXSnow04a.tga"); - grass = WW3DAssetManager::Get_Instance()->Get_Texture("TMGras23a.tga"); - ground = WW3DAssetManager::Get_Instance()->Get_Texture("TXAsph01a.tga"); - } - - VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - g_renderBackend->Set_Material(vmat); - REF_PTR_RELEASE(vmat); - g_renderBackend->Set_Shader(ShaderClass::_PresetOpaqueShader); - - Matrix3D tm(1); //identity - g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,tm); - - //grass - RenderVBTile(grass,580.0f,480.0f,0.0f,0.0f); RenderVBTile(grass,590.0f,480.0f,0.25f,0.0f); - RenderVBTile(grass,580.0f,470.0f,0.0f,0.25f); RenderVBTile(grass,590.0f,470.0f,0.25f,0.25f); - RenderVBTile(grass,580.0f,460.0f,0.0f,0.5f); RenderVBTile(grass,590.0f,460.0f,0.25f,0.5f,B_L); - RenderVBTile(grass,580.0f,450.0f,0.0f,0.75f); RenderVBTile(grass,590.0f,450.0f,0.25f,0.75f,B_L); - RenderVBTile(grass,580.0f,440.0f,0.0f,0.0f); RenderVBTile(grass,590.0f,440.0f,0.25f,0.0f,B_L); - - RenderVBTile(grass,610.0f,460.0f,0.0f,0.5f, B_B); - RenderVBTile(grass,610.0f,450.0f,0.0f,0.75f); - RenderVBTile(grass,610.0f,440.0f,0.0f,0.0f); - - - //snow - RenderVBTile(snow,590.0f,480.0f,0.0f,0.0f, B_R); RenderVBTile(snow,600.0f,480.0f,0.25f,0.0f); RenderVBTile(snow,610.0f,480.0f,0.5f,0.0f); - RenderVBTile(snow,590.0f,470.0f,0.0f,0.25f, B_R); RenderVBTile(snow,600.0f,470.0f,0.25f,0.25f); RenderVBTile(snow,610.0f,470.0f,0.5f,0.25f); - RenderVBTile(snow,590.0f,460.0f,0.0f,0.5f, B_TR); RenderVBTile(snow,600.0f,460.0f,0.25f,0.5f,B_T); RenderVBTile(snow,610.0f,460.0f,0.5f,0.5f,B_T); -} -#endif void W3DProjectedShadowManager::flushDecals(W3DShadowTexture *texture, ShadowType type) { @@ -689,15 +534,9 @@ void W3DProjectedShadowManager::flushDecals(W3DShadowTexture *texture, ShadowTyp return; } - LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); - if (!m_pDev) return; //no D3D Device to render + if (!DX8Wrapper::_Get_D3D_Device8()) + return; //no D3D Device to render - // TheSuperHackers @refactor bobtista 10/04/2026 partial migration: - // the high-level Set_Material/Set_Texture/Set_Shader/Apply_Render_State_Changes - // calls below are routed through g_renderBackend. The raw m_pDev->X() calls - // later in this function (SetIndices, SetStreamSource, SetVertexShader, - // SetRenderState, DrawIndexedPrimitive) remain on IDirect3DDevice8 because - // they belong to the deeply-coupled inner rendering loop. VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); g_renderBackend->Set_Material(vmat); REF_PTR_RELEASE(vmat); @@ -723,51 +562,60 @@ void W3DProjectedShadowManager::flushDecals(W3DShadowTexture *texture, ShadowTyp g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices -//Alpha Blended Shadows -// m_pDev->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); -// m_pDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); -/* UnsignedInt color=TheW3DShadowManager->getShadowColor(); - m_pDev->SetRenderState( D3DRS_TEXTUREFACTOR, 0xff000000 | color); - m_pDev->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); - m_pDev->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); - m_pDev->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TFACTOR); - - m_pDev->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); - m_pDev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); - m_pDev->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TFACTOR); -*/ + // TheSuperHackers @bugfix bobtista 16/04/2026 TFACTOR + // removed. _PresetMultiplicativeShader uses COLORARG2=DIFFUSE (vertex + // color), not COLORARG2=TFACTOR, so Set_Texture_Factor is a no-op on + // DX8. On bgfx it incorrectly maps to matDiffuse which uniformly + // darkens the entire quad, making the square boundary visible. The + // shadow shape comes from the texture alone via multiplicative blend. - m_pDev->SetIndices(shadowDecalIndexBufferD3D,nShadowDecalStartBatchVertex); - m_pDev->SetTransform(D3DTS_WORLD,(_D3DMATRIX *)&mWorld); + g_renderBackend->Set_Index_Buffer(shadowDecalIndexBuffer, nShadowDecalStartBatchVertex); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD, reinterpret_cast(mWorld)); - m_pDev->SetStreamSource(0,shadowDecalVertexBufferD3D,sizeof(SHADOW_DECAL_VERTEX)); - m_pDev->SetVertexShader(SHADOW_DECAL_FVF); + g_renderBackend->Set_Vertex_Buffer(shadowDecalVertexBuffer, 0); + g_renderBackend->Set_Vertex_Shader(SHADOW_DECAL_FVF); //Hard Shadows using stencil /* m_pDev->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ZERO); m_pDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE ); - m_pDev->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE); //should reject background pixels - m_pDev->SetRenderState( D3DRS_STENCILENABLE, TRUE ); + g_renderBackend->Override_Alpha_Test(true, 0, 8); //should reject background pixels + g_renderBackend->Set_Stencil_Enable(true); */ -/* m_pDev->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_ALWAYS ); - m_pDev->SetRenderState( D3DRS_STENCILREF, 0x1 ); - m_pDev->SetRenderState( D3DRS_STENCILMASK, 0xffffffff ); - m_pDev->SetRenderState( D3DRS_STENCILWRITEMASK,0xffffffff ); - m_pDev->SetRenderState( D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP ); - m_pDev->SetRenderState( D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ); - m_pDev->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_INCR ); +/* g_renderBackend->Set_Stencil_Func(RB_CMP_ALWAYS); + g_renderBackend->Set_Stencil_Ref(0x1); + g_renderBackend->Set_Stencil_Mask(0xffffffff); + g_renderBackend->Set_Stencil_Write_Mask(0xffffffff); + g_renderBackend->Set_Stencil_ZFail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Fail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Pass_Op(RB_STENCIL_OP_INCR); */ //m_pDev->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ); //useful to see bounds if (DX8Wrapper::_Is_Triangle_Draw_Enabled()) { Debug_Statistics::Record_DX8_Polys_And_Vertices(nShadowDecalPolysInBatch,nShadowDecalVertsInBatch,ShaderClass::_PresetOpaqueShader); - m_pDev->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,nShadowDecalVertsInBatch,nShadowDecalStartBatchIndex,nShadowDecalPolysInBatch); + static int s_decalLog = 0; + if (s_decalLog++ < 10) + { + TextureClass * shadowTex = texture ? texture->getTexture() : nullptr; + WWDEBUG_SAY(("[DECAL] flush polys=%d verts=%d tex=%p texName='%s' type=%d", + nShadowDecalPolysInBatch, nShadowDecalVertsInBatch, + shadowTex, + shadowTex ? (const char*)shadowTex->Get_Full_Path() : "null", + (int)type)); + } + // TheSuperHackers @refactor bobtista 16/04/2026 skip + // bgfx submit for projected shadow decals since CSM handles shadows. + g_renderBackend->Skip_Next_Bgfx_Submit(); + g_renderBackend->Draw_Triangles(nShadowDecalStartBatchIndex, nShadowDecalPolysInBatch, 0, nShadowDecalVertsInBatch); } -// m_pDev->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE); //should reject background pixels -// m_pDev->SetRenderState( D3DRS_STENCILENABLE, FALSE ); + g_renderBackend->Set_Vertex_Buffer(nullptr, 0); + g_renderBackend->Set_Index_Buffer(nullptr, 0); + +// g_renderBackend->Override_Alpha_Test(false, 0, 0); //should reject background pixels +// g_renderBackend->Set_Stencil_Enable(false); //m_pDev->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); @@ -788,25 +636,6 @@ void W3DProjectedShadowManager::flushDecals(W3DShadowTexture *texture, ShadowTyp nShadowDecalVertsInBatch=0; } -/* -void testShadowDecal() -{ - Shadow::ShadowTypeInfo decalInfo; - decalInfo.allowUpdates = FALSE; //shadow image will never update - decalInfo.allowWorldAlign = TRUE; //shadow image will wrap around world objects - decalInfo.m_type = SHADOW_ALPHA_DECAL; - strcpy(decalInfo.m_ShadowName,"exwave256"); - decalInfo.m_sizeX = 1280.0f; - decalInfo.m_sizeY = 1280.0f; - decalInfo.m_offsetX = 0; - decalInfo.m_offsetY = 0; - Shadow *shadow=TheProjectedShadowManager->addDecal(&decalInfo); - shadow->setPosition(600,600,600); - shadow->setAngle(0.0f); - shadow->setColor(0xffff0000); -} -*/ - #define BRIDGE_OFFSET_FACTOR 1.5f /**Decals have a low poly count so its better to render large numbers at once. This system will queue them up until the buffers fill up. It will then flush the buffer (draw decals) and be ready for new decals. This @@ -1015,18 +844,12 @@ void W3DProjectedShadowManager::queueDecal(W3DProjectedShadow *shadow) if (nShadowDecalVertsInBuf > (SHADOW_DECAL_VERTEX_SIZE-numVerts)) //check if room for model verts { //flush the buffer by drawing the contents and re-locking again flushDecals(shadow->m_shadowTexture[0], shadow->m_type); - if (shadowDecalVertexBufferD3D->Lock(0,numVerts*sizeof(SHADOW_DECAL_VERTEX),(unsigned char**)&pvVertices,D3DLOCK_DISCARD) != D3D_OK) - return; nShadowDecalStartBatchVertex=0; nShadowDecalPolysInBatch=0; //reset number of polys in texture batch nShadowDecalVertsInBatch=0; nShadowDecalVertsInBuf=0; } - else - { if (shadowDecalVertexBufferD3D->Lock(nShadowDecalVertsInBuf*sizeof(SHADOW_DECAL_VERTEX),numVerts*sizeof(SHADOW_DECAL_VERTEX), (unsigned char**)&pvVertices,D3DLOCK_NOOVERWRITE) != D3D_OK) - return; - } //code to deal with rotated shadows based on sun direction, fix this later. For now shadow rotates with object rotation. //shadow->m_shadowTexture[0]->getDecalUVAxis(&uVector,&vVector); @@ -1041,9 +864,33 @@ void W3DProjectedShadowManager::queueDecal(W3DProjectedShadow *shadow) */ DEBUG_ASSERTCRASH(numVerts == ((endY-startY+1)*(endX-startX+1)), ("queueDecal VB size mismatch")); - if(pvVertices) { - if (layerHeight) + unsigned vbFlags = (nShadowDecalVertsInBuf == 0) ? D3DLOCK_DISCARD : D3DLOCK_NOOVERWRITE; + VertexBufferClass::AppendLockClass vbLock(shadowDecalVertexBuffer, nShadowDecalVertsInBuf, numVerts, vbFlags); + pvVertices = (SHADOW_DECAL_VERTEX *)vbLock.Get_Vertex_Array(); + + if(pvVertices) + { + if (layerHeight) + for (j=startY; j <= endY; j++) + { + hmapVertex.Y=(float)(j-borderSize) * MAP_XY_FACTOR; + + for (i=startX; i <= endX; i++) + { + hmapVertex.X=(float)(i-borderSize)*MAP_XY_FACTOR; + hmapVertex.Z=__max((float)hmap->getHeight(i,j)*MAP_HEIGHT_SCALE,layerHeight); + pvVertices->x=hmapVertex.X; + pvVertices->y=hmapVertex.Y; + pvVertices->z=hmapVertex.Z; + pvVertices->diffuse=shadow->m_diffuse; + pvVertices->u=Vector3::Dot_Product(uVector, (hmapVertex-objPos))+uOffset; + pvVertices->v=Vector3::Dot_Product(vVector, (hmapVertex-objPos))+vOffset; + pvVertices++; + } + } + else + //insert each cell's bottom/left edge vertex for (j=startY; j <= endY; j++) { hmapVertex.Y=(float)(j-borderSize) * MAP_XY_FACTOR; @@ -1051,7 +898,7 @@ void W3DProjectedShadowManager::queueDecal(W3DProjectedShadow *shadow) for (i=startX; i <= endX; i++) { hmapVertex.X=(float)(i-borderSize)*MAP_XY_FACTOR; - hmapVertex.Z=__max((float)hmap->getHeight(i,j)*MAP_HEIGHT_SCALE,layerHeight); + hmapVertex.Z=(float)hmap->getHeight(i,j)*MAP_HEIGHT_SCALE+0.01f * MAP_XY_FACTOR; pvVertices->x=hmapVertex.X; pvVertices->y=hmapVertex.Y; pvVertices->z=hmapVertex.Z; @@ -1061,77 +908,54 @@ void W3DProjectedShadowManager::queueDecal(W3DProjectedShadow *shadow) pvVertices++; } } - else - //insert each cell's bottom/left edge vertex - for (j=startY; j <= endY; j++) - { - hmapVertex.Y=(float)(j-borderSize) * MAP_XY_FACTOR; - - for (i=startX; i <= endX; i++) - { - hmapVertex.X=(float)(i-borderSize)*MAP_XY_FACTOR; - hmapVertex.Z=(float)hmap->getHeight(i,j)*MAP_HEIGHT_SCALE+0.01f * MAP_XY_FACTOR; - pvVertices->x=hmapVertex.X; - pvVertices->y=hmapVertex.Y; - pvVertices->z=hmapVertex.Z; - pvVertices->diffuse=shadow->m_diffuse; - pvVertices->u=Vector3::Dot_Product(uVector, (hmapVertex-objPos))+uOffset; - pvVertices->v=Vector3::Dot_Product(vVector, (hmapVertex-objPos))+vOffset; - pvVertices++; - } } } - shadowDecalVertexBufferD3D->Unlock(); - if (nShadowDecalIndicesInBuf > (SHADOW_DECAL_INDEX_SIZE-numIndex)) //check if room for model verts { //flush the buffer by drawing the contents and re-locking again flushDecals(shadow->m_shadowTexture[0], shadow->m_type); - if (shadowDecalIndexBufferD3D->Lock(0,numIndex*sizeof(short),(unsigned char**)&pvIndices,D3DLOCK_DISCARD) != D3D_OK) - return; - nShadowDecalStartBatchIndex=0; nShadowDecalPolysInBatch=0; //reset number of polys in texture batch nShadowDecalVertsInBatch=0; nShadowDecalIndicesInBuf=0; } - else - { if (shadowDecalIndexBufferD3D->Lock(nShadowDecalIndicesInBuf*sizeof(short),numIndex*sizeof(short), (unsigned char**)&pvIndices,D3DLOCK_NOOVERWRITE) != D3D_OK) - return; - } - if(pvIndices) - { //fill each cell's vertex indices - Int rowStart; - for (j=startY,rowStart=0; jgetFlipState(k,j)) - { pvIndices[0]=i+1+nShadowDecalVertsInBatch; - pvIndices[1]=i+vertsPerRow+nShadowDecalVertsInBatch; - pvIndices[2]=i+nShadowDecalVertsInBatch; - pvIndices[3]=i+1+nShadowDecalVertsInBatch; - pvIndices[4]=i+1+vertsPerRow+nShadowDecalVertsInBatch; - pvIndices[5]=i+vertsPerRow+nShadowDecalVertsInBatch; - } - else - { pvIndices[0]=i+nShadowDecalVertsInBatch; - pvIndices[1]=i+1+vertsPerRow+nShadowDecalVertsInBatch; - pvIndices[2]=i+vertsPerRow+nShadowDecalVertsInBatch; - pvIndices[3]=i+nShadowDecalVertsInBatch; - pvIndices[4]=i+1+nShadowDecalVertsInBatch; - pvIndices[5]=i+1+vertsPerRow+nShadowDecalVertsInBatch; + for (i=rowStart,k=startX; kgetFlipState(k,j)) + { pvIndices[0]=i+1+nShadowDecalVertsInBatch; + pvIndices[1]=i+vertsPerRow+nShadowDecalVertsInBatch; + pvIndices[2]=i+nShadowDecalVertsInBatch; + pvIndices[3]=i+1+nShadowDecalVertsInBatch; + pvIndices[4]=i+1+vertsPerRow+nShadowDecalVertsInBatch; + pvIndices[5]=i+vertsPerRow+nShadowDecalVertsInBatch; + } + else + { pvIndices[0]=i+nShadowDecalVertsInBatch; + pvIndices[1]=i+1+vertsPerRow+nShadowDecalVertsInBatch; + pvIndices[2]=i+vertsPerRow+nShadowDecalVertsInBatch; + pvIndices[3]=i+nShadowDecalVertsInBatch; + pvIndices[4]=i+1+nShadowDecalVertsInBatch; + pvIndices[5]=i+1+vertsPerRow+nShadowDecalVertsInBatch; + } + pvIndices += 6; } - pvIndices += 6; } } } - shadowDecalIndexBufferD3D->Unlock(); - Int numPolys = (endX - startX)*(endY - startY)*2; //2 triangles per cell nShadowDecalPolysInBatch += numPolys; @@ -1187,93 +1011,97 @@ void W3DProjectedShadowManager::queueSimpleDecal(W3DProjectedShadow *shadow) if (nShadowDecalVertsInBuf > (SHADOW_DECAL_VERTEX_SIZE-numVerts)) //check if room for model verts { //flush the buffer by drawing the contents and re-locking again flushDecals(shadow->m_shadowTexture[0], shadow->m_type); - if (shadowDecalVertexBufferD3D->Lock(0,numVerts*sizeof(SHADOW_DECAL_VERTEX),(unsigned char**)&pvVertices,D3DLOCK_DISCARD) != D3D_OK) - return; nShadowDecalStartBatchVertex=0; nShadowDecalPolysInBatch=0; //reset number of polys in texture batch nShadowDecalVertsInBatch=0; nShadowDecalVertsInBuf=0; } - else - { if (shadowDecalVertexBufferD3D->Lock(nShadowDecalVertsInBuf*sizeof(SHADOW_DECAL_VERTEX),numVerts*sizeof(SHADOW_DECAL_VERTEX), (unsigned char**)&pvVertices,D3DLOCK_NOOVERWRITE) != D3D_OK) - return; - } objPos.Z=groundHeight; //force decal to ground level objPos += groundNormal * 1.0f; //offset decal slightly above terrain to reduce z-fighting. Vector3 vertex; - if(pvVertices) { - //Top-left - vertex = objPos + vVector * shadow->m_decalSizeY * -0.5f - uVector * shadow->m_decalSizeX * 0.5f; - pvVertices->x=vertex.X; - pvVertices->y=vertex.Y; - pvVertices->z=vertex.Z; - pvVertices->u=0.0f; - pvVertices->v=0.0f; - pvVertices++; - - //Bottom-left - vertex += vVector * shadow->m_decalSizeY; - pvVertices->x=vertex.X; - pvVertices->y=vertex.Y; - pvVertices->z=vertex.Z; - pvVertices->u=0.0f; - pvVertices->v=1.0f; - pvVertices++; - - //Bottom-right - vertex += uVector * shadow->m_decalSizeX; - pvVertices->x=vertex.X; - pvVertices->y=vertex.Y; - pvVertices->z=vertex.Z; - pvVertices->u=1.0f; - pvVertices->v=1.0f; - pvVertices++; - - //Top-right - vertex -= vVector * shadow->m_decalSizeY; - pvVertices->x=vertex.X; - pvVertices->y=vertex.Y; - pvVertices->z=vertex.Z; - pvVertices->u=1.0f; - pvVertices->v=0.0f; - pvVertices++; - } + unsigned vbFlags = (nShadowDecalVertsInBuf == 0) ? D3DLOCK_DISCARD : D3DLOCK_NOOVERWRITE; + VertexBufferClass::AppendLockClass vbLock(shadowDecalVertexBuffer, nShadowDecalVertsInBuf, numVerts, vbFlags); + pvVertices = (SHADOW_DECAL_VERTEX *)vbLock.Get_Vertex_Array(); - shadowDecalVertexBufferD3D->Unlock(); + if(pvVertices) + { + //Top-left + vertex = objPos + vVector * shadow->m_decalSizeY * -0.5f - uVector * shadow->m_decalSizeX * 0.5f; + pvVertices->x=vertex.X; + pvVertices->y=vertex.Y; + pvVertices->z=vertex.Z; + // TheSuperHackers @bugfix bobtista 16/04/2026 initialize vertex + // diffuse to white. The uber shader MODULATE stage multiplies + // tex0 by vertex color; uninitialized diffuse produces black on + // bgfx. White passes the texture through so matDiffuse (from + // TFACTOR) provides the shadow tint. + pvVertices->diffuse=0xFFFFFFFF; + pvVertices->u=0.0f; + pvVertices->v=0.0f; + pvVertices++; + + //Bottom-left + vertex += vVector * shadow->m_decalSizeY; + pvVertices->x=vertex.X; + pvVertices->y=vertex.Y; + pvVertices->z=vertex.Z; + pvVertices->diffuse=0xFFFFFFFF; + pvVertices->u=0.0f; + pvVertices->v=1.0f; + pvVertices++; + + //Bottom-right + vertex += uVector * shadow->m_decalSizeX; + pvVertices->x=vertex.X; + pvVertices->y=vertex.Y; + pvVertices->z=vertex.Z; + pvVertices->diffuse=0xFFFFFFFF; + pvVertices->u=1.0f; + pvVertices->v=1.0f; + pvVertices++; + + //Top-right + vertex -= vVector * shadow->m_decalSizeY; + pvVertices->x=vertex.X; + pvVertices->y=vertex.Y; + pvVertices->z=vertex.Z; + pvVertices->diffuse=0xFFFFFFFF; + pvVertices->u=1.0f; + pvVertices->v=0.0f; + pvVertices++; + } + } if (nShadowDecalIndicesInBuf > (SHADOW_DECAL_INDEX_SIZE-numIndex)) //check if room for model verts { //flush the buffer by drawing the contents and re-locking again flushDecals(shadow->m_shadowTexture[0],shadow->m_type); - if (shadowDecalIndexBufferD3D->Lock(0,numIndex*sizeof(short),(unsigned char**)&pvIndices,D3DLOCK_DISCARD) != D3D_OK) - return; - nShadowDecalStartBatchIndex=0; nShadowDecalPolysInBatch=0; //reset number of polys in texture batch nShadowDecalVertsInBatch=0; nShadowDecalIndicesInBuf=0; } - else - { if (shadowDecalIndexBufferD3D->Lock(nShadowDecalIndicesInBuf*sizeof(short),numIndex*sizeof(short), (unsigned char**)&pvIndices,D3DLOCK_NOOVERWRITE) != D3D_OK) - return; - } - if(pvIndices) - { pvIndices[0]=nShadowDecalVertsInBatch; - pvIndices[1]=nShadowDecalVertsInBatch+1; - pvIndices[2]=nShadowDecalVertsInBatch+2; - pvIndices[3]=nShadowDecalVertsInBatch; - pvIndices[4]=nShadowDecalVertsInBatch+2; - pvIndices[5]=nShadowDecalVertsInBatch+3; - pvIndices += 6; + { + unsigned ibFlags = (nShadowDecalIndicesInBuf == 0) ? D3DLOCK_DISCARD : D3DLOCK_NOOVERWRITE; + IndexBufferClass::AppendLockClass ibLock(shadowDecalIndexBuffer, nShadowDecalIndicesInBuf, numIndex, ibFlags); + pvIndices = ibLock.Get_Index_Array(); + + if(pvIndices) + { pvIndices[0]=nShadowDecalVertsInBatch; + pvIndices[1]=nShadowDecalVertsInBatch+1; + pvIndices[2]=nShadowDecalVertsInBatch+2; + pvIndices[3]=nShadowDecalVertsInBatch; + pvIndices[4]=nShadowDecalVertsInBatch+2; + pvIndices[5]=nShadowDecalVertsInBatch+3; + pvIndices += 6; + } } - shadowDecalIndexBufferD3D->Unlock(); - Int numPolys = 2; //2 triangles per decal nShadowDecalPolysInBatch += numPolys; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DShadow.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DShadow.cpp index efa56604fb5..bcb939f96b0 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DShadow.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DShadow.cpp @@ -37,6 +37,7 @@ #include "WW3D2/camera.h" #include "WW3D2/light.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/hlod.h" #include "WW3D2/mesh.h" #include "WW3D2/meshmdl.h" @@ -235,6 +236,11 @@ void W3DShadowManager::setLightPosition(Int lightIndex, Real x, Real y, Real z) return; ///@todo: Add support for multiple lights LightPosWorld[lightIndex]=Vector3(x,y,z); + + // TheSuperHackers @refactor bobtista 16/04/2026 forward + // the shadow sun position to the render backend so bgfx's CSM uses + // the same light direction as DX8's stencil shadow system. + g_renderBackend->Set_Shadow_Light_Position(x, y, z); } void W3DShadowManager::setTimeOfDay(TimeOfDay tod) diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp index 5140de0c59f..de81d0ea26d 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp @@ -36,6 +36,9 @@ // SYSTEM INCLUDES //////////////////////////////////////////////////////////// #include +#include +#include +#include // USER INCLUDES ////////////////////////////////////////////////////////////// #include "WWLib/always.h" @@ -43,6 +46,8 @@ #include "WW3D2/camera.h" #include "WW3D2/light.h" #include "WW3D2/dx8wrapper.h" +#include "WW3D2/dx8vertexbuffer.h" +#include "WW3D2/dx8indexbuffer.h" #include "WW3D2/RenderBackend.h" #include "WW3D2/hlod.h" #include "WW3D2/mesh.h" @@ -108,8 +113,17 @@ struct SHADOW_STATIC_VOLUME_VERTEX //vertex structure passed to D3D #define SHADOW_DYNAMIC_VOLUME_FVF D3DFVF_XYZ #endif -LPDIRECT3DVERTEXBUFFER8 shadowVertexBufferD3D=nullptr; /// +// Set_Vertex_Buffer / Set_Index_Buffer / Draw_Triangles can route them +// through the bgfx capture hooks. Lock semantics preserved via the new +// flags parameter on AppendLockClass (D3DLOCK_NOOVERWRITE for partial +// appends, D3DLOCK_DISCARD via WriteLockClass on wrap-around). +// Forward declaration (implementation further down in the file). +static int EarClip2D(const float * xy, int N, short * out_indices); + +DX8VertexBufferClass * shadowVertexBuffer = nullptr; +DX8IndexBufferClass * shadowIndexBuffer = nullptr; int nShadowVertsInBuf=0; //model vetices in vertex buffer int nShadowStartBatchVertex=0; int nShadowIndicesInBuf=0; //model vetices in vertex buffer @@ -1352,16 +1366,14 @@ void W3DVolumetricShadow::RenderMeshVolume(Int meshIndex, Int lightIndex, const if( numVerts == 0 || numPolys == 0 ) return; - D3DMATRIX dxmWorld = To_D3DMATRIX(*meshXform); - m_pDev->SetTransform(D3DTS_WORLD,&dxmWorld); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD, *meshXform); W3DBufferManager::W3DVertexBufferSlot *vbSlot=m_shadowVolumeVB[lightIndex][ meshIndex ]; if (!vbSlot) return; if (vbSlot->m_VB->m_DX8VertexBuffer->Get_DX8_Vertex_Buffer() != lastActiveVertexBuffer) { lastActiveVertexBuffer=vbSlot->m_VB->m_DX8VertexBuffer->Get_DX8_Vertex_Buffer(); - m_pDev->SetStreamSource(0,lastActiveVertexBuffer, - vbSlot->m_VB->m_DX8VertexBuffer->FVF_Info().Get_FVF_Size()); //12 bytes per vertex. + g_renderBackend->Set_Vertex_Buffer(vbSlot->m_VB->m_DX8VertexBuffer, 0); } DEBUG_ASSERTCRASH(vbSlot->m_size >= numVerts,("Overflowing Shadow Vertex Buffer Slot")); @@ -1372,14 +1384,20 @@ void W3DVolumetricShadow::RenderMeshVolume(Int meshIndex, Int lightIndex, const DEBUG_ASSERTCRASH(ibSlot->m_size >= numIndex,("Overflowing Shadow Index Buffer Slot")); - m_pDev->SetIndices(ibSlot->m_IB->m_DX8IndexBuffer->Get_DX8_Index_Buffer(),vbSlot->m_start); + g_renderBackend->Set_Index_Buffer(ibSlot->m_IB->m_DX8IndexBuffer, vbSlot->m_start); if (DX8Wrapper::_Is_Triangle_Draw_Enabled()) { Debug_Statistics::Record_DX8_Polys_And_Vertices(numPolys,numVerts,ShaderClass::_PresetOpaqueShader); - m_pDev->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,numVerts,ibSlot->m_start,numPolys); + g_renderBackend->Set_Shadow_Volume_Shader_Active(true); + g_renderBackend->Draw_Triangles(ibSlot->m_start, numPolys, 0, numVerts); + g_renderBackend->Set_Shadow_Volume_Shader_Active(false); } + // No Set_*_Buffer(nullptr) cleanup here: static shadow volume VBs + // come from W3DBufferManager, are NOT AppendLock'd each frame, and + // the lastActiveVertexBuffer optimization above assumes the cached + // binding persists across calls. Clearing it would desync the cache. } void W3DVolumetricShadow::RenderDynamicMeshVolume(Int meshIndex, Int lightIndex, const Matrix3D *meshXform) @@ -1414,72 +1432,74 @@ void W3DVolumetricShadow::RenderDynamicMeshVolume(Int meshIndex, Int lightIndex, return; - if (nShadowVertsInBuf > (SHADOW_VERTEX_SIZE-numVerts)) //check if room for model verts - { //flush the buffer by drawing the contents and re-locking again - if (shadowVertexBufferD3D->Lock(0,numVerts*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX),(unsigned char**)&pvVertices,D3DLOCK_DISCARD) != D3D_OK) - return; - nShadowVertsInBuf=0; - nShadowStartBatchVertex=0; - } - else - { if (shadowVertexBufferD3D->Lock(nShadowVertsInBuf*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX),numVerts*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX), (unsigned char**)&pvVertices,D3DLOCK_NOOVERWRITE) != D3D_OK) - return; + // Wrap-around (DISCARD) when the buffer can't fit this batch. + const bool wrapVerts = (nShadowVertsInBuf > (SHADOW_VERTEX_SIZE - numVerts)); + if (wrapVerts) { + nShadowVertsInBuf = 0; + nShadowStartBatchVertex = 0; } -#ifdef SV_DEBUG - srand(0x1345465); -#endif - if(pvVertices) { + const unsigned vbFlags = wrapVerts ? D3DLOCK_DISCARD : D3DLOCK_NOOVERWRITE; + VertexBufferClass::AppendLockClass vbLock(shadowVertexBuffer, nShadowVertsInBuf, numVerts, vbFlags); + pvVertices = (SHADOW_DYNAMIC_VOLUME_VERTEX *)vbLock.Get_Vertex_Array(); #ifdef SV_DEBUG - for (Int i=0; iGetVertex(i); //cast is valid since both start with xyz - pvVertices->diffuse=(rand()%255) | ((rand()%255)<<8) | ((rand()%255)<<16); - pvVertices++; - } +#ifdef SV_DEBUG + for (Int i=0; iGetVertex(i); + pvVertices->diffuse=(rand()%255) | ((rand()%255)<<8) | ((rand()%255)<<16); + pvVertices++; + } #else - memcpy(pvVertices,geometry->GetVertex(0),numVerts*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX)); + memcpy(pvVertices,geometry->GetVertex(0),numVerts*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX)); #endif + } } - shadowVertexBufferD3D->Unlock(); - - if (nShadowIndicesInBuf > (SHADOW_INDEX_SIZE-numIndex)) //check if room for model verts - { //flush the buffer by drawing the contents and re-locking again - if (shadowIndexBufferD3D->Lock(0,numIndex*sizeof(short),(unsigned char**)&pvIndices,D3DLOCK_DISCARD) != D3D_OK) - return; - nShadowIndicesInBuf=0; - nShadowStartBatchIndex=0; - } - else - { if (shadowIndexBufferD3D->Lock(nShadowIndicesInBuf*sizeof(short),numIndex*sizeof(short), (unsigned char**)&pvIndices,D3DLOCK_NOOVERWRITE) != D3D_OK) - return; + const bool wrapIndices = (nShadowIndicesInBuf > (SHADOW_INDEX_SIZE - numIndex)); + if (wrapIndices) { + nShadowIndicesInBuf = 0; + nShadowStartBatchIndex = 0; } - - - if(pvIndices) { - memcpy(pvIndices,geometry->GetPolygonIndex(0,(short *)pvIndices),numPolys*3*sizeof(short)); + const unsigned ibFlags = wrapIndices ? D3DLOCK_DISCARD : D3DLOCK_NOOVERWRITE; + IndexBufferClass::AppendLockClass ibLock(shadowIndexBuffer, nShadowIndicesInBuf, numIndex, ibFlags); + pvIndices = ibLock.Get_Index_Array(); + if (pvIndices) + { + memcpy(pvIndices,geometry->GetPolygonIndex(0,(short *)pvIndices),numPolys*3*sizeof(short)); + } } - shadowIndexBufferD3D->Unlock(); - - m_pDev->SetIndices(shadowIndexBufferD3D,nShadowStartBatchVertex); - - D3DMATRIX dxmWorld = To_D3DMATRIX(*meshXform); - m_pDev->SetTransform(D3DTS_WORLD,&dxmWorld); - - if (shadowVertexBufferD3D != lastActiveVertexBuffer) - { m_pDev->SetStreamSource(0,shadowVertexBufferD3D,sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX)); - lastActiveVertexBuffer = shadowVertexBufferD3D; - } + // TheSuperHackers @refactor bobtista 15/04/2026 route through + // g_renderBackend so DX8Wrapper flushes cached stencil/blend state + // before the shadow draw. Clear texture stages to prevent stale + // terrain textures from bleeding onto the stencil volume verts. + g_renderBackend->Set_Texture(0, nullptr); + g_renderBackend->Set_Texture(1, nullptr); + g_renderBackend->Set_Index_Buffer(shadowIndexBuffer, nShadowStartBatchVertex); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD, *meshXform); + g_renderBackend->Set_Vertex_Buffer(shadowVertexBuffer, 0); + g_renderBackend->Set_Vertex_Shader(SHADOW_DYNAMIC_VOLUME_FVF); + lastActiveVertexBuffer = shadowVertexBuffer->Get_DX8_Vertex_Buffer(); if (DX8Wrapper::_Is_Triangle_Draw_Enabled()) { Debug_Statistics::Record_DX8_Polys_And_Vertices(numPolys,numVerts,ShaderClass::_PresetOpaqueShader); - m_pDev->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,numVerts,nShadowStartBatchIndex,numPolys); + g_renderBackend->Set_Shadow_Volume_Shader_Active(true); + g_renderBackend->Draw_Triangles(nShadowStartBatchIndex, numPolys, 0, numVerts); + g_renderBackend->Set_Shadow_Volume_Shader_Active(false); } + // Release engine refs on the shadow ring buffers so the next + // AppendLockClass (which asserts !Engine_Refs) can proceed. + g_renderBackend->Set_Vertex_Buffer(nullptr, 0); + g_renderBackend->Set_Index_Buffer(nullptr, 0); + nShadowVertsInBuf += numVerts; nShadowStartBatchVertex=nShadowVertsInBuf; @@ -1564,70 +1584,67 @@ void W3DVolumetricShadow::RenderMeshVolumeBounds(Int meshIndex, Int lightIndex, return; - if (nShadowVertsInBuf > (SHADOW_VERTEX_SIZE-numVerts)) //check if room for model verts - { //flush the buffer by drawing the contents and re-locking again - if (shadowVertexBufferD3D->Lock(0,numVerts*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX),(unsigned char**)&pvVertices,D3DLOCK_DISCARD) != D3D_OK) - return; - nShadowVertsInBuf=0; - nShadowStartBatchVertex=0; + const bool wrapVerts = (nShadowVertsInBuf > (SHADOW_VERTEX_SIZE - numVerts)); + if (wrapVerts) { + nShadowVertsInBuf = 0; + nShadowStartBatchVertex = 0; } - else - { if (shadowVertexBufferD3D->Lock(nShadowVertsInBuf*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX),numVerts*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX), (unsigned char**)&pvVertices,D3DLOCK_NOOVERWRITE) != D3D_OK) - return; - } - srand(0x1345465); - if(pvVertices) - { for (Int i=0; i<8; i++) - { - pvVertices->x=verts[i][0]; - pvVertices->y=verts[i][1]; - pvVertices->z=verts[i][2]; + { + const unsigned vbFlags = wrapVerts ? D3DLOCK_DISCARD : D3DLOCK_NOOVERWRITE; + VertexBufferClass::AppendLockClass vbLock(shadowVertexBuffer, nShadowVertsInBuf, numVerts, vbFlags); + pvVertices = (SHADOW_DYNAMIC_VOLUME_VERTEX *)vbLock.Get_Vertex_Array(); + srand(0x1345465); + if (pvVertices) + { for (Int i=0; i<8; i++) + { + pvVertices->x=verts[i][0]; + pvVertices->y=verts[i][1]; + pvVertices->z=verts[i][2]; #ifdef SV_DEBUG - pvVertices->diffuse=(rand()%255) | ((rand()%255)<<8) | ((rand()%255)<<16); + pvVertices->diffuse=(rand()%255) | ((rand()%255)<<8) | ((rand()%255)<<16); #endif - pvVertices++; + pvVertices++; + } } } - shadowVertexBufferD3D->Unlock(); - - if (nShadowIndicesInBuf > (SHADOW_INDEX_SIZE-numIndex)) //check if room for model verts - { //flush the buffer by drawing the contents and re-locking again - if (shadowIndexBufferD3D->Lock(0,numIndex*sizeof(short),(unsigned char**)&pvIndices,D3DLOCK_DISCARD) != D3D_OK) - return; - nShadowIndicesInBuf=0; - nShadowStartBatchIndex=0; - } - else - { if (shadowIndexBufferD3D->Lock(nShadowIndicesInBuf*sizeof(short),numIndex*sizeof(short), (unsigned char**)&pvIndices,D3DLOCK_NOOVERWRITE) != D3D_OK) - return; + const bool wrapIndices = (nShadowIndicesInBuf > (SHADOW_INDEX_SIZE - numIndex)); + if (wrapIndices) { + nShadowIndicesInBuf = 0; + nShadowStartBatchIndex = 0; } - - - if(pvIndices) { - for (Int i=0; iUnlock(); - - m_pDev->SetIndices(shadowIndexBufferD3D,nShadowStartBatchVertex); - + g_renderBackend->Set_Texture(0, nullptr); + g_renderBackend->Set_Texture(1, nullptr); + g_renderBackend->Set_Index_Buffer(shadowIndexBuffer, nShadowStartBatchVertex); //todo: replace this with mesh transform - Matrix4x4 mWorld(1); //identity since boxes are pre-transformed to world space. - D3DMATRIX dxmWorld = To_D3DMATRIX(mWorld); - m_pDev->SetTransform(D3DTS_WORLD,&dxmWorld); + Matrix3D mWorld(true); //identity since boxes are pre-transformed to world space. + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD, mWorld); - m_pDev->SetStreamSource(0,shadowVertexBufferD3D,sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX)); - m_pDev->SetVertexShader(SHADOW_DYNAMIC_VOLUME_FVF); + g_renderBackend->Set_Vertex_Buffer(shadowVertexBuffer, 0); + g_renderBackend->Set_Vertex_Shader(SHADOW_DYNAMIC_VOLUME_FVF); - m_pDev->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,numVerts,nShadowStartBatchIndex,numPolys); + g_renderBackend->Set_Shadow_Volume_Shader_Active(true); + g_renderBackend->Draw_Triangles(nShadowStartBatchIndex, numPolys, 0, numVerts); + g_renderBackend->Set_Shadow_Volume_Shader_Active(false); + + g_renderBackend->Set_Vertex_Buffer(nullptr, 0); + g_renderBackend->Set_Index_Buffer(nullptr, 0); nShadowVertsInBuf += numVerts; nShadowStartBatchVertex=nShadowVertsInBuf; @@ -2553,6 +2570,130 @@ void W3DVolumetricShadow::buildSilhouette(Int meshIndex, Vector3 *lightPosObject // buffer - to be rendered via a dynamic vertex buffer. // // ============================================================================ +// TheSuperHackers @refactor bobtista 15/04/2026 ear-clipping +// 2D polygon triangulation. Used to close shadow volume caps for bgfx +// stencil correctness. Input: 2D XY coordinates of N polygon vertices +// in loop order. Output: triangle indices (each triangle = 3 shorts +// referring to local indices [0..N-1]). Returns total index count +// written, or 0 on failure (non-simple polygon, degenerate). +static int EarClip2D(const float * xy, int N, short * out_indices) +{ + if (N < 3) return 0; + if (N == 3) + { + out_indices[0] = 0; out_indices[1] = 1; out_indices[2] = 2; + return 3; + } + + // Determine polygon winding via shoelace signed area. + float signedArea = 0.0f; + for (int i = 0; i < N; ++i) + { + int j = (i + 1) % N; + signedArea += xy[i*2] * xy[j*2+1]; + signedArea -= xy[j*2] * xy[i*2+1]; + } + const bool polygonCCW = (signedArea > 0.0f); + + std::vector poly(N); + for (int i = 0; i < N; ++i) poly[i] = i; + + int outCount = 0; + int safetyMax = N * N; + while (static_cast(poly.size()) > 3 && safetyMax-- > 0) + { + bool foundEar = false; + const int M = static_cast(poly.size()); + for (int i = 0; i < M; ++i) + { + const int iPrev = poly[(i + M - 1) % M]; + const int iCurr = poly[i]; + const int iNext = poly[(i + 1) % M]; + const float ax = xy[iPrev*2], ay = xy[iPrev*2+1]; + const float bx = xy[iCurr*2], by = xy[iCurr*2+1]; + const float cx = xy[iNext*2], cy = xy[iNext*2+1]; + const float cross = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax); + // Must match polygon winding to be a convex corner (ear candidate). + if ((polygonCCW && cross <= 0.0f) || (!polygonCCW && cross >= 0.0f)) + { + continue; + } + // No other vertex may lie inside triangle (iPrev, iCurr, iNext). + bool hasInside = false; + for (int j = 0; j < M; ++j) + { + const int iTest = poly[j]; + if (iTest == iPrev || iTest == iCurr || iTest == iNext) continue; + const float px = xy[iTest*2], py = xy[iTest*2+1]; + const float d1 = (px - bx) * (ay - by) - (ax - bx) * (py - by); + const float d2 = (px - cx) * (by - cy) - (bx - cx) * (py - cy); + const float d3 = (px - ax) * (cy - ay) - (cx - ax) * (py - ay); + const bool hasNeg = (d1 < 0.0f) || (d2 < 0.0f) || (d3 < 0.0f); + const bool hasPos = (d1 > 0.0f) || (d2 > 0.0f) || (d3 > 0.0f); + if (!(hasNeg && hasPos)) + { + hasInside = true; + break; + } + } + if (!hasInside) + { + out_indices[outCount++] = static_cast(iPrev); + out_indices[outCount++] = static_cast(iCurr); + out_indices[outCount++] = static_cast(iNext); + poly.erase(poly.begin() + i); + foundEar = true; + break; + } + } + if (!foundEar) return 0; // Non-simple polygon. + } + if (poly.size() == 3) + { + out_indices[outCount++] = static_cast(poly[0]); + out_indices[outCount++] = static_cast(poly[1]); + out_indices[outCount++] = static_cast(poly[2]); + } + return outCount; +} + +// TheSuperHackers @debug bobtista 15/04/2026 mesh edge- +// manifold audit. A closed 2-manifold has every undirected edge used +// by EXACTLY TWO triangles. Open tubes have some edges used once (the +// "rim" edges). Logs the first N distinct audits per construction path. +static void AuditShadowVolumeEdges(Geometry * shadowVolume, int vertexCount, + int polygonCount, const char * tag) +{ + static int s_auditCount = 0; + if (s_auditCount++ >= 20) return; + + std::map, int> edgeCount; + for (int p = 0; p < polygonCount; ++p) + { + short idx[3]; + shadowVolume->GetPolygonIndex(p, idx); + for (int e = 0; e < 3; ++e) + { + int a = idx[e]; + int b = idx[(e+1) % 3]; + if (a > b) std::swap(a, b); + edgeCount[std::make_pair(a, b)]++; + } + } + int used1 = 0, used2 = 0, usedOther = 0; + for (auto & kv : edgeCount) + { + if (kv.second == 1) ++used1; + else if (kv.second == 2) ++used2; + else ++usedOther; + } + WWDEBUG_SAY(("[SHADOW MESH AUDIT] %s verts=%d tris=%d edges=%zu " + "used_once=%d used_twice=%d used_3+=%d %s", + tag, vertexCount, polygonCount, edgeCount.size(), + used1, used2, usedOther, + (used1 == 0 && usedOther == 0) ? "CLOSED_MANIFOLD" : "OPEN_OR_NON_MANIFOLD")); +} + void W3DVolumetricShadow::constructVolume( Vector3 *lightPosObject,Real shadowExtrudeDistance, Int volumeIndex, Int meshIndex ) { Geometry *shadowVolume; @@ -2769,6 +2910,8 @@ void W3DVolumetricShadow::constructVolume( Vector3 *lightPosObject,Real shadowEx shadowVolume->SetNumActivePolygon(polygonCount); shadowVolume->SetNumActiveVertex(vertexCount); + + AuditShadowVolumeEdges(shadowVolume, vertexCount, polygonCount, "constructVolume(dynamic)"); } // constructVolumeVB ========================================================== @@ -3390,6 +3533,16 @@ void W3DVolumetricShadowManager::renderStencilShadows() if (DX8Wrapper::_Is_Triangle_Draw_Enabled()) m_pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANSLITVERTEX)); + // TheSuperHackers @refactor bobtista 15/04/2026 issue the + // matching darken pass on the bgfx backend. DX8 already drew the + // quad above via raw m_pDev; bgfx never saw those raw calls, so ask + // the backend to draw its own fullscreen darkening quad with the + // same stencil test (LEQUAL ref=1 mask=~shadowMask). + g_renderBackend->Apply_Stencil_Shadow_Darken( + TheW3DShadowManager->getShadowColor(), + ~TheW3DShadowManager->getStencilShadowMask(), + 0x1); + m_pDev->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); m_pDev->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ); // turn off the stencil buffer @@ -3491,27 +3644,24 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) m_pDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE ); m_pDev->SetRenderState(D3DRS_ALPHABLENDENABLE , TRUE); } - m_pDev->SetRenderState( D3DRS_STENCILENABLE, TRUE ); + g_renderBackend->Set_Stencil_Enable(true); #endif - //Any pixels with stencil already set to 128 contains a potential occluder. If this pixels also has any of the player - //color stencil bits also set, it means that it's an occluded player color and we need to NOT render shadows here. We - //do this determination by comparing the value in the combined bits against a value containing only a potential occluder. - //If the value of just the potential occluder bit is >= than the combined bits, then we know none of the player color - //bits were set and it's okay to render shadow. + // TheSuperHackers @refactor bobtista 15/04/2026 Route stencil + cull state through + // g_renderBackend so the bgfx capture hooks observe the values the DX8 path applies. if (TheW3DShadowManager->getStencilShadowMask() == 0x80808080) - m_pDev->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_NOTEQUAL ); //in this mode, MSB indicates occluded player pixels. + g_renderBackend->Set_Stencil_Func(RB_CMP_NOT_EQUAL); else - m_pDev->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_GREATEREQUAL ); //in this mode, multiple bits indicate occluded player pixels. - m_pDev->SetRenderState( D3DRS_STENCILREF, 0x80808080 ); //isolate MSB, it's used to indicate pixels containing potential occluders. - m_pDev->SetRenderState( D3DRS_STENCILMASK, TheW3DShadowManager->getStencilShadowMask()); //isolate upper bits containing PotentialOccluderBit|PlayerColorBits - m_pDev->SetRenderState( D3DRS_STENCILWRITEMASK,0xffffffff ); - m_pDev->SetRenderState( D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP ); - m_pDev->SetRenderState( D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ); - m_pDev->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_INCR ); + g_renderBackend->Set_Stencil_Func(RB_CMP_GREATER_EQUAL); + g_renderBackend->Set_Stencil_Ref(0x80808080); + g_renderBackend->Set_Stencil_Mask(TheW3DShadowManager->getStencilShadowMask()); + g_renderBackend->Set_Stencil_Write_Mask(0xffffffff); + g_renderBackend->Set_Stencil_ZFail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Fail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Pass_Op(RB_STENCIL_OP_INCR); m_pDev->SetVertexShader(SHADOW_DYNAMIC_VOLUME_FVF); - m_pDev->SetRenderState(D3DRS_CULLMODE,D3DCULL_CW); + g_renderBackend->Set_Cull_Mode(RB_CULL_CW); // m_pDev->SetRenderState(D3DRS_ZBIAS,1); ///@todo: See if this helps or makes things worse. //m_pDev->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME); @@ -3560,14 +3710,14 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) } // change the stencil op to decrement - m_pDev->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_DECRSAT); + g_renderBackend->Set_Stencil_Pass_Op(RB_STENCIL_OP_DECR_SAT); // // invert normals of shadow volumes so we can decrement in the // stencil buffer and render // - m_pDev->SetRenderState(D3DRS_CULLMODE,D3DCULL_CCW); + g_renderBackend->Set_Cull_Mode(RB_CULL_CCW); for (nextVb=TheW3DBufferManager->getNextVertexBuffer(nullptr,W3DBufferManager::VBM_FVF_XYZ);nextVb != nullptr; nextVb=TheW3DBufferManager->getNextVertexBuffer(nextVb,W3DBufferManager::VBM_FVF_XYZ)) { @@ -3595,7 +3745,7 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) nextVb->m_renderTaskList=nullptr; } - m_pDev->SetRenderState(D3DRS_CULLMODE,D3DCULL_CW); + g_renderBackend->Set_Cull_Mode(RB_CULL_CW); // m_pDev->SetRenderState(D3DRS_ZBIAS,0); ///@todo: See if this helps or makes things worse. //m_pDev->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID); @@ -3735,12 +3885,8 @@ W3DVolumetricShadowManager::~W3DVolumetricShadowManager() /** Releases all W3D/D3D assets before a reset.. */ void W3DVolumetricShadowManager::ReleaseResources() { - if (shadowIndexBufferD3D) - shadowIndexBufferD3D->Release(); - if (shadowVertexBufferD3D) - shadowVertexBufferD3D->Release(); - shadowIndexBufferD3D=nullptr; - shadowVertexBufferD3D=nullptr; + REF_PTR_RELEASE(shadowIndexBuffer); + REF_PTR_RELEASE(shadowVertexBuffer); if (TheW3DBufferManager) { TheW3DBufferManager->ReleaseResources(); invalidateCachedLightPositions(); //vertex buffers need to be refilled. @@ -3752,31 +3898,14 @@ Bool W3DVolumetricShadowManager::ReAcquireResources() { ReleaseResources(); - LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); - - DEBUG_ASSERTCRASH(m_pDev, ("Trying to ReAcquireResources on W3DVolumetricShadowManager without device")); - - if (FAILED(m_pDev->CreateIndexBuffer - ( - SHADOW_INDEX_SIZE*sizeof(WORD), - D3DUSAGE_WRITEONLY|D3DUSAGE_DYNAMIC, - D3DFMT_INDEX16, - D3DPOOL_DEFAULT, - &shadowIndexBufferD3D - ))) + shadowIndexBuffer = NEW_REF(DX8IndexBufferClass, (SHADOW_INDEX_SIZE, DX8IndexBufferClass::USAGE_DYNAMIC)); + if (shadowIndexBuffer == nullptr) return FALSE; - if (shadowVertexBufferD3D == nullptr) - { // Create vertex buffer - - if (FAILED(m_pDev->CreateVertexBuffer - ( - SHADOW_VERTEX_SIZE*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX), - D3DUSAGE_WRITEONLY|D3DUSAGE_DYNAMIC, - 0, - D3DPOOL_DEFAULT, - &shadowVertexBufferD3D - ))) + if (shadowVertexBuffer == nullptr) + { + shadowVertexBuffer = NEW_REF(DX8VertexBufferClass, (SHADOW_DYNAMIC_VOLUME_FVF, SHADOW_VERTEX_SIZE, DX8VertexBufferClass::USAGE_DYNAMIC)); + if (shadowVertexBuffer == nullptr) return FALSE; } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp index 49e07b3c41e..08712b39840 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp @@ -82,6 +82,7 @@ W3DShroud::W3DShroud() m_dstTextureHeight=m_numMaxVisibleCellsY=0; m_boderShroudLevel = (W3DShroudLevel)TheGlobalData->m_shroudAlpha; //assume border is black m_clearDstTexture = TRUE; //force clearing of destination texture; + m_shroudDirty = TRUE; m_cellWidth=DEFAULT_SHROUD_CELL_SIZE; m_cellHeight=DEFAULT_SHROUD_CELL_SIZE; @@ -288,6 +289,7 @@ void W3DShroud::setShroudLevel(Int x, Int y, W3DShroudLevel level, Bool textureO if (x < m_numCellsX && y < m_numCellsY) { + m_shroudDirty = TRUE; if (level < TheGlobalData->m_shroudAlpha) level = TheGlobalData->m_shroudAlpha; @@ -349,7 +351,7 @@ void W3DShroud::setShroudLevel(Int x, Int y, W3DShroudLevel level, Bool textureO ///Quickly sets the shroud level of entire map to a single value void W3DShroud::fillShroudData(W3DShroudLevel level) { - + m_shroudDirty = TRUE; Int x,y; UnsignedShort pixel; @@ -741,6 +743,35 @@ void W3DShroud::render(CameraClass *cam) src_desc.Format); } + // TheSuperHackers @feature bobtista 17/04/2026 Push shroud pixel data to + // the bgfx backend so it can mirror the POOL_DEFAULT destination texture. + // m_srcTextureData is the persistently-mapped system-memory surface that + // the shroud system writes into; we read from it after the CopyRects above + // has pushed the same data to the DX8 video-memory copy. + if (g_renderBackend != nullptr && m_pSrcTexture != nullptr && m_pDstTexture != nullptr && m_shroudDirty) + { + m_shroudDirty = FALSE; + SurfaceClass::SurfaceDescription srcDesc; + m_pSrcTexture->Get_Description(srcDesc); + g_renderBackend->Capture_Shroud_Texture( + m_pDstTexture, + m_srcTextureData, + m_dstTextureWidth, m_dstTextureHeight, + visEndX - visStartX, visEndY - visStartY, + dstPoint.x, dstPoint.y, + m_srcTexturePitch, + srcDesc.Format); + } + else + { + static int s_shroudSkipLog = 0; + if (s_shroudSkipLog++ < 3) + { + WWDEBUG_SAY(("[SHROUD CAP] SKIPPED: backend=%p src=%p dst=%p", + g_renderBackend, m_pSrcTexture, m_pDstTexture)); + } + } + REF_PTR_RELEASE (pDestSurface); } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameLogic/W3DTerrainLogic.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameLogic/W3DTerrainLogic.cpp index 91b5a06f7f3..ab653a45abc 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameLogic/W3DTerrainLogic.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameLogic/W3DTerrainLogic.cpp @@ -164,9 +164,10 @@ Bool W3DTerrainLogic::loadMap( AsciiString filename , Bool query ) if( TerrainLogic::loadMap( filename, query ) == false ) return FALSE; - // Map file now contains lighting & time of day info. - if( TheWritableGlobalData->setTimeOfDay( TheGlobalData->m_timeOfDay ) ) - TheGameClient->setTimeOfDay( TheGlobalData->m_timeOfDay ); + // TheSuperHackers @fix bobtista 16/04/2026 Always re-propagate sun direction on map load + // so the bgfx shadow light position is set even when TOD does not change between maps. + TheWritableGlobalData->setTimeOfDay( TheGlobalData->m_timeOfDay ); + TheGameClient->setTimeOfDay( TheGlobalData->m_timeOfDay ); return TRUE; // success From db049aa5416b58bc87b1263bbb3ed72f44a3b9a3 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Thu, 28 May 2026 19:54:01 +1000 Subject: [PATCH 053/523] chore(bgfx-features): strip phase narrative from shader comments From 4af9c502bd26c17abc0a660769c5446a766fb17e Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 21 Apr 2026 14:02:48 -0400 Subject: [PATCH 054/523] feat(bgfx): Cutover - bgfx takes main window, DX8 moves to reference popup (cherry picked from commit 21658ce1b8fed0c1599193be6bdd646e30086431) --- .../Source/WWVegas/WW3D2/BgfxBackend.cpp | 279 +++++++++++------- .../Source/WWVegas/WW3D2/dx8wrapper.cpp | 24 ++ .../Source/WWVegas/WW3D2/dx8wrapper.h | 3 + .../Source/WWVegas/WW3D2/render2d.cpp | 32 +- .../W3DDevice/GameClient/W3DDisplay.cpp | 7 + .../Source/WWVegas/WW3D2/render2d.cpp | 32 +- 6 files changed, 241 insertions(+), 136 deletions(-) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp index 6672bc37ccb..bb141c7648f 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -57,11 +57,8 @@ #include #include -// TheSuperHackers @refactor bobtista 11/04/2026 BgfxBackend -// now creates its own top-level popup window and hands that HWND to bgfx::init. -// Required because Windows DWM promotes whichever swapchain is actively -// presenting to a HWND, so sharing the game's HWND with DX8 loses DX8's -// output. A separate HWND sidesteps the conflict entirely. +// TheSuperHackers @refactor bobtista 16/04/2026 bgfx takes the main +// game window. A secondary popup is created for D3D8 reference output. #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include @@ -143,18 +140,20 @@ BgfxLoggingCallback g_bgfxCallback; // End_Scene / Shutdown can skip bgfx calls if init was never reached. bool g_bgfxInitialized = false; -// TheSuperHackers @refactor bobtista 11/04/2026 The -// popup window BgfxBackend owns and hands to bgfx::init. Null until -// Initialize runs, nulled again by Shutdown. +// TheSuperHackers @refactor bobtista 16/04/2026 bgfx now renders +// into the main game window. The DX8 reference popup is a secondary window +// where the D3D8 device is moved so its output remains visible for debugging. HWND g_bgfxWindow = nullptr; +HWND g_dx8ReferenceWindow = nullptr; const wchar_t * const kBgfxWindowClass = L"GGC_BgfxDebugWindow"; -// TheSuperHackers @refactor bobtista 11/04/2026 popup -// dimensions chosen to match the game's 1.6 aspect ratio (1280x800) -// so engine geometry rendered with the game's projection matrix -// does not look horizontally squished. -const int kBgfxWindowWidth = 1024; -const int kBgfxWindowHeight = 768; +const int kDX8RefWindowWidth = 800; +const int kDX8RefWindowHeight = 600; + +// TheSuperHackers @refactor bobtista 16/04/2026 bgfx resolution +// matches the main game window. Set during Initialize from GetClientRect. +int g_bgfxWidth = 800; +int g_bgfxHeight = 600; // TheSuperHackers @refactor bobtista 11/04/2026 program handle // for the passthrough shader pair. Created once in Initialize after bgfx::init @@ -348,6 +347,10 @@ float g_atestOverrideRef = 0.0f; bool g_suppressBgfxDraw = false; int g_colorWriteOverride = -1; bool g_effectOverlayActive = false; +// TheSuperHackers @feature bobtista 16/04/2026 2D overlay active flag. +// Set by Set_View_Identity (Render2DClass enters 2D mode), cleared by +// Set_Transform(VIEW) when a real camera view is restored or at Begin_Scene. +bool g_2DOverlayActive = false; // UV set selection: when > 0, the fragment shader samples stage 0 // from v_texcoord1 instead of v_texcoord0. Set by the terrain shader @@ -1090,6 +1093,11 @@ const bgfx::ViewId kBgfxShadowApplyView = 7; // CSM caster pass view. Depth-only render target, renders // opaque casters from the sun's perspective into a D24 shadow map. const bgfx::ViewId kBgfxShadowMapView = 8; +// TheSuperHackers @feature bobtista 16/04/2026 dedicated view for +// 2D UI overlay draws (Render2DClass). Sequential mode preserves draw order; +// identity view+projection so screen-space quads render at their authored +// positions. Composites over the 3D scene as the last view in the order. +const bgfx::ViewId kBgfxUIView = 10; const uint16_t kShadowMapResolution = 4096; const float kShadowOrthoSize = 1200.0f; const float kShadowCameraDistance = 2000.0f; @@ -1166,42 +1174,9 @@ void W3DMatrix3DToBgfx(const Matrix3D & m, float * out) out[3] = 0.0f; out[7] = 0.0f; out[11] = 0.0f; out[15] = 1.0f; } -// TheSuperHackers @refactor bobtista 11/04/2026 aspect ratio -// correction. The engine computes the projection matrix for the main -// game window's aspect ratio. The bgfx popup has a fixed 800x500 -// framebuffer (aspect 1.6), so applying the engine's projection -// directly stretches geometry horizontally when the game runs at a -// different aspect (e.g. 1024x768 = 1.33, or 1280x720 = 1.78). Rescale -// column 0 by (gameAspect / popupAspect) so the popup shows the same -// field of view the engine is rendering to the main window. -// -// The game aspect is extracted from the projection itself: for a -// standard perspective matrix proj[0][0] = cot(fov/2)/aspect and -// proj[1][1] = cot(fov/2), so gameAspect = proj[1][1] / proj[0][0]. -// Avoids touching DX8Wrapper's protected resolution accessors and -// works even when the engine later reshapes the backbuffer. -void ApplyPopupAspectCorrection(float * proj) -{ - // Column-major: proj[0] is (col 0, row 0), proj[5] is (col 1, row 1). - const float p00 = proj[0]; - const float p11 = proj[5]; - if (p00 <= 0.0f || p11 <= 0.0f) - { - return; - } - const float gameAspect = p11 / p00; - const float popupAspect = static_cast(kBgfxWindowWidth) / - static_cast(kBgfxWindowHeight); - if (popupAspect <= 0.0f) - { - return; - } - const float scale = gameAspect / popupAspect; - proj[0] *= scale; - proj[1] *= scale; - proj[2] *= scale; - proj[3] *= scale; -} +// TheSuperHackers @refactor bobtista 16/04/2026 Aspect correction +// is no longer needed because bgfx renders into the same window as the game. +// The engine's projection matrix already matches the bgfx framebuffer aspect. // TheSuperHackers @bugfix bobtista 11/04/2026 buffer copy // kill switch. The Intel UHD Graphics driver corrupts the dx8 GPU @@ -1563,8 +1538,10 @@ static void UpdateShadowLightTransform() g_shadowLightCaptured = true; } -// Create the bgfx debug popup window. Returns the HWND or nullptr on failure. -HWND CreateBgfxDebugWindow() +// TheSuperHackers @refactor bobtista 16/04/2026 Create a secondary +// popup window for the DX8 reference output. The D3D8 device is moved here +// so its rendering remains visible alongside bgfx on the main window. +HWND CreateDX8ReferenceWindow() { if (!RegisterBgfxDebugWindowClass()) { @@ -1574,26 +1551,28 @@ HWND CreateBgfxDebugWindow() const DWORD style = WS_OVERLAPPEDWINDOW; const DWORD exStyle = WS_EX_NOACTIVATE; - RECT rc = { 0, 0, kBgfxWindowWidth, kBgfxWindowHeight }; + RECT rc = { 0, 0, kDX8RefWindowWidth, kDX8RefWindowHeight }; AdjustWindowRectEx(&rc, style, FALSE, exStyle); const int frameW = rc.right - rc.left; const int frameH = rc.bottom - rc.top; - // Place the popup on the right edge of the primary monitor so the - // game's default top-left window and the bgfx popup are side by side - // without manual dragging. Fall back to (100,100) if the screen is - // narrower than the popup (multi-monitor edge case / RDP session). const int screenW = GetSystemMetrics(SM_CXSCREEN); const int screenH = GetSystemMetrics(SM_CYSCREEN); int posX = screenW - frameW - 10; int posY = 40; - if (posX < 0) posX = 100; - if (posY + frameH > screenH) posY = 10; + if (posX < 0) + { + posX = 100; + } + if (posY + frameH > screenH) + { + posY = 10; + } HWND hwnd = CreateWindowExW( exStyle, kBgfxWindowClass, - L"bgfx backend", + L"DX8 reference", style, posX, posY, frameW, frameH, @@ -1604,8 +1583,8 @@ HWND CreateBgfxDebugWindow() if (hwnd == nullptr) { - WWDEBUG_SAY(("[BgfxBackend] CreateWindowExW failed, GetLastError=%lu.", - GetLastError())); + WWDEBUG_SAY(("[BgfxBackend] CreateWindowExW for DX8 reference failed, " + "GetLastError=%lu.", GetLastError())); return nullptr; } @@ -1614,7 +1593,7 @@ HWND CreateBgfxDebugWindow() } } -void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) +void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) { if (g_bgfxInitialized) { @@ -1622,17 +1601,32 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) return; } - g_bgfxWindow = CreateBgfxDebugWindow(); + // TheSuperHackers @feature bobtista 16/04/2026 bgfx takes the + // main game window; DX8 moves to a secondary popup for reference. + g_bgfxWindow = static_cast(hwnd); if (g_bgfxWindow == nullptr) { - WWDEBUG_SAY(("[BgfxBackend] Could not create debug window. " - "Backend will remain dormant.")); + WWDEBUG_SAY(("[BgfxBackend] hwnd is null. Backend will remain dormant.")); return; } - // Force bgfx into single-threaded rendering mode by calling - // bgfx::renderFrame BEFORE bgfx::init. Makes bgfx::frame() fully - // synchronous on the calling thread. + RECT clientRect; + if (GetClientRect(g_bgfxWindow, &clientRect)) + { + g_bgfxWidth = clientRect.right - clientRect.left; + g_bgfxHeight = clientRect.bottom - clientRect.top; + } + if (g_bgfxWidth <= 0) + { + g_bgfxWidth = 800; + } + if (g_bgfxHeight <= 0) + { + g_bgfxHeight = 600; + } + WWDEBUG_SAY(("[BgfxBackend] Using main game window %p (%dx%d) for bgfx.", + g_bgfxWindow, g_bgfxWidth, g_bgfxHeight)); + bgfx::renderFrame(); bgfx::PlatformData pd; @@ -1644,21 +1638,16 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) bgfx::setPlatformData(pd); bgfx::Init initArgs; - initArgs.type = bgfx::RendererType::Count; // auto-select (D3D11 on Windows) + initArgs.type = bgfx::RendererType::Count; initArgs.callback = &g_bgfxCallback; - initArgs.resolution.width = static_cast(kBgfxWindowWidth); - initArgs.resolution.height = static_cast(kBgfxWindowHeight); - // enable 4x MSAA on the bgfx framebuffer. Without this, - // polygon boundaries between terrain tiles, rock shorelines, and - // unit silhouettes alias hard at the popup's lower resolution. + initArgs.resolution.width = static_cast(g_bgfxWidth); + initArgs.resolution.height = static_cast(g_bgfxHeight); initArgs.resolution.reset = BGFX_RESET_NONE; initArgs.platformData = pd; if (!bgfx::init(initArgs)) { - WWDEBUG_SAY(("[BgfxBackend] bgfx::init FAILED on debug window. " - "Backend will remain dormant.")); - DestroyWindow(g_bgfxWindow); + WWDEBUG_SAY(("[BgfxBackend] bgfx::init FAILED. Backend will remain dormant.")); g_bgfxWindow = nullptr; return; } @@ -1674,8 +1663,8 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) // for the DESTALPHA water blending technique. Without this, the // swap chain may use an opaque format (e.g. BGRX8) where alpha // is always 1.0, making destination-alpha blending ineffective. - bgfx::reset(static_cast(kBgfxWindowWidth), - static_cast(kBgfxWindowHeight), + bgfx::reset(static_cast(g_bgfxWidth), + static_cast(g_bgfxHeight), BGFX_RESET_NONE, bgfx::TextureFormat::RGBA8); @@ -1688,8 +1677,8 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) 1.0f, 0); bgfx::setViewRect(kBgfxDebugView, 0, 0, - static_cast(kBgfxWindowWidth), - static_cast(kBgfxWindowHeight)); + static_cast(g_bgfxWidth), + static_cast(g_bgfxHeight)); // View 1 is the engine geometry view. Same render target, but its // own clear/depth and (eventually) its own view+projection matrices @@ -1710,8 +1699,8 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) // Default sort can place decals before terrain → overwritten. bgfx::setViewMode(kBgfxEngineView, bgfx::ViewMode::Sequential); bgfx::setViewRect(kBgfxEngineView, 0, 0, - static_cast(kBgfxWindowWidth), - static_cast(kBgfxWindowHeight)); + static_cast(g_bgfxWidth), + static_cast(g_bgfxHeight)); // TheSuperHackers @refactor bobtista 11/04/2026 sorted // draws view. No clear (reuses view 1's color + depth so sorted @@ -1724,8 +1713,8 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) 1.0f, 0); bgfx::setViewRect(kBgfxEngineSortView, 0, 0, - static_cast(kBgfxWindowWidth), - static_cast(kBgfxWindowHeight)); + static_cast(g_bgfxWidth), + static_cast(g_bgfxHeight)); // Effect overlay view for dazzle draws with NDC-space vertices. // Permanent identity view + identity projection; reuses the @@ -1736,8 +1725,8 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) 1.0f, 0); bgfx::setViewRect(kBgfxEffectOverlayView, 0, 0, - static_cast(kBgfxWindowWidth), - static_cast(kBgfxWindowHeight)); + static_cast(g_bgfxWidth), + static_cast(g_bgfxHeight)); { float identityMtx[16]; IdentityMatrix(identityMtx); @@ -1750,16 +1739,16 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) // dirty-flag logic alongside view 1. bgfx::setViewClear(kBgfxShadowVolumeView, BGFX_CLEAR_NONE, 0, 1.0f, 0); bgfx::setViewRect(kBgfxShadowVolumeView, 0, 0, - static_cast(kBgfxWindowWidth), - static_cast(kBgfxWindowHeight)); + static_cast(g_bgfxWidth), + static_cast(g_bgfxHeight)); bgfx::setViewMode(kBgfxShadowVolumeView, bgfx::ViewMode::Sequential); // shadow darken apply pass. Sequential, identity transforms // (the fullscreen quad is authored in clip space). bgfx::setViewClear(kBgfxShadowApplyView, BGFX_CLEAR_NONE, 0, 1.0f, 0); bgfx::setViewRect(kBgfxShadowApplyView, 0, 0, - static_cast(kBgfxWindowWidth), - static_cast(kBgfxWindowHeight)); + static_cast(g_bgfxWidth), + static_cast(g_bgfxHeight)); bgfx::setViewMode(kBgfxShadowApplyView, bgfx::ViewMode::Sequential); { float identityMtx[16]; @@ -1767,6 +1756,20 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) bgfx::setViewTransform(kBgfxShadowApplyView, identityMtx, identityMtx); } + // UI overlay view. Sequential mode preserves draw order for + // 2D quads; identity view+projection; no clear so it composites over + // the 3D scene. + bgfx::setViewClear(kBgfxUIView, BGFX_CLEAR_NONE, 0, 1.0f, 0); + bgfx::setViewRect(kBgfxUIView, 0, 0, + static_cast(g_bgfxWidth), + static_cast(g_bgfxHeight)); + bgfx::setViewMode(kBgfxUIView, bgfx::ViewMode::Sequential); + { + float identityMtx[16]; + IdentityMatrix(identityMtx); + bgfx::setViewTransform(kBgfxUIView, identityMtx, identityMtx); + } + // Default the cached transforms to identity until the engine writes // real values via Set_Transform. This keeps the first few engine // submits well-defined even if they fire before any matrices are @@ -1986,6 +1989,7 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) kBgfxWaterView, // 4 kBgfxEngineSortView, // 2 kBgfxEffectOverlayView, // 5 + kBgfxUIView, // 10 — 2D UI overlay (last) }; bgfx::setViewOrder(kBgfxDebugView, BX_COUNTOF(order), order); WWDEBUG_SAY(("[CSM] view order set: shadow map (view %u) runs first", @@ -2008,9 +2012,9 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) const bgfx::RendererType::Enum selected = bgfx::getRendererType(); const char * rendererName = bgfx::getRendererName(selected); const bgfx::Caps * caps = bgfx::getCaps(); - WWDEBUG_SAY(("[BgfxBackend] bgfx::init OK on debug window " + WWDEBUG_SAY(("[BgfxBackend] bgfx::init OK on main window " "(renderer=%s, %dx%d, hwnd=%p, passthrough=%s, uber=%s).", - rendererName, kBgfxWindowWidth, kBgfxWindowHeight, + rendererName, g_bgfxWidth, g_bgfxHeight, g_bgfxWindow, bgfx::isValid(g_passthroughProgram) ? "ok" : "FAILED", bgfx::isValid(g_uberProgram) ? "ok" : "FAILED")); @@ -2025,6 +2029,24 @@ void BgfxBackend::Initialize(void * /*hwnd*/, int /*width*/, int /*height*/) rgba8Supported ? 1 : 0, bgra8Supported ? 1 : 0, caps->homogeneousDepth ? 1 : 0, caps->originBottomLeft ? 1 : 0)); + + // TheSuperHackers @feature bobtista 16/04/2026 Create a DX8 + // reference popup and move the D3D8 device to it so both renderers + // are visible simultaneously. + g_dx8ReferenceWindow = CreateDX8ReferenceWindow(); + if (g_dx8ReferenceWindow != nullptr) + { + DX8Wrapper::Set_Device_Window(g_dx8ReferenceWindow, + kDX8RefWindowWidth, + kDX8RefWindowHeight); + WWDEBUG_SAY(("[BgfxBackend] DX8 device moved to reference window %p (%dx%d).", + g_dx8ReferenceWindow, kDX8RefWindowWidth, kDX8RefWindowHeight)); + } + else + { + WWDEBUG_SAY(("[BgfxBackend] Could not create DX8 reference window. " + "DX8 stays on main window.")); + } } template @@ -2123,10 +2145,13 @@ void BgfxBackend::Shutdown() WWDEBUG_SAY(("[BgfxBackend] bgfx::shutdown complete.")); } - if (g_bgfxWindow != nullptr) + // The bgfx window is the main game window, do not destroy it. + g_bgfxWindow = nullptr; + + if (g_dx8ReferenceWindow != nullptr) { - DestroyWindow(g_bgfxWindow); - g_bgfxWindow = nullptr; + DestroyWindow(g_dx8ReferenceWindow); + g_dx8ReferenceWindow = nullptr; } } @@ -2149,11 +2174,13 @@ void BgfxBackend::Begin_Scene() bgfx::touch(kBgfxShadowVolumeView); bgfx::touch(kBgfxShadowApplyView); bgfx::touch(kBgfxShadowMapView); + bgfx::touch(kBgfxUIView); // CSM: light transform updated lazily at first draw // (see SubmitEngineDraw), not here, because g_bgfxView is still // identity at Begin_Scene time. g_shadowLightCaptured = false; - bgfx::setViewRect(kBgfxWaterView, 0, 0, kBgfxWindowWidth, kBgfxWindowHeight); + g_2DOverlayActive = false; + bgfx::setViewRect(kBgfxWaterView, 0, 0, g_bgfxWidth, g_bgfxHeight); // No clear on water view — it composites over the opaque scene. bgfx::setViewClear(kBgfxWaterView, BGFX_CLEAR_NONE, 0, 1.0f, 0); g_renderToTexture = false; @@ -2187,10 +2214,21 @@ void BgfxBackend::End_Scene(bool /*flip_frame*/) g_bgfxSortProjCaptured = false; } + // Push identity transforms and current rect to the UI view + // so 2D overlay draws land in screen space over the 3D scene. + { + float identityMtx[16]; + IdentityMatrix(identityMtx); + bgfx::setViewTransform(kBgfxUIView, identityMtx, identityMtx); + } + bgfx::setViewRect(kBgfxUIView, 0, 0, + static_cast(g_bgfxWidth), + static_cast(g_bgfxHeight)); + // The shadow map (view 8) renders FIRST so the depth // texture is populated before the scene samples it. Then RTT (3), // engine opaque (1), shadow volume fill (6), shadow darken (7), - // water (4), sort (2), effect overlay (5). + // water (4), sort (2), effect overlay (5), UI overlay (10) last. bgfx::ViewId viewOrder[] = { kBgfxShadowMapView, // 8 — shadow caster depth pass kBgfxRTTView, // 3 @@ -2200,6 +2238,7 @@ void BgfxBackend::End_Scene(bool /*flip_frame*/) kBgfxWaterView, // 4 kBgfxEngineSortView, // 2 kBgfxEffectOverlayView, // 5 + kBgfxUIView, // 10 — 2D UI overlay (last) }; bgfx::setViewOrder(kBgfxDebugView, BX_COUNTOF(viewOrder), viewOrder); @@ -2788,6 +2827,7 @@ void BgfxBackend::Capture_Sorted_Batch_Light(const D3DLIGHT8 & light, bool enabl static void BindTextureStages() { + // bgfx default (flags=0) is bilinear filtering. No explicit flags needed. if (bgfx::isValid(g_sTex0)) { const bgfx::TextureHandle bound = @@ -2934,6 +2974,17 @@ void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, ? g_currentBgfxState : (BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); + // TheSuperHackers @fix bobtista 16/04/2026 Override cull mode from + // D3D device state — same as SubmitEngineDraw. + { + unsigned d3dCull = DX8Wrapper::Get_DX8_Render_State(D3DRS_CULLMODE); + state &= ~(BGFX_STATE_CULL_CW | BGFX_STATE_CULL_CCW); + if (d3dCull == 2) // D3DCULL_CW + state |= BGFX_STATE_CULL_CW; + else if (d3dCull == 3) // D3DCULL_CCW + state |= BGFX_STATE_CULL_CCW; + } + // Apply color write mask override — same as SubmitEngineDraw. // Without this, sorted particles/effects write alpha, destroying // the shoreline depth gradient used by DESTALPHA water blending. @@ -3730,10 +3781,10 @@ void BgfxBackend::Set_Transform(TransformKind transform, const Matrix4x4 & m) case RB_TRANSFORM_VIEW: W3DMatrix4ToBgfx(m, g_bgfxView); g_bgfxViewProjDirty = true; + g_2DOverlayActive = false; break; case RB_TRANSFORM_PROJECTION: W3DMatrix4ToBgfx(m, g_bgfxProj); - ApplyPopupAspectCorrection(g_bgfxProj); g_bgfxViewProjDirty = true; break; default: @@ -3753,6 +3804,7 @@ void BgfxBackend::Set_Transform(TransformKind transform, const Matrix3D & m) case RB_TRANSFORM_VIEW: W3DMatrix3DToBgfx(m, g_bgfxView); g_bgfxViewProjDirty = true; + g_2DOverlayActive = false; break; default: break; @@ -3773,6 +3825,7 @@ void BgfxBackend::Set_View_Identity() DX8Backend::Set_View_Identity(); IdentityMatrix(g_bgfxView); g_bgfxViewProjDirty = true; + g_2DOverlayActive = true; } void BgfxBackend::Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & matrix, @@ -3781,7 +3834,6 @@ void BgfxBackend::Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & matrix, DX8Backend::Set_Projection_Transform_With_Z_Bias(matrix, znear, zfar); W3DMatrix4ToBgfx(matrix, g_bgfxProj); - ApplyPopupAspectCorrection(g_bgfxProj); g_bgfxViewProjDirty = true; } @@ -3845,7 +3897,11 @@ void SubmitEngineDraw(unsigned short start_index, // so it never needs a per-submit setViewTransform - only view 1 // (the opaque view) uses the dirty flag. bgfx::ViewId submitView; - if (g_renderToTexture) + if (g_2DOverlayActive) + { + submitView = kBgfxUIView; + } + else if (g_renderToTexture) { submitView = kBgfxRTTView; } @@ -3871,7 +3927,7 @@ void SubmitEngineDraw(unsigned short start_index, // applies until the next change so we do not need to call it per // submit, only when the engine has updated either matrix. Sort view // draws never touch g_bgfxView so the opaque view is never stomped. - if (!g_inSortFlush && !g_renderToTexture && g_bgfxViewProjDirty) + if (!g_inSortFlush && !g_renderToTexture && !g_2DOverlayActive && g_bgfxViewProjDirty) { // Capture the camera view+proj at the first opaque draw of each // frame. Later Set_Projection calls (water, shadows, sneak attack) @@ -4108,6 +4164,20 @@ void SubmitEngineDraw(unsigned short start_index, : (BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); state |= BGFX_STATE_MSAA; + // TheSuperHackers @fix bobtista 16/04/2026 Override cull mode from + // D3D device state. ShaderClass::Apply() may change D3DRS_CULLMODE + // directly (e.g. water reflection inversion) without going through + // g_renderBackend->Set_Cull_Mode(). + { + unsigned d3dCull = DX8Wrapper::Get_DX8_Render_State(D3DRS_CULLMODE); + state &= ~(BGFX_STATE_CULL_CW | BGFX_STATE_CULL_CCW); + if (d3dCull == 2) // D3DCULL_CW + state |= BGFX_STATE_CULL_CW; + else if (d3dCull == 3) // D3DCULL_CCW + state |= BGFX_STATE_CULL_CCW; + // D3DCULL_NONE (1) = no cull bits set + } + // Apply post-ShaderClass blend override (terrain blend, W3DCustomEdging, etc.) if (g_blendOverrideActive) { @@ -4193,7 +4263,8 @@ void SubmitEngineDraw(unsigned short start_index, // the per-view transform (set in UpdateShadowLightTransform) so // the caster rasterizes from the light's POV. const bool isShadowCaster = - (submitView == kBgfxEngineView || submitView == kBgfxEngineSortView); + (submitView == kBgfxEngineView || submitView == kBgfxEngineSortView) + && !g_2DOverlayActive; const bool hasVB = g_currentUseTransientVB || bgfx::isValid(g_currentBgfxVB); if (isShadowCaster && bgfx::isValid(g_shadowCasterProgram) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp index f4400f57e27..fc5b798b274 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp @@ -1295,6 +1295,30 @@ bool DX8Wrapper::Set_Device_Resolution(int width,int height,int bits,int windowe } } +// TheSuperHackers @feature bobtista 16/04/2026 Move the DX8 device to a +// different window. Updates the present parameters and resets the device so +// it renders into the given hwnd at the specified dimensions. +void DX8Wrapper::Set_Device_Window(HWND hwnd, int width, int height) +{ + if (D3DDevice == nullptr) + { + return; + } + + // TheSuperHackers @refactor bobtista 18/04/2026 move the D3D8 + // device to a reference popup window. Keep the original backbuffer + // resolution so the game's UI layout calculations stay correct — + // D3D8 stretches the output to fit the popup window automatically. + _Hwnd = hwnd; + _PresentParameters.hDeviceWindow = hwnd; + _PresentParameters.Windowed = TRUE; + + WWDEBUG_SAY(("DX8Wrapper::Set_Device_Window moving device to hwnd=%p (%dx%d), " + "keeping backbuffer at %dx%d.", + hwnd, width, height, ResolutionWidth, ResolutionHeight)); + Reset_Device(true); +} + void DX8Wrapper::Get_Device_Resolution(int & set_w,int & set_h,int & set_bits,bool & set_windowed) { WWASSERT(IsInitted); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h index 1f38125f9e2..ea65dcb7b57 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h @@ -544,6 +544,9 @@ class DX8Wrapper static WW3DFormat getBackBufferFormat(); static bool Reset_Device(bool reload_assets=true); + // TheSuperHackers @feature bobtista 16/04/2026 Move DX8 device to a different window + static void Set_Device_Window(HWND hwnd, int width, int height); + static const DX8Caps* Get_Current_Caps() { WWASSERT(CurrentCaps); return CurrentCaps; } static bool Registry_Save_Render_Device( const char * sub_key ); diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/render2d.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/render2d.cpp index a36cf4a5901..5fc3225b892 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/render2d.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/render2d.cpp @@ -47,6 +47,7 @@ #include "vertmaterial.h" #include "WW3D2/dx8fvf.h" #include "WW3D2/dx8caps.h" +#include "WW3D2/RenderBackend.h" #include "WWDebug/wwprofile.h" #include "WWDebug/wwmemlog.h" #include "assetmgr.h" @@ -533,8 +534,8 @@ void Render2DClass::Render() Matrix4x4 view,proj; Matrix4x4 identity(true); - DX8Wrapper::Get_Transform(D3DTS_VIEW,view); - DX8Wrapper::Get_Transform(D3DTS_PROJECTION,proj); + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW,view); + g_renderBackend->Get_Transform(RB_TRANSFORM_PROJECTION,proj); // // Configure the viewport for entire screen @@ -549,15 +550,15 @@ void Render2DClass::Render() DX8Wrapper::Set_Viewport(&vp); - DX8Wrapper::Set_Texture(0,Texture); + g_renderBackend->Set_Texture(0,Texture); VertexMaterialClass *vm=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vm); + g_renderBackend->Set_Material(vm); REF_PTR_RELEASE(vm); - DX8Wrapper::Set_World_Identity(); - DX8Wrapper::Set_View_Identity(); - DX8Wrapper::Set_Transform(D3DTS_PROJECTION,identity); + g_renderBackend->Set_World_Identity(); + g_renderBackend->Set_View_Identity(); + g_renderBackend->Set_Transform(RB_TRANSFORM_PROJECTION,identity); DynamicVBAccessClass vb(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,Vertices.Count()); { @@ -584,13 +585,13 @@ void Render2DClass::Render() mem[i]=Indices[i]; } - DX8Wrapper::Set_Vertex_Buffer(vb); - DX8Wrapper::Set_Index_Buffer(ib,0); + g_renderBackend->Set_Vertex_Buffer(vb); + g_renderBackend->Set_Index_Buffer(ib,0); if (IsGrayScale) { //special case added to draw grayscale non-alpha blended images. - DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); - DX8Wrapper::Apply_Render_State_Changes(); //force update of all regular W3D states. + g_renderBackend->Set_Shader(ShaderClass::_PresetOpaqueShader); + g_renderBackend->Apply_Render_State_Changes(); //force update of all regular W3D states. if (DX8Wrapper::Get_Current_Caps()->Support_Dot3()) { //Override W3D states with customizations for grayscale DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR, 0x80A5CA8E); @@ -615,14 +616,14 @@ void Render2DClass::Render() } } else - DX8Wrapper::Set_Shader(Shader); + g_renderBackend->Set_Shader(Shader); - DX8Wrapper::Draw_Triangles(0,Indices.Count()/3,0,Vertices.Count()); + g_renderBackend->Draw_Triangles(0,Indices.Count()/3,0,Vertices.Count()); // SphereClass sphere(Vector3(0.0f,0.0f,0.0f),0.0f); // SortingRendererClass::Insert_Triangles(sphere,0,Indices.Count()/3,0,Vertices.Count()); - DX8Wrapper::Set_Transform(D3DTS_VIEW,view); - DX8Wrapper::Set_Transform(D3DTS_PROJECTION,proj); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW,view); + g_renderBackend->Set_Transform(RB_TRANSFORM_PROJECTION,proj); if (IsGrayScale) ShaderClass::Invalidate(); //force both stages to be reset. @@ -781,4 +782,3 @@ Vector2 Render2DTextClass::Get_Text_Extents( const WCHAR * text ) return extent; } - diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 8941965c08b..17a14c10a83 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -918,7 +918,14 @@ void W3DDisplay::init() WW3D::Set_Collision_Box_Display_Mask(0x00); ///m_windowed ); diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/render2d.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/render2d.cpp index 36592e83276..217b1d392f0 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/render2d.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/render2d.cpp @@ -54,6 +54,7 @@ #include "vertmaterial.h" #include "WW3D2/dx8fvf.h" #include "WW3D2/dx8caps.h" +#include "WW3D2/RenderBackend.h" #include "WWDebug/wwprofile.h" #include "WWDebug/wwmemlog.h" #include "assetmgr.h" @@ -606,8 +607,8 @@ void Render2DClass::Render() Matrix4x4 view,proj; Matrix4x4 identity(true); - DX8Wrapper::Get_Transform(D3DTS_VIEW,view); - DX8Wrapper::Get_Transform(D3DTS_PROJECTION,proj); + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW,view); + g_renderBackend->Get_Transform(RB_TRANSFORM_PROJECTION,proj); // // Configure the viewport for entire screen @@ -623,15 +624,15 @@ void Render2DClass::Render() vp.MinZ = 0; vp.MaxZ = 1; DX8Wrapper::Set_Viewport(&vp); - DX8Wrapper::Set_Texture(0,Texture); + g_renderBackend->Set_Texture(0,Texture); VertexMaterialClass *vm=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); - DX8Wrapper::Set_Material(vm); + g_renderBackend->Set_Material(vm); REF_PTR_RELEASE(vm); - DX8Wrapper::Set_World_Identity(); - DX8Wrapper::Set_View_Identity(); - DX8Wrapper::Set_Transform(D3DTS_PROJECTION,identity); + g_renderBackend->Set_World_Identity(); + g_renderBackend->Set_View_Identity(); + g_renderBackend->Set_Transform(RB_TRANSFORM_PROJECTION,identity); DynamicVBAccessClass vb(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,Vertices.Count()); { @@ -658,13 +659,13 @@ void Render2DClass::Render() mem[i]=Indices[i]; } - DX8Wrapper::Set_Vertex_Buffer(vb); - DX8Wrapper::Set_Index_Buffer(ib,0); + g_renderBackend->Set_Vertex_Buffer(vb); + g_renderBackend->Set_Index_Buffer(ib,0); if (IsGrayScale) { //special case added to draw grayscale non-alpha blended images. - DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); - DX8Wrapper::Apply_Render_State_Changes(); //force update of all regular W3D states. + g_renderBackend->Set_Shader(ShaderClass::_PresetOpaqueShader); + g_renderBackend->Apply_Render_State_Changes(); //force update of all regular W3D states. if (DX8Wrapper::Get_Current_Caps()->Support_Dot3()) { //Override W3D states with customizations for grayscale DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR, 0x80A5CA8E); @@ -689,11 +690,11 @@ void Render2DClass::Render() } } else - DX8Wrapper::Set_Shader(Shader); - DX8Wrapper::Draw_Triangles(0,Indices.Count()/3,0,Vertices.Count()); + g_renderBackend->Set_Shader(Shader); + g_renderBackend->Draw_Triangles(0,Indices.Count()/3,0,Vertices.Count()); - DX8Wrapper::Set_Transform(D3DTS_VIEW,view); - DX8Wrapper::Set_Transform(D3DTS_PROJECTION,proj); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW,view); + g_renderBackend->Set_Transform(RB_TRANSFORM_PROJECTION,proj); if (IsGrayScale) ShaderClass::Invalidate(); //force both stages to be reset. @@ -854,4 +855,3 @@ Vector2 Render2DTextClass::Get_Text_Extents( const WCHAR * text ) return extent; } - From 70d3f42c65423b46b8d6ab12073de499e4e10f46 Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 21 Apr 2026 14:02:48 -0400 Subject: [PATCH 055/523] feat(bgfx): Mouse input, window sizing, 2D overlay detection, single-pass terrain blend (cherry picked from commit a8ff9d4a291697f14a7b2631e10f928903923d4b) --- .../Source/W3DDevice/GameClient/HeightMap.cpp | 14 ++ .../W3DDevice/GameClient/W3DShaderManager.cpp | 72 +++++-- .../Source/WWVegas/WW3D2/BgfxBackend.cpp | 176 ++++++++---------- .../Source/WWVegas/WW3D2/dx8wrapper.cpp | 82 ++++++++ GeneralsMD/Code/Main/WinMain.cpp | 2 + 5 files changed, 240 insertions(+), 106 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp index 5c3f56078a6..6744607a436 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp @@ -1938,9 +1938,18 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) Bool doMultiPassWireFrame=FALSE; +#if defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @feature bobtista 19/04/2026 the alpha mask + // and shroud early-return paths use D3D8 pixel shaders (shroud.pso) that + // bgfx cannot interpret. Skip them entirely so terrain always renders + // through the normal W3DShaderManager path which sets proper TSS ops. + if (false) + { +#else if (((RTS3DScene *)rinfo.Camera.Get_User_Data())->getCustomPassMode() == SCENE_PASS_ALPHA_MASK || ((SceneClass *)rinfo.Camera.Get_User_Data())->Get_Extra_Pass_Polygon_Mode() == SceneClass::EXTRA_PASS_CLEAR_LINE) { +#endif if (WW3D::Is_Texturing_Enabled()) { //first pass where we just fill the z-buffer @@ -2122,12 +2131,17 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) if (TheTerrainTracksRenderObjClassSystem) TheTerrainTracksRenderObjClassSystem->flush(); +#if !defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @feature bobtista 19/04/2026 shroud + // overlay pass uses D3D8 pixel shaders. Skip for bgfx — shroud + // is handled separately via the bgfx uber shader's shroud path. if (m_shroud && rinfo.Additional_Pass_Count()) { rinfo.Peek_Additional_Pass(0)->Install_Materials(); renderTerrainPass(&rinfo.Camera); rinfo.Peek_Additional_Pass(0)->UnInstall_Materials(); } +#endif ShaderClass::Invalidate(); g_renderBackend->Apply_Render_State_Changes(); diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp index 273719bb3a4..6f52abda810 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp @@ -1595,19 +1595,29 @@ Int TerrainShader2Stage::init() //no special device validation needed - anything in our min spec should handle this. W3DShaders[W3DShaderManager::ST_TERRAIN_BASE]=&terrainShader2Stage; - W3DShadersPassCount[W3DShaderManager::ST_TERRAIN_BASE]=2; W3DShaders[W3DShaderManager::ST_TERRAIN_BASE_NOISE1]=&terrainShader2Stage; - W3DShadersPassCount[W3DShaderManager::ST_TERRAIN_BASE_NOISE1]=3; W3DShaders[W3DShaderManager::ST_TERRAIN_BASE_NOISE2]=&terrainShader2Stage; - W3DShadersPassCount[W3DShaderManager::ST_TERRAIN_BASE_NOISE2]=3; W3DShaders[W3DShaderManager::ST_TERRAIN_BASE_NOISE12]=&terrainShader2Stage; +#if defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @feature bobtista 19/04/2026 bgfx does terrain blend + // in a single pass. Cloud/noise passes (2,3) are not yet supported. + W3DShadersPassCount[W3DShaderManager::ST_TERRAIN_BASE]=1; + W3DShadersPassCount[W3DShaderManager::ST_TERRAIN_BASE_NOISE1]=1; + W3DShadersPassCount[W3DShaderManager::ST_TERRAIN_BASE_NOISE2]=1; + W3DShadersPassCount[W3DShaderManager::ST_TERRAIN_BASE_NOISE12]=1; +#else + W3DShadersPassCount[W3DShaderManager::ST_TERRAIN_BASE]=2; + W3DShadersPassCount[W3DShaderManager::ST_TERRAIN_BASE_NOISE1]=3; + W3DShadersPassCount[W3DShaderManager::ST_TERRAIN_BASE_NOISE2]=3; W3DShadersPassCount[W3DShaderManager::ST_TERRAIN_BASE_NOISE12]=3; +#endif return TRUE; } void TerrainShader2Stage::reset() { + g_renderBackend->Override_Terrain_Blend(false); ShaderClass::Invalidate(); //Free references to textures @@ -1661,9 +1671,15 @@ Int TerrainShader2Stage::set(Int pass) if (!s_loggedTerrainShader) { s_loggedTerrainShader = true; - WWDEBUG_SAY(("[BgfxBackend] TerrainShader2Stage::set first fire pass=%d", pass)); + WWDEBUG_SAY(("[W3DShaderManager] TerrainShader2Stage::set first fire pass=%d", pass)); } +#if defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @feature bobtista 19/04/2026 bgfx blends base and + // per-tile textures in a single pass via the uber shader's terrain + // blend path. Pass 0 has stage 0 = base texture (from shader manager) + // and stage 1 = per-tile blend texture (from drawVisiblePolys). g_renderBackend->Override_Terrain_Blend(true); +#endif //force WW3D2 system to set it's states so it won't later overwrite our custom settings. g_renderBackend->Apply_Render_State_Changes(); @@ -1809,6 +1825,11 @@ Int TerrainShader2Stage::set(Int pass) Int TerrainShader8Stage::init() { +#if defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @feature bobtista 19/04/2026 8-stage multi-texture + // is invisible to bgfx. Fall through to the 2Stage variant. + return FALSE; +#endif ChipsetType res; //this shader will also use the 2Stage shader for some of the passes so initialize it too. @@ -1836,7 +1857,7 @@ Int TerrainShader8Stage::set(Int pass) if (!s_logged8Stage) { s_logged8Stage = true; - WWDEBUG_SAY(("[BgfxBackend] TerrainShader8Stage::set first fire")); + WWDEBUG_SAY(("[W3DShaderManager] TerrainShader8Stage::set first fire")); } g_renderBackend->Override_Terrain_Blend(true); @@ -1955,6 +1976,7 @@ Int TerrainShader8Stage::set(Int pass) void TerrainShader8Stage::reset() { + g_renderBackend->Override_Terrain_Blend(false); DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_COLOROP, D3DTOP_DISABLE); DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_ALPHAOP, D3DTOP_DISABLE); DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_COLOROP, D3DTOP_DISABLE); @@ -1990,6 +2012,11 @@ Int TerrainShaderPixelShader::init() Int res; #ifdef DISABLE_PIXEL_SHADERS return false; +#endif +#if defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @feature bobtista 19/04/2026 D3D8 pixel shaders are + // invisible to bgfx. Fall through to the 2Stage variant. + return FALSE; #endif //this shader will also use the 2Stage shader for some of the passes so initialize it too. if (terrainShader2Stage.init() && (res=W3DShaderManager::getChipset()) >= DC_GENERIC_PIXEL_SHADER_1_1) @@ -2039,7 +2066,9 @@ Int TerrainShaderPixelShader::init() Int TerrainShaderPixelShader::set(Int pass) { - g_renderBackend->Override_Terrain_Blend(true); + // Do not set terrain blend — the D3D8 pixel shader binds textures + // differently than what the bgfx terrain blend path expects. The + // normal TSS path handles the texture operations correctly. //force WW3D2 system to set it's states so it won't later overwrite our custom settings. g_renderBackend->Apply_Render_State_Changes(); @@ -2147,6 +2176,7 @@ Int TerrainShaderPixelShader::set(Int pass) void TerrainShaderPixelShader::reset() { + g_renderBackend->Override_Terrain_Blend(false); DX8Wrapper::_Get_D3D_Device8()->SetTexture(2,nullptr); //release reference to any texture DX8Wrapper::_Get_D3D_Device8()->SetTexture(3,nullptr); //release reference to any texture @@ -3297,19 +3327,27 @@ Int FlatTerrainShader2Stage::init() //no special device validation needed - anything in our min spec should handle this. W3DShaders[W3DShaderManager::ST_FLAT_TERRAIN_BASE]=&flatTerrainShader2Stage; - W3DShadersPassCount[W3DShaderManager::ST_FLAT_TERRAIN_BASE]=1; W3DShaders[W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE1]=&flatTerrainShader2Stage; - W3DShadersPassCount[W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE1]=2; W3DShaders[W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE2]=&flatTerrainShader2Stage; - W3DShadersPassCount[W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE2]=2; W3DShaders[W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE12]=&flatTerrainShader2Stage; +#if defined(GGC_RENDER_BACKEND_BGFX) + W3DShadersPassCount[W3DShaderManager::ST_FLAT_TERRAIN_BASE]=1; + W3DShadersPassCount[W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE1]=1; + W3DShadersPassCount[W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE2]=1; + W3DShadersPassCount[W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE12]=1; +#else + W3DShadersPassCount[W3DShaderManager::ST_FLAT_TERRAIN_BASE]=1; + W3DShadersPassCount[W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE1]=2; + W3DShadersPassCount[W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE2]=2; W3DShadersPassCount[W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE12]=2; +#endif return TRUE; } void FlatTerrainShader2Stage::reset() { + g_renderBackend->Override_Terrain_Blend(false); ShaderClass::Invalidate(); //Free references to textures @@ -3327,8 +3365,10 @@ void FlatTerrainShader2Stage::reset() Int FlatTerrainShader2Stage::set(Int pass) { static bool s_loggedFlat2 = false; - if (!s_loggedFlat2) { s_loggedFlat2 = true; WWDEBUG_SAY(("[BgfxBackend] FlatTerrainShader2Stage::set first fire pass=%d", pass)); } + if (!s_loggedFlat2) { s_loggedFlat2 = true; WWDEBUG_SAY(("[W3DShaderManager] FlatTerrainShader2Stage::set first fire pass=%d", pass)); } +#if defined(GGC_RENDER_BACKEND_BGFX) g_renderBackend->Override_Terrain_Blend(true); +#endif //force WW3D2 system to set it's states so it won't later overwrite our custom settings. g_renderBackend->Apply_Render_State_Changes(); @@ -3546,6 +3586,14 @@ Int FlatTerrainShaderPixelShader::init() return false; #endif +#if defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @feature bobtista 19/04/2026 D3D8 pixel shaders are + // invisible to the bgfx backend. Skip pixel shader terrain variants so + // the 2Stage fallback is used — it sets proper TSS ops that the bgfx + // uber shader can interpret. + return FALSE; +#endif + //this shader will also use the 2Stage shader for some of the passes so initialize it too. if ((res=W3DShaderManager::getChipset()) >= DC_GENERIC_PIXEL_SHADER_1_1) { @@ -3599,7 +3647,8 @@ Int FlatTerrainShaderPixelShader::init() Int FlatTerrainShaderPixelShader::set(Int pass) { - g_renderBackend->Override_Terrain_Blend(true); + // Do not set terrain blend — flat terrain uses a single texture + // with vertex-colored lighting, not a two-texture blend. //setup base pass Int curStage = 1; // setup terrain [3/31/2003] @@ -3753,6 +3802,7 @@ Int FlatTerrainShaderPixelShader::set(Int pass) void FlatTerrainShaderPixelShader::reset() { + g_renderBackend->Override_Terrain_Blend(false); DX8Wrapper::_Get_D3D_Device8()->SetTexture(2,nullptr); //release reference to any texture DX8Wrapper::_Get_D3D_Device8()->SetTexture(3,nullptr); //release reference to any texture diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp index bb141c7648f..b95baa8cd7c 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -144,11 +144,7 @@ bool g_bgfxInitialized = false; // into the main game window. The DX8 reference popup is a secondary window // where the D3D8 device is moved so its output remains visible for debugging. HWND g_bgfxWindow = nullptr; -HWND g_dx8ReferenceWindow = nullptr; - const wchar_t * const kBgfxWindowClass = L"GGC_BgfxDebugWindow"; -const int kDX8RefWindowWidth = 800; -const int kDX8RefWindowHeight = 600; // TheSuperHackers @refactor bobtista 16/04/2026 bgfx resolution // matches the main game window. Set during Initialize from GetClientRect. @@ -1538,59 +1534,6 @@ static void UpdateShadowLightTransform() g_shadowLightCaptured = true; } -// TheSuperHackers @refactor bobtista 16/04/2026 Create a secondary -// popup window for the DX8 reference output. The D3D8 device is moved here -// so its rendering remains visible alongside bgfx on the main window. -HWND CreateDX8ReferenceWindow() -{ - if (!RegisterBgfxDebugWindowClass()) - { - return nullptr; - } - - const DWORD style = WS_OVERLAPPEDWINDOW; - const DWORD exStyle = WS_EX_NOACTIVATE; - - RECT rc = { 0, 0, kDX8RefWindowWidth, kDX8RefWindowHeight }; - AdjustWindowRectEx(&rc, style, FALSE, exStyle); - const int frameW = rc.right - rc.left; - const int frameH = rc.bottom - rc.top; - - const int screenW = GetSystemMetrics(SM_CXSCREEN); - const int screenH = GetSystemMetrics(SM_CYSCREEN); - int posX = screenW - frameW - 10; - int posY = 40; - if (posX < 0) - { - posX = 100; - } - if (posY + frameH > screenH) - { - posY = 10; - } - - HWND hwnd = CreateWindowExW( - exStyle, - kBgfxWindowClass, - L"DX8 reference", - style, - posX, posY, - frameW, frameH, - nullptr, - nullptr, - GetModuleHandleW(nullptr), - nullptr); - - if (hwnd == nullptr) - { - WWDEBUG_SAY(("[BgfxBackend] CreateWindowExW for DX8 reference failed, " - "GetLastError=%lu.", GetLastError())); - return nullptr; - } - - ShowWindow(hwnd, SW_SHOWNOACTIVATE); - return hwnd; -} } void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) @@ -1654,19 +1597,10 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) g_bgfxInitialized = true; - // TheSuperHackers @refactor bobtista 11/04/2026 force - // a bgfx::reset() after init so the MSAA flag actually takes effect. - // On D3D11 bgfx needs an explicit reset call to rebuild the swapchain - // with a multi-sampled back buffer; setting resolution.reset in the - // Init struct alone isn't enough in every bgfx build. - // Force RGBA8 back buffer format so the alpha channel is available - // for the DESTALPHA water blending technique. Without this, the - // swap chain may use an opaque format (e.g. BGRX8) where alpha - // is always 1.0, making destination-alpha blending ineffective. - bgfx::reset(static_cast(g_bgfxWidth), - static_cast(g_bgfxHeight), - BGFX_RESET_NONE, - bgfx::TextureFormat::RGBA8); + // TheSuperHackers @refactor bobtista 16/04/2026 The explicit + // bgfx::reset() after init is removed because it triggers a DXGI + // assertion when bgfx owns the main game HWND. The init call already + // configured the resolution and format correctly. // Configure view 0 to clear the debug window to a dark teal so it's // visually obvious bgfx is running and alive. View 0 holds the test @@ -2030,23 +1964,9 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) caps->homogeneousDepth ? 1 : 0, caps->originBottomLeft ? 1 : 0)); - // TheSuperHackers @feature bobtista 16/04/2026 Create a DX8 - // reference popup and move the D3D8 device to it so both renderers - // are visible simultaneously. - g_dx8ReferenceWindow = CreateDX8ReferenceWindow(); - if (g_dx8ReferenceWindow != nullptr) - { - DX8Wrapper::Set_Device_Window(g_dx8ReferenceWindow, - kDX8RefWindowWidth, - kDX8RefWindowHeight); - WWDEBUG_SAY(("[BgfxBackend] DX8 device moved to reference window %p (%dx%d).", - g_dx8ReferenceWindow, kDX8RefWindowWidth, kDX8RefWindowHeight)); - } - else - { - WWDEBUG_SAY(("[BgfxBackend] Could not create DX8 reference window. " - "DX8 stays on main window.")); - } + // TheSuperHackers @refactor bobtista 16/04/2026 DX8 now creates + // its own secondary reference window in DX8Wrapper::Init, so no need to + // create or move anything here. } template @@ -2146,13 +2066,8 @@ void BgfxBackend::Shutdown() } // The bgfx window is the main game window, do not destroy it. + // DX8's secondary reference window is owned by DX8Wrapper. g_bgfxWindow = nullptr; - - if (g_dx8ReferenceWindow != nullptr) - { - DestroyWindow(g_dx8ReferenceWindow); - g_dx8ReferenceWindow = nullptr; - } } // Device state queries (Is_Device_Lost / Has_Stencil / Get_Back_Buffer_Format @@ -2166,6 +2081,55 @@ void BgfxBackend::Begin_Scene() { return; } + // Show the DX8 reference popup after a few frames, giving the game's + // input system time to fully initialize. Showing too early steals focus + // and permanently blocks mouse capture. + { + static int s_dx8RefFrameCount = 0; + if (s_dx8RefFrameCount >= 0) + { + s_dx8RefFrameCount++; + if (s_dx8RefFrameCount > 30) + { + s_dx8RefFrameCount = -1; + HWND dx8Hwnd = FindWindowW(L"GGC_DX8RefWindow", nullptr); + if (dx8Hwnd) + { + ShowWindow(dx8Hwnd, SW_SHOWNA); + } + // Re-assert focus on the main game window. Use SetFocus + // instead of SetForegroundWindow which has restrictions + // on Windows that can cause it to silently fail. + if (g_bgfxWindow) + { + SetForegroundWindow(g_bgfxWindow); + SetFocus(g_bgfxWindow); + } + } + } + } + + // Check if the game window was resized (e.g., by Set_Render_Device) and + // update bgfx's swapchain to match. Without this, bgfx renders at the + // old resolution while the game expects the new one. + if (g_bgfxWindow) + { + RECT cr; + if (GetClientRect(g_bgfxWindow, &cr)) + { + int w = cr.right - cr.left; + int h = cr.bottom - cr.top; + if (w > 0 && h > 0 && (w != g_bgfxWidth || h != g_bgfxHeight)) + { + WWDEBUG_SAY(("[BgfxBackend] Window resized %dx%d -> %dx%d, calling bgfx::reset.", + g_bgfxWidth, g_bgfxHeight, w, h)); + g_bgfxWidth = w; + g_bgfxHeight = h; + bgfx::reset(g_bgfxWidth, g_bgfxHeight, BGFX_RESET_NONE); + } + } + } + bgfx::touch(kBgfxDebugView); bgfx::touch(kBgfxEngineView); bgfx::touch(kBgfxEngineSortView); @@ -3723,7 +3687,11 @@ void BgfxBackend::Clear_State_Overrides() g_suppressBgfxDraw = false; g_colorWriteOverride = -1; g_currentTexcoordSelect[0] = 0.0f; - g_currentTexcoordSelect[1] = 0.0f; + // Do NOT clear g_currentTexcoordSelect[1] (terrain blend) here. + // Override_Terrain_Blend is called from the shader manager BEFORE + // Set_Shader, so clearing it in Set_Shader (which calls us) would + // undo the terrain blend flag every frame. Terrain blend is reset + // at Begin_Scene and by Override_Terrain_Blend(false) explicitly. } void BgfxBackend::Set_Light_Environment(LightEnvironmentClass * light_env) @@ -3896,8 +3864,26 @@ void SubmitEngineDraw(unsigned short start_index, // init and are refreshed by Set_Projection_Transform_With_Z_Bias, // so it never needs a per-submit setViewTransform - only view 1 // (the opaque view) uses the dirty flag. + // Secondary 2D detection: if the view matrix is identity and the + // projection has no perspective (w-divide), this is a 2D overlay + // draw even if Set_View_Identity wasn't called. This catches draws + // where DX8 state restores clear g_2DOverlayActive between the + // Set_View_Identity call and the actual Draw_Triangles. + bool is2D = g_2DOverlayActive; + if (!is2D && !g_renderToTexture && !g_waterOverrideActive + && !g_effectOverlayActive && !g_inSortFlush) + { + // Check if view matrix is identity (2D mode) + const float *v = g_bgfxView; + if (v[0] == 1.0f && v[5] == 1.0f && v[10] == 1.0f && v[15] == 1.0f + && v[1] == 0.0f && v[2] == 0.0f && v[4] == 0.0f && v[6] == 0.0f) + { + is2D = true; + } + } + bgfx::ViewId submitView; - if (g_2DOverlayActive) + if (is2D) { submitView = kBgfxUIView; } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp index fc5b798b274..89f180d8b39 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp @@ -110,6 +110,9 @@ int DX8Wrapper_PreserveFPU = 0; ***********************************************************************************/ static HWND _Hwnd = nullptr; +#if defined(GGC_RENDER_BACKEND_BGFX) +static HWND _GameHwndForBgfx = nullptr; +#endif bool DX8Wrapper::IsInitted = false; bool DX8Wrapper::_EnableTriangleDraw = true; @@ -264,6 +267,38 @@ bool DX8Wrapper::Init(void * hwnd, bool lite) /* ** Initialize all variables! */ + + // TheSuperHackers @feature bobtista 19/04/2026 when bgfx is + // the active render backend, D3D8 uses a secondary reference window + // so bgfx can take the main game HWND without DXGI swapchain conflict. + // Save the original game HWND for bgfx before redirecting D3D8. +#if defined(GGC_RENDER_BACKEND_BGFX) + _GameHwndForBgfx = (HWND)hwnd; + { + WNDCLASSEXW wc = {}; + wc.cbSize = sizeof(WNDCLASSEXW); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.lpfnWndProc = DefWindowProcW; + wc.hInstance = GetModuleHandleW(nullptr); + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.lpszClassName = L"GGC_DX8RefWindow"; + RegisterClassExW(&wc); + + // Create the window hidden — it will be shown later after + // the game's input system is fully initialized. Creating it + // visible during Init steals focus and blocks mouse capture. + HWND refWnd = CreateWindowExW( + WS_EX_NOACTIVATE, L"GGC_DX8RefWindow", L"DX8 reference", + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, + nullptr, nullptr, GetModuleHandleW(nullptr), nullptr); + if (refWnd) + { + hwnd = refWnd; + } + } +#endif + _Hwnd = (HWND)hwnd; _MainThreadID=ThreadClass::_Get_Current_Thread_ID(); WWDEBUG_SAY(("DX8Wrapper main thread: 0x%x",_MainThreadID)); @@ -391,7 +426,12 @@ void DX8Wrapper::Do_Onetime_Device_Dependent_Inits() // by the time this function is called, so it is safe to run the // backend's real Initialize() here, well before the _Init() calls. Init_Render_Backend(); +#if defined(GGC_RENDER_BACKEND_BGFX) + // Pass the original game HWND to bgfx, not the DX8 reference window. + g_renderBackend->Initialize(_GameHwndForBgfx, ResolutionWidth, ResolutionHeight); +#else g_renderBackend->Initialize(_Hwnd, ResolutionWidth, ResolutionHeight); +#endif /* ** Initialize any other subsystems inside of WW3D @@ -954,6 +994,42 @@ void DX8Wrapper::Resize_And_Position_Window() DEBUG_LOG(("Window positioned to x:%d y:%d, resized to w:%d h:%d", left, top, width, height)); } } + +#if defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @feature bobtista 19/04/2026 also resize the + // main game window (used by bgfx) to match the game's resolution. Without + // this, the game window stays at its initial size and mouse coordinates + // don't match the game's UI coordinate system. + if (_GameHwndForBgfx && _GameHwndForBgfx != _Hwnd) + { + RECT gameRect = { 0 }; + ::GetClientRect(_GameHwndForBgfx, &gameRect); + if ((gameRect.right - gameRect.left) != ResolutionWidth || + (gameRect.bottom - gameRect.top) != ResolutionHeight) + { + RECT r = { 0, 0, ResolutionWidth, ResolutionHeight }; + DWORD dwstyle = ::GetWindowLong(_GameHwndForBgfx, GWL_STYLE); + AdjustWindowRect(&r, dwstyle, FALSE); + int w = r.right - r.left; + int h = r.bottom - r.top; + + MONITORINFO mi = {sizeof(MONITORINFO)}; + GetMonitorInfo(MonitorFromWindow(_GameHwndForBgfx, MONITOR_DEFAULTTOPRIMARY), &mi); + int l = (mi.rcWork.left + mi.rcWork.right - w) / 2; + int t = (mi.rcWork.top + mi.rcWork.bottom - h) / 2; + + RECT rc; + rc.left = l - r.left; + rc.top = t - r.top; + rc.right = rc.left + ResolutionWidth; + rc.bottom = rc.top + ResolutionHeight; + MoveRectIntoOtherRect(rc, mi.rcMonitor, &l, &t); + + ::SetWindowPos(_GameHwndForBgfx, nullptr, l, t, w, h, SWP_NOZORDER); + WWDEBUG_SAY(("Bgfx game window resized to %dx%d at (%d,%d)", w, h, l, t)); + } + } +#endif } bool DX8Wrapper::Set_Render_Device(int dev, int width, int height, int bits, int windowed, @@ -981,6 +1057,12 @@ bool DX8Wrapper::Set_Render_Device(int dev, int width, int height, int bits, int if (bits != -1) BitDepth = bits; if (windowed != -1) IsWindowed = (windowed != 0); +#if defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @feature bobtista 16/04/2026 D3D8 reference + // window must always use windowed mode. Fullscreen-exclusive would steal + // input focus from the main game window where bgfx renders. + IsWindowed = true; +#endif DX8Wrapper_IsWindowed = IsWindowed; WWDEBUG_SAY(("Attempting Set_Render_Device: name: %s (%s:%s), width: %d, height: %d, windowed: %d", diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index 0d37cab5933..6d06f67a2f6 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -568,8 +568,10 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, return 0; // ignore when window is not active +#if !defined(GGC_RENDER_BACKEND_BGFX) if( !isWinMainActive ) return 0; +#endif Int x = (Int)LOWORD( lParam ); Int y = (Int)HIWORD( lParam ); From d893d752c88825e6de347cfa3d451648925cd96c Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 21 Apr 2026 14:02:48 -0400 Subject: [PATCH 056/523] fix(bgfx): Bug fixes - D3D8 guards, viewport sync, shadow decals, water blend, videos, scorch masking (cherry picked from commit d95a1661606e3c8d2b19d72d550ebb7231e7e5b4) --- .../W3DDevice/GameClient/W3DShaderManager.h | 1 + .../W3DDevice/GameClient/BaseHeightMap.cpp | 22 +- .../Source/W3DDevice/GameClient/HeightMap.cpp | 23 +- .../W3DDevice/GameClient/W3DShaderManager.cpp | 118 ++-- .../W3DDevice/GameClient/W3DVideoBuffer.cpp | 11 + .../W3DDevice/GameClient/Water/W3DWater.cpp | 44 +- .../Source/WWVegas/WW3D2/BgfxBackend.cpp | 609 ++++++++++++++++-- .../Source/WWVegas/WW3D2/BgfxBackend.h | 9 + .../Source/WWVegas/WW3D2/IRenderBackend.h | 39 ++ .../Source/WWVegas/WW3D2/dx8wrapper.cpp | 26 + .../Source/WWVegas/WW3D2/render2dsentence.cpp | 8 + .../Source/WWVegas/WW3D2/shaders/fs_uber.sc | 45 +- .../WWVegas/WW3D2/shaders/varying.def.sc | 1 + .../Source/WWVegas/WW3D2/shaders/vs_trees.sc | 8 +- .../Source/WWVegas/WW3D2/shaders/vs_uber.sc | 13 +- .../Source/WWVegas/WW3D2/texture.cpp | 11 + .../GameClient/Shadow/W3DProjectedShadow.cpp | 13 +- .../W3DDevice/GameClient/W3DDisplay.cpp | 10 +- .../Source/WWVegas/WW3D2/render2d.cpp | 7 + 19 files changed, 857 insertions(+), 161 deletions(-) diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DShaderManager.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DShaderManager.h index bb86bca8057..f68a0abdc70 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DShaderManager.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DShaderManager.h @@ -79,6 +79,7 @@ class W3DShaderManager static void init(); ///= MAX_SCORCH_VERTEX) return; - curVb->diffuse = diffuse; Real theZ; theZ = amtToFloat+((float)getClipHeight(i+m_map->getBorderSizeInline(),j+m_map->getBorderSizeInline())*MAP_HEIGHT_SCALE); // The scorchmarks are spaced out by 1.5 in the texture. @@ -1984,6 +1983,27 @@ void BaseHeightMapRenderObjClass::updateScorches() curVb->x = X; curVb->y = Y; curVb->z = theZ; + + // TheSuperHackers @fix bobtista 20/04/2026 Corner cells of the scorch grid overshoot the + // atlas tile and sample the neighboring scorch; zero vertex alpha outside the unit radius + // on the shader-pipeline path so those fragments render transparent. + bool useBgfxAlphaMask = (g_renderBackend != nullptr + && g_renderBackend->Has_Shader_Pipeline()); + if (useBgfxAlphaMask) + { + Real dx = (X - loc.X) / radius; + Real dy = (Y - loc.Y) / radius; + if (dx*dx + dy*dy > 1.0f) { + curVb->diffuse = diffuse & 0x00FFFFFF; + } else { + curVb->diffuse = diffuse; + } + } + else + { + curVb->diffuse = diffuse; + } + curVb++; m_curNumScorchVertices++; } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp index 6744607a436..d5ee3bf98c6 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp @@ -1887,6 +1887,12 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) W3DShaderManager::updateCloud(); } + // TheSuperHackers @feature bobtista 20/04/2026 Push cloud state to the + // render backend each frame. bgfx samples this in fs_uber to modulate + // terrain colour; DX8 ignores it (still uses its own multi-pass TSS). + // Gated off when doCloud==false so terrain renders without modulation. + W3DShaderManager::pushCloudShadowToBackend(doCloud, doCloud ? m_stageTwoTexture : nullptr); + Matrix3D tm(Transform); #if 0 // There is some weirdness sometimes with the dx8 static buffers. // This usually fixes terrain flashing. jba. @@ -1938,18 +1944,9 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) Bool doMultiPassWireFrame=FALSE; -#if defined(GGC_RENDER_BACKEND_BGFX) - // TheSuperHackers @feature bobtista 19/04/2026 the alpha mask - // and shroud early-return paths use D3D8 pixel shaders (shroud.pso) that - // bgfx cannot interpret. Skip them entirely so terrain always renders - // through the normal W3DShaderManager path which sets proper TSS ops. - if (false) - { -#else if (((RTS3DScene *)rinfo.Camera.Get_User_Data())->getCustomPassMode() == SCENE_PASS_ALPHA_MASK || ((SceneClass *)rinfo.Camera.Get_User_Data())->Get_Extra_Pass_Polygon_Mode() == SceneClass::EXTRA_PASS_CLEAR_LINE) { -#endif if (WW3D::Is_Texturing_Enabled()) { //first pass where we just fill the z-buffer @@ -2131,17 +2128,12 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) if (TheTerrainTracksRenderObjClassSystem) TheTerrainTracksRenderObjClassSystem->flush(); -#if !defined(GGC_RENDER_BACKEND_BGFX) - // TheSuperHackers @feature bobtista 19/04/2026 shroud - // overlay pass uses D3D8 pixel shaders. Skip for bgfx — shroud - // is handled separately via the bgfx uber shader's shroud path. if (m_shroud && rinfo.Additional_Pass_Count()) { rinfo.Peek_Additional_Pass(0)->Install_Materials(); renderTerrainPass(&rinfo.Camera); rinfo.Peek_Additional_Pass(0)->UnInstall_Materials(); } -#endif ShaderClass::Invalidate(); g_renderBackend->Apply_Render_State_Changes(); @@ -2161,6 +2153,9 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) ShaderClass::Invalidate(); g_renderBackend->Set_Material(nullptr); + // Scope the cloud state to this function — clear before returning so + // subsequent 3D draws (units, trees, effects) don't get modulated. + W3DShaderManager::pushCloudShadowToBackend(false, nullptr); } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp index 6f52abda810..f327f351ad5 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp @@ -227,6 +227,7 @@ Bool ScreenDefaultFilter::postRender(FilterModes mode, Coord2D &scrollDelta,Bool Int xpos, ypos, width, height; DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,tex); //previously rendered frame inside this texture + if (g_renderBackend) g_renderBackend->Set_Texture(0, nullptr); // clear bgfx cache for raw D3D tex TheTacticalView->getOrigin(&xpos,&ypos); width=TheTacticalView->getWidth(); height=TheTacticalView->getHeight(); @@ -276,7 +277,7 @@ Int ScreenDefaultFilter::set(FilterModes mode) void ScreenDefaultFilter::reset() { - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,nullptr); //previously rendered frame inside this texture + DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,nullptr); g_renderBackend->Invalidate_Cached_Render_States(); } @@ -366,6 +367,7 @@ Bool ScreenBWFilter::postRender(FilterModes mode, Coord2D &scrollDelta,Bool &doE Int xpos, ypos, width, height; DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,tex); //previously rendered frame inside this texture + if (g_renderBackend) g_renderBackend->Set_Texture(0, nullptr); // clear bgfx cache for raw D3D tex TheTacticalView->getOrigin(&xpos,&ypos); width=TheTacticalView->getWidth(); height=TheTacticalView->getHeight(); @@ -494,7 +496,7 @@ Int ScreenBWFilter::set(FilterModes mode) void ScreenBWFilter::reset() { - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,nullptr); //previously rendered frame inside this texture + DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,nullptr); DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(0); //turn off pixel shader g_renderBackend->Invalidate_Cached_Render_States(); } @@ -603,6 +605,7 @@ Bool ScreenBWFilterDOT3::postRender(FilterModes mode, Coord2D &scrollDelta,Bool } DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,tex); //previously rendered frame inside this texture + if (g_renderBackend) g_renderBackend->Set_Texture(0, nullptr); // clear bgfx cache for raw D3D tex pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANS_LIT_TEX_VERTEX)); @@ -678,7 +681,7 @@ Int ScreenBWFilterDOT3::set(FilterModes mode) void ScreenBWFilterDOT3::reset() { - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,nullptr); //previously rendered frame inside this texture + DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,nullptr); g_renderBackend->Invalidate_Cached_Render_States(); } @@ -820,6 +823,7 @@ Bool ScreenCrossFadeFilter::postRender(FilterModes mode, Coord2D &scrollDelta,Bo Real radius = 0.0f; DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,tex); //previously rendered frame inside this texture + if (g_renderBackend) g_renderBackend->Set_Texture(0, nullptr); // clear bgfx cache for raw D3D tex if (mode == FM_VIEW_CROSSFADE_CIRCLE) { W3DShaderManager_BindStageTexture(1, m_fadePatternTexture); //Use the current fade level to scale the mask texture, for other modes the texture @@ -917,7 +921,7 @@ void ScreenCrossFadeFilter::reset() { DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_DISABLE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,nullptr); //previously rendered frame inside this texture + DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,nullptr); g_renderBackend->Invalidate_Cached_Render_States(); } @@ -987,6 +991,7 @@ Bool ScreenMotionBlurFilter::postRender(FilterModes mode, Coord2D &scrollDelta,B Int xpos, ypos, width, height; DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,tex); //previously rendered frame inside this texture + if (g_renderBackend) g_renderBackend->Set_Texture(0, nullptr); // clear bgfx cache for raw D3D tex TheTacticalView->getOrigin(&xpos,&ypos); width=TheTacticalView->getWidth(); height=TheTacticalView->getHeight(); @@ -1185,7 +1190,7 @@ Int ScreenMotionBlurFilter::set(FilterModes mode) void ScreenMotionBlurFilter::reset() { - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,nullptr); //previously rendered frame inside this texture + DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,nullptr); g_renderBackend->Invalidate_Cached_Render_States(); } @@ -1598,19 +1603,10 @@ Int TerrainShader2Stage::init() W3DShaders[W3DShaderManager::ST_TERRAIN_BASE_NOISE1]=&terrainShader2Stage; W3DShaders[W3DShaderManager::ST_TERRAIN_BASE_NOISE2]=&terrainShader2Stage; W3DShaders[W3DShaderManager::ST_TERRAIN_BASE_NOISE12]=&terrainShader2Stage; -#if defined(GGC_RENDER_BACKEND_BGFX) - // TheSuperHackers @feature bobtista 19/04/2026 bgfx does terrain blend - // in a single pass. Cloud/noise passes (2,3) are not yet supported. - W3DShadersPassCount[W3DShaderManager::ST_TERRAIN_BASE]=1; - W3DShadersPassCount[W3DShaderManager::ST_TERRAIN_BASE_NOISE1]=1; - W3DShadersPassCount[W3DShaderManager::ST_TERRAIN_BASE_NOISE2]=1; - W3DShadersPassCount[W3DShaderManager::ST_TERRAIN_BASE_NOISE12]=1; -#else W3DShadersPassCount[W3DShaderManager::ST_TERRAIN_BASE]=2; W3DShadersPassCount[W3DShaderManager::ST_TERRAIN_BASE_NOISE1]=3; W3DShadersPassCount[W3DShaderManager::ST_TERRAIN_BASE_NOISE2]=3; W3DShadersPassCount[W3DShaderManager::ST_TERRAIN_BASE_NOISE12]=3; -#endif return TRUE; } @@ -1621,8 +1617,8 @@ void TerrainShader2Stage::reset() ShaderClass::Invalidate(); //Free references to textures - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, nullptr); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(1, nullptr); + W3DShaderManager_BindStageTexture(0, nullptr); + W3DShaderManager_BindStageTexture(1, nullptr); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|0); @@ -1673,14 +1669,6 @@ Int TerrainShader2Stage::set(Int pass) s_loggedTerrainShader = true; WWDEBUG_SAY(("[W3DShaderManager] TerrainShader2Stage::set first fire pass=%d", pass)); } -#if defined(GGC_RENDER_BACKEND_BGFX) - // TheSuperHackers @feature bobtista 19/04/2026 bgfx blends base and - // per-tile textures in a single pass via the uber shader's terrain - // blend path. Pass 0 has stage 0 = base texture (from shader manager) - // and stage 1 = per-tile blend texture (from drawVisiblePolys). - g_renderBackend->Override_Terrain_Blend(true); -#endif - //force WW3D2 system to set it's states so it won't later overwrite our custom settings. g_renderBackend->Apply_Render_State_Changes(); @@ -1825,11 +1813,6 @@ Int TerrainShader2Stage::set(Int pass) Int TerrainShader8Stage::init() { -#if defined(GGC_RENDER_BACKEND_BGFX) - // TheSuperHackers @feature bobtista 19/04/2026 8-stage multi-texture - // is invisible to bgfx. Fall through to the 2Stage variant. - return FALSE; -#endif ChipsetType res; //this shader will also use the 2Stage shader for some of the passes so initialize it too. @@ -1984,8 +1967,8 @@ void TerrainShader8Stage::reset() DX8Wrapper::Set_DX8_Texture_Stage_State( 4, D3DTSS_COLOROP, D3DTOP_DISABLE); DX8Wrapper::Set_DX8_Texture_Stage_State( 4, D3DTSS_ALPHAOP, D3DTOP_DISABLE); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, nullptr); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(1, nullptr); + W3DShaderManager_BindStageTexture(0, nullptr); + W3DShaderManager_BindStageTexture(1, nullptr); g_renderBackend->Invalidate_Cached_Render_States(); } @@ -2012,11 +1995,6 @@ Int TerrainShaderPixelShader::init() Int res; #ifdef DISABLE_PIXEL_SHADERS return false; -#endif -#if defined(GGC_RENDER_BACKEND_BGFX) - // TheSuperHackers @feature bobtista 19/04/2026 D3D8 pixel shaders are - // invisible to bgfx. Fall through to the 2Stage variant. - return FALSE; #endif //this shader will also use the 2Stage shader for some of the passes so initialize it too. if (terrainShader2Stage.init() && (res=W3DShaderManager::getChipset()) >= DC_GENERIC_PIXEL_SHADER_1_1) @@ -2066,9 +2044,11 @@ Int TerrainShaderPixelShader::init() Int TerrainShaderPixelShader::set(Int pass) { - // Do not set terrain blend — the D3D8 pixel shader binds textures - // differently than what the bgfx terrain blend path expects. The - // normal TSS path handles the texture operations correctly. + // TheSuperHackers @feature bobtista 19/04/2026 Enable terrain blend for + // bgfx. This variant binds base (stage 0) and blend (stage 1) textures + // via g_renderBackend, so the bgfx uber shader can blend them correctly. + if (g_renderBackend != nullptr && g_renderBackend->Has_Shader_Pipeline()) + g_renderBackend->Override_Terrain_Blend(true); //force WW3D2 system to set it's states so it won't later overwrite our custom settings. g_renderBackend->Apply_Render_State_Changes(); @@ -2177,13 +2157,13 @@ Int TerrainShaderPixelShader::set(Int pass) void TerrainShaderPixelShader::reset() { g_renderBackend->Override_Terrain_Blend(false); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(2,nullptr); //release reference to any texture - DX8Wrapper::_Get_D3D_Device8()->SetTexture(3,nullptr); //release reference to any texture + W3DShaderManager_BindStageTexture(2, nullptr); + W3DShaderManager_BindStageTexture(3, nullptr); DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(0); //turn off pixel shader - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, nullptr); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(1, nullptr); + W3DShaderManager_BindStageTexture(0, nullptr); + W3DShaderManager_BindStageTexture(1, nullptr); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|0); @@ -2773,6 +2753,26 @@ void W3DShaderManager::updateCloud() terrainShader2Stage.updateCloud(); } +// TheSuperHackers @feature bobtista 20/04/2026 Push cloud-shadow state +// through g_renderBackend so the bgfx backend can modulate the scrolling +// cloud texture into terrain color from its uber shader (equivalent of +// the DX8 ST_TERRAIN_BASE_NOISE1 / _NOISE12 multi-pass path). DX8Backend +// ignores this — DX8 drives its own TSS cascade as before. +void W3DShaderManager::pushCloudShadowToBackend(Bool enabled, TextureClass * cloudTex) +{ + if (g_renderBackend == nullptr) + { + return; + } + const float stretch = (float)(1.0 / (63.0 * MAP_XY_FACTOR / 2.0)); + g_renderBackend->Set_Cloud_Shadow_Params( + enabled ? true : false, + terrainShader2Stage.m_xOffset, + terrainShader2Stage.m_yOffset, + stretch, + cloudTex); +} + // W3DShaderManager::getShaderPasses ======================================================= /** Return number of renderig passes required in perform the desired shader on current hardware. App will need to re-render the polygons this many times to complete the @@ -3330,17 +3330,10 @@ Int FlatTerrainShader2Stage::init() W3DShaders[W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE1]=&flatTerrainShader2Stage; W3DShaders[W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE2]=&flatTerrainShader2Stage; W3DShaders[W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE12]=&flatTerrainShader2Stage; -#if defined(GGC_RENDER_BACKEND_BGFX) - W3DShadersPassCount[W3DShaderManager::ST_FLAT_TERRAIN_BASE]=1; - W3DShadersPassCount[W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE1]=1; - W3DShadersPassCount[W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE2]=1; - W3DShadersPassCount[W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE12]=1; -#else W3DShadersPassCount[W3DShaderManager::ST_FLAT_TERRAIN_BASE]=1; W3DShadersPassCount[W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE1]=2; W3DShadersPassCount[W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE2]=2; W3DShadersPassCount[W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE12]=2; -#endif return TRUE; } @@ -3351,8 +3344,8 @@ void FlatTerrainShader2Stage::reset() ShaderClass::Invalidate(); //Free references to textures - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, nullptr); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(1, nullptr); + W3DShaderManager_BindStageTexture(0, nullptr); + W3DShaderManager_BindStageTexture(1, nullptr); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|0); @@ -3366,9 +3359,8 @@ Int FlatTerrainShader2Stage::set(Int pass) { static bool s_loggedFlat2 = false; if (!s_loggedFlat2) { s_loggedFlat2 = true; WWDEBUG_SAY(("[W3DShaderManager] FlatTerrainShader2Stage::set first fire pass=%d", pass)); } -#if defined(GGC_RENDER_BACKEND_BGFX) - g_renderBackend->Override_Terrain_Blend(true); -#endif + if (g_renderBackend != nullptr && g_renderBackend->Has_Shader_Pipeline()) + g_renderBackend->Override_Terrain_Blend(true); //force WW3D2 system to set it's states so it won't later overwrite our custom settings. g_renderBackend->Apply_Render_State_Changes(); @@ -3586,14 +3578,6 @@ Int FlatTerrainShaderPixelShader::init() return false; #endif -#if defined(GGC_RENDER_BACKEND_BGFX) - // TheSuperHackers @feature bobtista 19/04/2026 D3D8 pixel shaders are - // invisible to the bgfx backend. Skip pixel shader terrain variants so - // the 2Stage fallback is used — it sets proper TSS ops that the bgfx - // uber shader can interpret. - return FALSE; -#endif - //this shader will also use the 2Stage shader for some of the passes so initialize it too. if ((res=W3DShaderManager::getChipset()) >= DC_GENERIC_PIXEL_SHADER_1_1) { @@ -3803,13 +3787,13 @@ Int FlatTerrainShaderPixelShader::set(Int pass) void FlatTerrainShaderPixelShader::reset() { g_renderBackend->Override_Terrain_Blend(false); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(2,nullptr); //release reference to any texture - DX8Wrapper::_Get_D3D_Device8()->SetTexture(3,nullptr); //release reference to any texture + W3DShaderManager_BindStageTexture(2, nullptr); + W3DShaderManager_BindStageTexture(3, nullptr); DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(0); //turn off pixel shader - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, nullptr); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(1, nullptr); + W3DShaderManager_BindStageTexture(0, nullptr); + W3DShaderManager_BindStageTexture(1, nullptr); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|0); diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DVideoBuffer.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DVideoBuffer.cpp index 977cf899c96..0a829f4d53d 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DVideoBuffer.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DVideoBuffer.cpp @@ -48,6 +48,7 @@ #include "Common/GameMemory.h" #include "WW3D2/texture.h" #include "WW3D2/textureloader.h" +#include "WW3D2/RenderBackend.h" #include "W3DDevice/GameClient/W3DVideoBuffer.h" //---------------------------------------------------------------------------- @@ -194,6 +195,16 @@ void W3DVideoBuffer::unlock() m_surface->Unlock(); m_surface->Release_Ref(); m_surface = nullptr; + + // TheSuperHackers @fix bobtista 20/04/2026 After a video frame is + // written via CPU Lock/Unlock into the underlying D3D8 surface, + // invalidate bgfx's cached copy so the next draw re-uploads the + // fresh pixels. Without this the bgfx handle holds the first frame + // forever and videos (logo, scorescreen, etc.) appear frozen. + if ( g_renderBackend != nullptr && m_texture != nullptr ) + { + g_renderBackend->Invalidate_Cached_Texture(m_texture); + } } } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp index 453e561cfee..c8b3d81892c 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp @@ -71,6 +71,18 @@ +// TheSuperHackers @refactor bobtista 19/04/2026 Helper to bind +// a texture to both D3D8 and g_renderBackend so bgfx's texture cache stays +// in sync. For raw D3D8 textures without a TextureClass*, pass nullptr for +// tex to clear the bgfx cache (prevents stale texture artifacts). +static inline void W3DWater_BindTexture(unsigned stage, TextureClass * tex) +{ + IDirect3DTexture8 * raw = (tex != nullptr) ? tex->Peek_D3D_Texture() : nullptr; + DX8Wrapper::_Get_D3D_Device8()->SetTexture(stage, raw); + if (g_renderBackend != nullptr) + g_renderBackend->Set_Texture(stage, tex); +} + #define MIPMAP_BUMP_TEXTURE // DEFINES //////////////////////////////////////////////////////////////////////////////////////// @@ -229,7 +241,7 @@ void WaterRenderObjClass::setupJbaWaterShader() DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_ADD ); if (!m_riverAlphaEdge->Is_Initialized()) m_riverAlphaEdge->Init(); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(3,m_riverAlphaEdge->Peek_D3D_Texture()); + W3DWater_BindTexture(3, m_riverAlphaEdge); DX8Wrapper::Set_DX8_Texture_Stage_State(3, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); DX8Wrapper::Set_DX8_Texture_Stage_State(3, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_TEXCOORDINDEX, 0); @@ -241,11 +253,11 @@ void WaterRenderObjClass::setupJbaWaterShader() if (m_riverWaterPixelShader && doSparkles) { if (!m_waterSparklesTexture->Is_Initialized()) m_waterSparklesTexture->Init(); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(1,m_waterSparklesTexture->Peek_D3D_Texture()); + W3DWater_BindTexture(1, m_waterSparklesTexture); if (!m_waterNoiseTexture->Is_Initialized()) m_waterNoiseTexture->Init(); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(2,m_waterNoiseTexture->Peek_D3D_Texture()); + W3DWater_BindTexture(2, m_waterNoiseTexture); DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); @@ -1863,6 +1875,7 @@ void WaterRenderObjClass::drawSea(RenderInfoClass & rinfo) m_pDev->SetTextureStageState(1, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); m_pDev->SetTexture( 0, m_pBumpTexture[(Int)m_fBumpFrame]); + if (g_renderBackend) g_renderBackend->Set_Texture(0, nullptr); // raw D3D texture, clear bgfx cache #ifdef MIPMAP_BUMP_TEXTURE m_pDev->SetTextureStageState( 0, D3DTSS_MIPFILTER, D3DTEXF_POINT ); m_pDev->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); @@ -1905,7 +1918,7 @@ void WaterRenderObjClass::drawSea(RenderInfoClass & rinfo) m_pDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); m_pDev->SetRenderState(D3DRS_ALPHABLENDENABLE , TRUE); - m_pDev->SetTexture( 1, m_pReflectionTexture->Peek_D3D_Texture()); + W3DWater_BindTexture(1, m_pReflectionTexture); // m_pDev->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);//LORENZEN @@ -1942,9 +1955,9 @@ void WaterRenderObjClass::drawSea(RenderInfoClass & rinfo) } // m_pDev->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID); m_pDev->SetRenderState(D3DRS_ALPHABLENDENABLE , FALSE); - m_pDev->SetTexture( 0, nullptr); //release reference to bump texture - m_pDev->SetTexture( 1, nullptr); //release reference to reflection texture - m_pDev->SetTexture( 2, nullptr); //release reference to reflection texture + W3DWater_BindTexture(0, nullptr); //release reference to bump texture + W3DWater_BindTexture(1, nullptr); //release reference to reflection texture + W3DWater_BindTexture(2, nullptr); //release reference to reflection texture m_pDev->SetTextureStageState( 0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); m_pDev->SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|0); @@ -2940,8 +2953,17 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) if (m_riverWaterPixelShader) DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(0); //restore blend mode to what W3D expects. + // TheSuperHackers @fix bobtista 20/04/2026 The flat water path below + // resets blend factors for both additive and non-additive modes, but + // this JBA path only reset for additive. On bgfx the DESTALPHA blend + // that Override_Material_Opacity() sets then leaked into subsequent + // draws (e.g. the small faction-emblem quad on the command-center + // bib), producing a black rectangle there. Match the flat water path + // so non-additive JBA water restores SRC_ALPHA/INV_SRC_ALPHA. if (TheWaterTransparency->m_additiveBlend) g_renderBackend->Set_Blend_Factors(RB_BLEND_ONE, RB_BLEND_ONE); + else + g_renderBackend->Set_Blend_Factors(RB_BLEND_SRC_ALPHA, RB_BLEND_INV_SRC_ALPHA); DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_CULLMODE, cull); @@ -2989,7 +3011,7 @@ void WaterRenderObjClass::setupFlatWaterShader() surface->Unlock(); REF_PTR_RELEASE(surface); } - DX8Wrapper::_Get_D3D_Device8()->SetTexture(3,m_whiteTexture->Peek_D3D_Texture()); + W3DWater_BindTexture(3, m_whiteTexture); } } @@ -3004,12 +3026,12 @@ void WaterRenderObjClass::setupFlatWaterShader() if (!m_waterSparklesTexture->Is_Initialized()) m_waterSparklesTexture->Init(); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(1,m_waterSparklesTexture->Peek_D3D_Texture()); + W3DWater_BindTexture(1, m_waterSparklesTexture); if (!m_waterNoiseTexture->Is_Initialized()) m_waterNoiseTexture->Init(); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(2,m_waterNoiseTexture->Peek_D3D_Texture()); + W3DWater_BindTexture(2, m_waterNoiseTexture); DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); @@ -3339,7 +3361,7 @@ void WaterRenderObjClass::drawTrapezoidWater(Vector3 points[4]) if (m_trapezoidWaterPixelShader) { //shroud was applied in stage3 of main pass so just need to restore state here. W3DShaderManager::resetShader(W3DShaderManager::ST_SHROUD_TEXTURE); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(3,nullptr); //free possible reference to shroud texture + W3DWater_BindTexture(3, nullptr); //free possible reference to shroud texture DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_ZFUNC, D3DCMP_EQUAL); } else diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp index b95baa8cd7c..727d5fd713c 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -156,6 +156,16 @@ int g_bgfxHeight = 600; // succeeds, destroyed in Shutdown before bgfx::shutdown. bgfx::ProgramHandle g_passthroughProgram = BGFX_INVALID_HANDLE; +// TheSuperHackers @fix bobtista 20/04/2026 Static vertex buffer used to +// force a fullscreen black draw on view 0 each frame. bgfx's setViewClear +// does not emit ClearRenderTargetView for our backbuffer view when the +// view is only activated via bgfx::touch — confirmed via RenderDoc export +// (zero ClearRenderTargetView calls per frame). A real submit on view 0 +// makes bgfx process the view fully, clearing and covering the backbuffer +// so persisted pixels from prior frames don't leak (e.g., flickering +// yellow strips under the control bar). +bgfx::VertexBufferHandle g_fullscreenClearVB = BGFX_INVALID_HANDLE; + // TheSuperHackers @refactor bobtista 12/04/2026 uber shader // program. Single program handles all TSS combinations via uniforms. bgfx::ProgramHandle g_uberProgram = BGFX_INVALID_HANDLE; @@ -320,6 +330,19 @@ bgfx::UniformHandle g_sTex3 = BGFX_INVALID_HANDLE; // alpha fades modulate the output. bgfx::UniformHandle g_uMatDiffuse = BGFX_INVALID_HANDLE; float g_currentMatDiffuse[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; +// TheSuperHackers @feature bobtista 20/04/2026 Material emissive uniform. +// D3D8 fixed-function adds the material's emissive color to the lit +// output (D3DRS_EMISSIVEMATERIALSOURCE, MATERIAL.Emissive). Self- +// illuminating meshes like the "move here" hint rely on this to produce +// their glow color even when no light reaches them. fs_uber needs this +// term or such meshes render black. +bgfx::UniformHandle g_uMatEmissive = BGFX_INVALID_HANDLE; +float g_currentMatEmissive[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; +// Grayscale output override for disabled 2D UI elements (Render2DClass::Enable_Grayscale). +// D3D8 path uses D3DTOP_DOTPRODUCT3 + TFACTOR=0x80A5CA8E (luminance weights); bgfx path +// applies the dot-product at the end of fs_uber when g_currentGrayscaleEnable[0] > 0.5. +bgfx::UniformHandle g_uGrayscaleEnable = BGFX_INVALID_HANDLE; +float g_currentGrayscaleEnable[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; // Alpha-test parameters consumed by fs_textured_lit_atest. .x is the // reference threshold in [0, 1]; engine writes ShaderClass alpha-ref / 255. // Named u_atestParams (not u_alphaRef) to avoid bgfx_shader.sh's internal @@ -359,6 +382,16 @@ float g_currentTexcoordSelect[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; // transform matrix. bgfx has no equivalent, so we upload the full texture // matrix and let the vertex shader compute the UVs explicitly. bgfx::UniformHandle g_uShroudParams = BGFX_INVALID_HANDLE; + +// TheSuperHackers @feature bobtista 20/04/2026 Cloud-shadow modulation +// for terrain (DX8 ST_TERRAIN_BASE_NOISE1 / _NOISE12). Scroll offset +// advances each frame from TerrainShader2Stage::m_xOffset/m_yOffset; +// the fragment shader multiplies the sampled cloud color into the base. +// .xy = scroll offset, .z = world→UV stretch, .w > 0.5 = enable. +bgfx::UniformHandle g_uCloudParams = BGFX_INVALID_HANDLE; +bgfx::UniformHandle g_sCloudMap = BGFX_INVALID_HANDLE; +float g_currentCloudParams[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; +bgfx::TextureHandle g_currentCloudTex = BGFX_INVALID_HANDLE; // .y = terrain blend flag: when > 0, shader does lerp(tex0, tex1, vertex_alpha) // using UV set 0 for tex0 and UV set 1 for tex1 @@ -970,6 +1003,20 @@ std::unordered_map g_vbCache; std::unordered_map g_ibCache; std::unordered_map g_textureCache; +// TheSuperHackers @fix bobtista 19/04/2026 Track D3D8 texture pointers +// and dimensions alongside TextureClass* to detect stale cache entries +// (address reuse) and enable in-place updates for same-sized textures. +struct D3DPtrInfo { IDirect3DBaseTexture8 * ptr; uint16_t w; uint16_t h; }; +std::unordered_map g_d3dPtrCache; + +// TheSuperHackers @fix bobtista 19/04/2026 Deferred texture destruction. +// When a stale texture cache entry is detected (D3D8 pointer changed), the +// old bgfx handle can't be destroyed immediately because in-flight draws +// may still reference it. Double-buffer: collect in current frame, destroy +// after the NEXT bgfx::frame() (2 frames later = guaranteed safe). +static std::vector g_deferredTextureDestroys; // current frame +static std::vector g_deferredTextureDestroysPrev; // previous frame (safe to destroy) + // The bgfx texture currently bound to stage 0 by Set_Texture. Used by // SubmitEngineDraw - falls back to g_defaultWhiteTexture if invalid. bgfx::TextureHandle g_currentBgfxTexture0 = BGFX_INVALID_HANDLE; @@ -1220,10 +1267,115 @@ bgfx::TextureHandle EnsureBgfxTexture(TextureBaseClass * tex) { return BGFX_INVALID_HANDLE; } + IDirect3DBaseTexture8 * curD3D = tex->Peek_D3D_Base_Texture(); + auto it = g_textureCache.find(tex); if (it != g_textureCache.end()) { - return it->second; + auto d3dIt = g_d3dPtrCache.find(tex); + bool d3dPtrMatch = (d3dIt != g_d3dPtrCache.end() && d3dIt->second.ptr == curD3D); + if (d3dPtrMatch) + { + return it->second; + } + // D3D8 texture pointer changed — the TextureClass* address was reused + // for a different texture, OR the engine invalidated via + // Invalidate_Cached_Texture after writing new pixels (e.g. video + // frame, font atlas). Try to UPDATE the existing bgfx handle in + // place (no handle churn) if dimensions and format match. + // Only fall back to destroy+recreate if they differ. + // TheSuperHackers @bugfix bobtista 20/04/2026 Capture the stored + // dimensions BEFORE overwriting the cache entry — the in-place + // update path below checks against them, and the iterator aliases + // the same entry we are about to stomp. + uint16_t cachedW = 0; + uint16_t cachedH = 0; + if (d3dIt != g_d3dPtrCache.end()) + { + cachedW = d3dIt->second.w; + cachedH = d3dIt->second.h; + } + g_d3dPtrCache[tex] = { curD3D, 0, 0 }; + if (bgfx::isValid(it->second)) + { + TextureClass * tex2d = tex->As_TextureClass(); + if (tex2d != nullptr && tex->Get_Pool() != TextureBaseClass::POOL_DEFAULT) + { + IDirect3DTexture8 * d3dTex = static_cast(curD3D); + D3DSURFACE_DESC desc; + if (SUCCEEDED(d3dTex->GetLevelDesc(0, &desc))) + { + const bgfx::TextureFormat::Enum bgfxFmt = TranslateWW3DFormat(tex2d->Get_Texture_Format()); + // Check if dimensions match the existing bgfx handle + if (desc.Width == cachedW + && desc.Height == cachedH + && bgfxFmt != bgfx::TextureFormat::Unknown) + { + D3DLOCKED_RECT locked = { 0 }; + if (SUCCEEDED(d3dTex->LockRect(0, &locked, NULL, D3DLOCK_READONLY))) + { + const bool isCompressed = (bgfxFmt >= bgfx::TextureFormat::BC1 && bgfxFmt <= bgfx::TextureFormat::BC7); + const unsigned bpp = isCompressed ? 0 : + (bgfxFmt == bgfx::TextureFormat::BGRA4 || bgfxFmt == bgfx::TextureFormat::R5G6B5 || bgfxFmt == bgfx::TextureFormat::BGR5A1) ? 2 : + (bgfxFmt == bgfx::TextureFormat::A8 || bgfxFmt == bgfx::TextureFormat::R8) ? 1 : 4; + const unsigned expectedPitch = isCompressed ? 0 : desc.Width * bpp; + const unsigned numRows = isCompressed ? ((desc.Height + 3) / 4) : desc.Height; + const unsigned srcPitch = static_cast(locked.Pitch); + const unsigned rowBytes = (expectedPitch > 0) ? expectedPitch : srcPitch; + const unsigned totalBytes = numRows * rowBytes; + const bgfx::Memory * mem = bgfx::alloc(totalBytes); + if (srcPitch == expectedPitch || isCompressed) + std::memcpy(mem->data, locked.pBits, totalBytes); + else + for (unsigned row = 0; row < numRows; ++row) + std::memcpy(mem->data + row * rowBytes, + static_cast(locked.pBits) + row * srcPitch, rowBytes); + d3dTex->UnlockRect(0); + + // TheSuperHackers @bugfix bobtista 20/04/2026 + // D3D8 samples the alpha channel of X8R8G8B8 + // textures as 1.0 regardless of memory content + // (the "X" component is ignored). bgfx's BGRA8 + // format has no such convention and samples the + // raw memory byte, which for FFmpeg video + // frames (AV_PIX_FMT_BGR0) is 0 — making + // SRC_ALPHA blended draws invisible. Force + // alpha to 0xFF ONLY for procedurally-created + // X8R8G8B8 textures (no file path — e.g. video + // buffers). TGA-loaded X8R8G8B8 textures may + // have real alpha data the game relies on + // (scorch marks, decals), so leave those alone. + if (tex2d->Get_Texture_Format() == WW3D_FORMAT_X8R8G8B8 + && bgfxFmt == bgfx::TextureFormat::BGRA8 + && tex2d->Get_Full_Path().Is_Empty()) + { + uint8_t * px = mem->data; + const unsigned pixelCount = (expectedPitch / 4) * numRows; + for (unsigned i = 0; i < pixelCount; ++i) + px[i * 4 + 3] = 0xff; + } + + bgfx::updateTexture2D(it->second, 0, 0, 0, 0, + static_cast(desc.Width), + static_cast(desc.Height), mem); + // Store dimensions so subsequent invalidations + // can still match the in-place update path. + g_d3dPtrCache[tex] = { curD3D, + static_cast(desc.Width), + static_cast(desc.Height) }; + return it->second; // Reused handle, no churn + } + } + } + } + // Dimensions or format changed — must destroy and recreate + g_deferredTextureDestroys.push_back(it->second); + } + g_textureCache.erase(it); + } + else + { + g_d3dPtrCache[tex] = { curD3D, 0, 0 }; } // Only handle TextureClass (regular 2D) for now. Cube and volume @@ -1251,16 +1403,6 @@ bgfx::TextureHandle EnsureBgfxTexture(TextureBaseClass * tex) return fbIt->second.colorTex; } - static int s_poolDefaultCount = 0; - if (s_poolDefaultCount < 10) - { - ++s_poolDefaultCount; - WWDEBUG_SAY(("[BgfxBackend] POOL_DEFAULT texture skipped #%d: %s %dx%d fmt=%d", - s_poolDefaultCount, - tex2d->Get_Full_Path().str(), - tex2d->Get_Width(), tex2d->Get_Height(), - static_cast(tex2d->Get_Texture_Format()))); - } g_renderTargetSet[tex] = true; return BGFX_INVALID_HANDLE; } @@ -1366,34 +1508,127 @@ bgfx::TextureHandle EnsureBgfxTexture(TextureBaseClass * tex) } d3dTex->UnlockRect(0); - { - static int s_texLog = 0; - if (s_texLog++ < 200) + // TheSuperHackers @fix bobtista 20/04/2026 Team-colored textures + // (name prefixed with "##" via W3DAssetManager's Munge_Texture_Name) + // go through Remap_Palette which forces alpha to 0xff on every pixel. + // The original TGA stored its "transparent" regions as pure-black RGB + // with full alpha, relying on a D3D8-era color-key-on-RGB mechanism + // (or implicit alpha-test on luminance) that we haven't reproduced in + // bgfx. Without it, the black background paints over the stone bib + // around sub-faction emblems (the long-standing "black rectangle" + // bug). Emulate color-keying at upload time: for #-prefixed BGRA8 + // textures, scan the pixel data and set alpha=0 on any pixel whose + // RGB is all zero. Scorch marks / shadows use different names so are + // unaffected. + if (!isCompressed && bgfxFmt == bgfx::TextureFormat::BGRA8) + { + TextureClass * t = tex->As_TextureClass(); + const char * texName = t ? t->Get_Full_Path().str() : nullptr; + if (texName != nullptr && texName[0] == '#') { - TextureClass * t = tex->As_TextureClass(); - WWDEBUG_SAY(("[TEX CREATE #%d] '%s' %ux%u d3dfmt=%d bgfxFmt=%d " - "srcPitch=%u expPitch=%u rows=%u bytes=%u comp=%d", - s_texLog, - t ? (const char*)t->Get_Full_Path() : "?", - desc.Width, desc.Height, - static_cast(desc.Format), - static_cast(bgfxFmt), - srcPitch, expectedPitch, numRows, totalBytes, - isCompressed ? 1 : 0)); + uint8_t * pix = mem->data; + const unsigned pixelCount = expectedPitch / 4 * numRows; + for (unsigned i = 0; i < pixelCount; ++i) + { + // BGRA8: byte order B, G, R, A. Transparent if B=G=R=0. + if (pix[i * 4 + 0] == 0 && pix[i * 4 + 1] == 0 && pix[i * 4 + 2] == 0) + { + pix[i * 4 + 3] = 0; + } + } } } + // TheSuperHackers @bugfix bobtista 20/04/2026 See matching in-place + // path above. D3D8's X8R8G8B8 ignores the "X" byte and samples alpha + // as 1.0; bgfx BGRA8 samples it literally. Video buffers (FFmpeg + // BGR0) are written with alpha=0 and would draw as transparent. + // Force 0xFF for procedurally-created X8R8G8B8 textures only (empty + // path); TGA-loaded X8R8G8B8 textures may carry real alpha data the + // game relies on (scorch marks, decals) so leave those untouched. + if (!isCompressed + && bgfxFmt == bgfx::TextureFormat::BGRA8 + && tex2d->Get_Texture_Format() == WW3D_FORMAT_X8R8G8B8 + && tex2d->Get_Full_Path().Is_Empty()) + { + uint8_t * px = mem->data; + const unsigned pixelCount = (expectedPitch / 4) * numRows; + for (unsigned i = 0; i < pixelCount; ++i) + px[i * 4 + 3] = 0xff; + } + + // TheSuperHackers @fix bobtista 19/04/2026 Create textures WITHOUT + // initial data so they are mutable. Passing data to createTexture2D + // makes the texture immutable, silently rejecting later updates (font + // atlas glyph additions, dynamic texture changes). Create empty, then + // upload via updateTexture2D. bgfx::TextureHandle h = bgfx::createTexture2D( static_cast(desc.Width), static_cast(desc.Height), false, 1, bgfxFmt, BGFX_TEXTURE_NONE | BGFX_SAMPLER_MIN_ANISOTROPIC | BGFX_SAMPLER_MAG_ANISOTROPIC, - mem); + nullptr); + if (bgfx::isValid(h) && mem != nullptr) + { + bgfx::updateTexture2D(h, 0, 0, 0, 0, + static_cast(desc.Width), + static_cast(desc.Height), + mem); + } g_textureCache[tex] = h; + // Record dimensions so future reuse can update in place + g_d3dPtrCache[tex] = { curD3D, static_cast(desc.Width), static_cast(desc.Height) }; return h; } +} // close anonymous namespace for Invalidate_Cached_Texture + +void BgfxBackend::Invalidate_Cached_Texture(TextureBaseClass * texture) +{ + if (texture == nullptr) + return; + // Set the D3D pointer to nullptr (sentinel) so the next EnsureBgfxTexture + // call detects a "change" and re-uploads pixel data. KEEP the dimensions + // so the in-place update path can check if the bgfx handle is reusable. + auto d3dIt = g_d3dPtrCache.find(texture); + if (d3dIt != g_d3dPtrCache.end()) + d3dIt->second.ptr = nullptr; +} + +void BgfxBackend::Release_Cached_Texture(TextureBaseClass * texture) +{ + if (texture == nullptr) + return; + // Called from TextureBaseClass::~TextureBaseClass before the D3D8 + // texture is released. Queue the bgfx handle for deferred destruction + // (in-flight draws may still reference it this frame) and erase the + // cache entries so a later allocation reusing this TextureBaseClass* + // address cannot inherit the stale handle. + auto it = g_textureCache.find(texture); + if (it != g_textureCache.end()) + { + if (bgfx::isValid(it->second)) + g_deferredTextureDestroys.push_back(it->second); + g_textureCache.erase(it); + } + g_d3dPtrCache.erase(texture); + g_renderTargetSet.erase(texture); + + // Framebuffer-backed textures (render targets) own a framebuffer whose + // color attachment IS the cached handle above. Destroying the + // framebuffer also destroys its attached textures, so we do that + // immediately and do NOT queue the colorTex for deferred destroy. + auto fbIt = g_framebufferCache.find(texture); + if (fbIt != g_framebufferCache.end()) + { + if (bgfx::isValid(fbIt->second.fb)) + bgfx::destroy(fbIt->second.fb); + g_framebufferCache.erase(fbIt); + } +} + +namespace { // reopen anonymous namespace bgfx::ProgramHandle g_currentBgfxProgram = BGFX_INVALID_HANDLE; uint64_t g_currentBgfxState = 0; @@ -1607,9 +1842,10 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) // triangle (bring-up sentinel). bgfx::setViewClear(kBgfxDebugView, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, - 0x1a3b5cff, // dark teal, 0xRRGGBBAA + 0x000000ff, // black 1.0f, 0); + bgfx::setViewFrameBuffer(kBgfxDebugView, BGFX_INVALID_HANDLE); bgfx::setViewRect(kBgfxDebugView, 0, 0, static_cast(g_bgfxWidth), static_cast(g_bgfxHeight)); @@ -1754,11 +1990,25 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) WWDEBUG_SAY(("[BgfxBackend] passthrough shader createShader FAILED.")); } + // Fullscreen-clear VB. Single triangle in NDC that covers the entire + // clip-space rectangle; submitted to view 0 every frame (Begin_Scene). + { + struct ClearVert { float x, y, z; uint32_t rgba; }; + static const ClearVert verts[3] = { + { -1.0f, -3.0f, 0.0f, 0xff000000u }, + { -1.0f, 1.0f, 0.0f, 0xff000000u }, + { 3.0f, 1.0f, 0.0f, 0xff000000u }, + }; + g_fullscreenClearVB = bgfx::createVertexBuffer( + bgfx::makeRef(verts, sizeof(verts)), g_triangleLayout); + } + g_sTex0 = bgfx::createUniform("s_tex0", bgfx::UniformType::Sampler); g_sTex1 = bgfx::createUniform("s_tex1", bgfx::UniformType::Sampler); g_sTex2 = bgfx::createUniform("s_tex2", bgfx::UniformType::Sampler); g_sTex3 = bgfx::createUniform("s_tex3", bgfx::UniformType::Sampler); g_uMatDiffuse = bgfx::createUniform("u_matDiffuse", bgfx::UniformType::Vec4); + g_uMatEmissive = bgfx::createUniform("u_matEmissive", bgfx::UniformType::Vec4); g_uAtestParams = bgfx::createUniform("u_atestParams", bgfx::UniformType::Vec4); g_uTssOps0 = bgfx::createUniform("u_tssOps0", bgfx::UniformType::Vec4); g_uTssOps1 = bgfx::createUniform("u_tssOps1", bgfx::UniformType::Vec4); @@ -1767,7 +2017,10 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) g_uSceneAmbient = bgfx::createUniform("u_sceneAmbient", bgfx::UniformType::Vec4); g_uLightingEnabled = bgfx::createUniform("u_lightingEnabled", bgfx::UniformType::Vec4); g_uTexcoordSelect = bgfx::createUniform("u_texcoordSelect", bgfx::UniformType::Vec4); + g_uGrayscaleEnable = bgfx::createUniform("u_grayscaleEnable", bgfx::UniformType::Vec4); g_uShroudParams = bgfx::createUniform("u_shroudParams", bgfx::UniformType::Vec4); + g_uCloudParams = bgfx::createUniform("u_cloudParams", bgfx::UniformType::Vec4); + g_sCloudMap = bgfx::createUniform("s_cloudMap", bgfx::UniformType::Sampler); // Default 1x1 white texture. Used as fallback for missing textures. // Multiplying by white is the identity operation. @@ -1914,8 +2167,8 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) // stale shadow map. Explicit order: shadow map first, then the // rest of the views in their natural ascending order. const bgfx::ViewId order[] = { + kBgfxDebugView, // 0 — full-canvas clear quad, MUST run first kBgfxShadowMapView, // 8 — shadow caster depth pass - kBgfxDebugView, // test/passthrough kBgfxRTTView, // 3 kBgfxEngineView, // 1 kBgfxShadowVolumeView, // 6 — stencil shadow volume fill @@ -1984,6 +2237,7 @@ void BgfxBackend::Shutdown() if (g_bgfxInitialized) { DestroyBgfxHandle(g_passthroughProgram); + DestroyBgfxHandle(g_fullscreenClearVB); DestroyBgfxHandle(g_uberProgram); DestroyBgfxHandle(g_treeProgram); DestroyBgfxHandle(g_uSwayTable); @@ -2003,6 +2257,8 @@ void BgfxBackend::Shutdown() DestroyBgfxHandle(g_uLightingEnabled); DestroyBgfxHandle(g_uTexcoordSelect); DestroyBgfxHandle(g_uShroudParams); + DestroyBgfxHandle(g_uCloudParams); + DestroyBgfxHandle(g_sCloudMap); DestroyBgfxHandle(g_shadowVolumeProgram); DestroyBgfxHandle(g_shadowApplyProgram); DestroyBgfxHandle(g_shadowCasterProgram); @@ -2073,6 +2329,58 @@ void BgfxBackend::Shutdown() // Device state queries (Is_Device_Lost / Has_Stencil / Get_Back_Buffer_Format // / Get_Back_Buffer / Set_Gamma) are inherited from DX8Backend. +// -- Viewport ---------------------------------------------------------------- + +void BgfxBackend::Set_Viewport(const RenderBackendViewport & viewport) +{ + // Do NOT call DX8Backend::Set_Viewport here — this method is called + // FROM DX8Wrapper::Set_Viewport, so the D3D8 viewport is already set. + // Calling the base class would cause infinite recursion. + + if (!g_bgfxInitialized) + return; + + // TheSuperHackers @fix bobtista 19/04/2026 Sync bgfx view rects with the + // game's viewport. Without this, bgfx uses the full window for the 3D + // scene while the game's picking/camera uses a smaller viewport (excluding + // the control bar), causing a vertical click offset when selecting units. + const uint16_t x = static_cast(viewport.x); + const uint16_t y = static_cast(viewport.y); + const uint16_t w = static_cast(viewport.width); + const uint16_t h = static_cast(viewport.height); + + // TheSuperHackers @fix bobtista 20/04/2026 DX8Wrapper::Set_Viewport + // is called from TWO very different contexts each frame: + // 1. CameraClass::Apply() with the tactical 3D viewport + // (e.g., 1280x640 when the control bar is visible) + // 2. Render2DClass::Render() with the full-canvas viewport + // (1280x800) for 2D UI drawing + // The 2D UI has its own bgfx view (kBgfxUIView) so its rect is + // independent. If we let the Render2DClass call stomp the 3D engine + // views with the full-canvas rect, the 3D scene renders stretched + // while the picking code still normalizes mouse Y through the + // 640-tall tactical view — producing a vertical click offset that + // scales with Y position. Ignore updates whose dimensions match the + // full bgfx canvas: the 3D engine views should keep the smaller + // tactical rect set by CameraClass::Apply. + const bool isFullCanvas = + (x == 0 && y == 0 && + static_cast(w) == g_bgfxWidth && + static_cast(h) == g_bgfxHeight); + if (isFullCanvas) + { + return; + } + + bgfx::setViewRect(kBgfxEngineView, x, y, w, h); + bgfx::setViewRect(kBgfxEngineSortView, x, y, w, h); + bgfx::setViewRect(kBgfxWaterView, x, y, w, h); + bgfx::setViewRect(kBgfxEffectOverlayView, x, y, w, h); + bgfx::setViewRect(kBgfxShadowVolumeView, x, y, w, h); + bgfx::setViewRect(kBgfxShadowApplyView, x, y, w, h); + bgfx::setViewRect(kBgfxRTTView, x, y, w, h); +} + // -- Frame lifecycle --------------------------------------------------------- void BgfxBackend::Begin_Scene() @@ -2081,6 +2389,16 @@ void BgfxBackend::Begin_Scene() { return; } + + // Destroy PREVIOUS frame's deferred textures. These were queued in + // frame N-1, survived through bgfx::frame() at End_Scene of frame N-1, + // so all in-flight draws referencing them are guaranteed complete. + for (auto & h : g_deferredTextureDestroysPrev) + { + if (bgfx::isValid(h)) + bgfx::destroy(h); + } + g_deferredTextureDestroysPrev.clear(); // Show the DX8 reference popup after a few frames, giving the game's // input system time to fully initialize. Showing too early steals focus // and permanently blocks mouse capture. @@ -2130,7 +2448,28 @@ void BgfxBackend::Begin_Scene() } } - bgfx::touch(kBgfxDebugView); + // TheSuperHackers @fix bobtista 20/04/2026 Force a real draw on view 0 + // each frame so bgfx actually processes the view and covers the + // backbuffer. Without this, touch-only activation leaves the backbuffer + // with stale content from prior frames — visible as flickering yellow + // strips from the chat button animation leaking under the control bar. + if (bgfx::isValid(g_passthroughProgram) && bgfx::isValid(g_fullscreenClearVB)) + { + float identity[16]; + IdentityMatrix(identity); + bgfx::setViewRect(kBgfxDebugView, 0, 0, + static_cast(g_bgfxWidth), + static_cast(g_bgfxHeight)); + bgfx::setViewTransform(kBgfxDebugView, identity, identity); + bgfx::setVertexBuffer(0, g_fullscreenClearVB); + bgfx::setState(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A + | BGFX_STATE_DEPTH_TEST_ALWAYS); + bgfx::submit(kBgfxDebugView, g_passthroughProgram); + } + else + { + bgfx::touch(kBgfxDebugView); + } bgfx::touch(kBgfxEngineView); bgfx::touch(kBgfxEngineSortView); bgfx::touch(kBgfxWaterView); @@ -2189,11 +2528,18 @@ void BgfxBackend::End_Scene(bool /*flip_frame*/) static_cast(g_bgfxWidth), static_cast(g_bgfxHeight)); - // The shadow map (view 8) renders FIRST so the depth - // texture is populated before the scene samples it. Then RTT (3), - // engine opaque (1), shadow volume fill (6), shadow darken (7), - // water (4), sort (2), effect overlay (5), UI overlay (10) last. + // The debug view (0) runs FIRST to emit the backbuffer + // clear quad, then shadow map (view 8) so its depth texture is + // populated before the scene samples it. Then RTT (3), engine + // opaque (1), shadow volume fill (6), shadow darken (7), water (4), + // sort (2), effect overlay (5), UI overlay (10) last. + // TheSuperHackers @bugfix bobtista 20/04/2026 view 0 MUST be + // included — when omitted, bgfx defers it to the end with a 1x1 + // viewport, and the full-canvas clear never fires (causing + // flickering UI leftovers under the control bar on frames where + // that area is not overdrawn). bgfx::ViewId viewOrder[] = { + kBgfxDebugView, // 0 — full-canvas clear quad, must run first kBgfxShadowMapView, // 8 — shadow caster depth pass kBgfxRTTView, // 3 kBgfxEngineView, // 1 @@ -2208,6 +2554,13 @@ void BgfxBackend::End_Scene(bool /*flip_frame*/) bgfx::frame(); + // Rotate deferred texture destroy buffers. Current frame's deferred + // handles move to "prev" — they'll be destroyed at the NEXT Begin_Scene + // after one more bgfx::frame() guarantees all references are gone. + g_deferredTextureDestroysPrev.insert(g_deferredTextureDestroysPrev.end(), + g_deferredTextureDestroys.begin(), g_deferredTextureDestroys.end()); + g_deferredTextureDestroys.clear(); + // Transient buffers are freed at bgfx::frame time. Invalidate the // pending and current slots so nothing next frame tries to reuse // a dead handle. @@ -2835,6 +3188,8 @@ static void UploadMaterialUniforms() if (bgfx::isValid(g_uMatDiffuse)) { bgfx::setUniform(g_uMatDiffuse, g_currentMatDiffuse); + if (bgfx::isValid(g_uMatEmissive)) + bgfx::setUniform(g_uMatEmissive, g_currentMatEmissive); } if (bgfx::isValid(g_uTssOps0)) @@ -2850,6 +3205,19 @@ static void UploadMaterialUniforms() float atestParams[4] = { g_currentAtestRef, 0.0f, 0.0f, 0.0f }; bgfx::setUniform(g_uAtestParams, atestParams); } + if (bgfx::isValid(g_uGrayscaleEnable)) + { + bgfx::setUniform(g_uGrayscaleEnable, g_currentGrayscaleEnable); + } + if (bgfx::isValid(g_uCloudParams)) + { + bgfx::setUniform(g_uCloudParams, g_currentCloudParams); + } + if (bgfx::isValid(g_sCloudMap) && bgfx::isValid(g_currentCloudTex)) + { + // WRAP addressing matches the DX8 cloud pass at W3DShaderManager.cpp:1742. + bgfx::setTexture(5, g_sCloudMap, g_currentCloudTex, BGFX_SAMPLER_NONE); + } } // TheSuperHackers @refactor bobtista 11/04/2026 sorted VB @@ -2959,6 +3327,19 @@ void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, } bgfx::setState(state); + + // TheSuperHackers @fix bobtista 19/04/2026 fs_uber declares + // SAMPLER2DSHADOW(s_shadowMap, 4); bgfx resets texture bindings per + // submit, so slot 4 must be rebound for EVERY submit that uses the + // uber program — not just the opaque path at SubmitEngineDraw. D3D11 + // debug layer raises DEVICE_DRAW_SAMPLER_MISMATCH (#390, 0x87A) when + // a draw reaches the shader with slot 4 empty, because the default + // sampler is not comparison-capable. + if (bgfx::isValid(g_shadowMapDepth) && bgfx::isValid(g_sShadowMap)) + { + bgfx::setTexture(4, g_sShadowMap, g_shadowMapDepth); + } + bgfx::submit(kBgfxEngineSortView, g_currentBgfxProgram); // CSM: also submit sorted geometry as shadow caster. @@ -3080,8 +3461,23 @@ void BgfxBackend::Set_Material(const VertexMaterialClass * material) // washed-out white or tinted by whatever the previous draw used. if (material != nullptr) { + // TheSuperHackers @bugfix bobtista 20/04/2026 Honor + // VertexMaterialClass's Diffuse_Color_Source. When set to COLOR1 + // (D3DMCS_COLOR1), D3D's FF pipeline uses the VERTEX diffuse as + // the material's effective diffuse and ignores material->Get_Diffuse. + // This is the PRELIT_DIFFUSE preset used by projected shadow decals, + // HUD/2D overlays, etc. Without this guard, fs_uber's + // `current *= u_matDiffuse` darkens the decals with whatever stale + // or default color the material carries (often black), producing + // the "black scorch-like patches" on the beach where alpha decals + // are batched. Force matDiffuse=1 so vertex color passes through. Vector3 diffuse(1.0f, 1.0f, 1.0f); - material->Get_Diffuse(&diffuse); + const VertexMaterialClass::ColorSourceType diffuseSource = + const_cast(material)->Get_Diffuse_Color_Source(); + if (diffuseSource == VertexMaterialClass::MATERIAL) + { + material->Get_Diffuse(&diffuse); + } g_currentMatDiffuse[0] = diffuse.X; g_currentMatDiffuse[1] = diffuse.Y; g_currentMatDiffuse[2] = diffuse.Z; @@ -3090,6 +3486,17 @@ void BgfxBackend::Set_Material(const VertexMaterialClass * material) // (terrain, pre-lit meshes), vertex colors already contain the // lit result and the shader must NOT apply N.L on top. g_currentLightingEnabled[0] = material->Get_Lighting() ? 1.0f : 0.0f; + // Emissive color — self-illumination. Self-glow meshes (e.g. + // SCMoveHint.w3d "move here" indicator) set the material emissive + // to their glow color and rely on D3D fixed-function adding it + // on top of the lit diffuse. Without this, they render black + // because the lit math produces 0 and there's no emissive term. + Vector3 emissive(0.0f, 0.0f, 0.0f); + material->Get_Emissive(&emissive); + g_currentMatEmissive[0] = emissive.X; + g_currentMatEmissive[1] = emissive.Y; + g_currentMatEmissive[2] = emissive.Z; + g_currentMatEmissive[3] = 0.0f; } else { @@ -3097,6 +3504,10 @@ void BgfxBackend::Set_Material(const VertexMaterialClass * material) g_currentMatDiffuse[1] = 1.0f; g_currentMatDiffuse[2] = 1.0f; g_currentMatDiffuse[3] = 1.0f; + g_currentMatEmissive[0] = 0.0f; + g_currentMatEmissive[1] = 0.0f; + g_currentMatEmissive[2] = 0.0f; + g_currentMatEmissive[3] = 0.0f; // Null material means no material-driven lighting. Dazzle and // similar effect overlays call Set_Material(nullptr) and bake // their per-vertex intensity into diffuse.rgb; the lit path in @@ -3128,24 +3539,23 @@ void BgfxBackend::Set_Texture(unsigned int stage, TextureBaseClass * texture) g_renderTargetSet.count(texture) > 0) { h = g_defaultWhiteTexture; - static int s_loggedRTFallback = 0; - if (s_loggedRTFallback < 5) + static bool s_loggedRTFallback = false; + if (!s_loggedRTFallback) { - ++s_loggedRTFallback; + s_loggedRTFallback = true; TextureClass * t2d_fb = texture->As_TextureClass(); WWDEBUG_SAY(("[BgfxBackend] RT FALLBACK: stage=%u using white fallback for %s", stage, t2d_fb ? t2d_fb->Get_Full_Path().str() : "(null)")); } } - // Log when an invalid texture goes to white fallback if (!bgfx::isValid(h) && texture != nullptr && g_renderTargetSet.count(texture) == 0) { - static int s_loggedWhiteFallback = 0; - if (s_loggedWhiteFallback < 5) + static bool s_loggedWhiteFallback = false; + if (!s_loggedWhiteFallback) { - ++s_loggedWhiteFallback; + s_loggedWhiteFallback = true; TextureClass * t2d_fb = texture->As_TextureClass(); WWDEBUG_SAY(("[BgfxBackend] WHITE FALLBACK: stage=%u tex=%s pool=%d", stage, @@ -3154,6 +3564,7 @@ void BgfxBackend::Set_Texture(unsigned int stage, TextureBaseClass * texture) } } TextureClass * t2d_name = texture ? texture->As_TextureClass() : nullptr; + // Capture the source texture's wrap mode into bgfx sampler flags // so we can pass it at bind time. Without this, WRAP is the // default and ramp/LUT textures with CLAMP semantics produce @@ -3188,6 +3599,41 @@ void BgfxBackend::Set_Ambient(const Vector3 & color) g_currentSceneAmbient[2] = color.Z; } +// TheSuperHackers @fix bobtista 20/04/2026 Set_Blend_Factors goes through +// the g_renderBackend abstraction so callers (water code in particular) +// can change the blend mode between draws. The DX8Backend default only +// updates D3D render state, leaving bgfx's own g_blendOverrideBits stale. +// Water rendering relies on this to restore SRC_ALPHA / INV_SRC_ALPHA +// blending after its DESTALPHA shoreline pass. Without this override the +// DESTALPHA state set by Override_Material_Opacity() persists into the +// next draw (e.g. the small faction-emblem quad on the command-center +// bib), producing a black rectangle where the emblem background should +// be transparent. Translate the BlendFactor enum values to bgfx bits and +// update the blend override so subsequent bgfx submits see the change. +void BgfxBackend::Set_Blend_Factors(BlendFactor src, BlendFactor dest) +{ + DX8Backend::Set_Blend_Factors(src, dest); + + static const uint64_t kBlendMap[9] = { + 0, + BGFX_STATE_BLEND_ZERO, // 1 = RB_BLEND_ZERO + BGFX_STATE_BLEND_ONE, // 2 = RB_BLEND_ONE + BGFX_STATE_BLEND_SRC_COLOR, // 3 + BGFX_STATE_BLEND_INV_SRC_COLOR, // 4 + BGFX_STATE_BLEND_SRC_ALPHA, // 5 = RB_BLEND_SRC_ALPHA + BGFX_STATE_BLEND_INV_SRC_ALPHA, // 6 = RB_BLEND_INV_SRC_ALPHA + BGFX_STATE_BLEND_DST_ALPHA, // 7 = RB_BLEND_DEST_ALPHA + BGFX_STATE_BLEND_INV_DST_ALPHA // 8 = RB_BLEND_INV_DEST_ALPHA + }; + const unsigned s = static_cast(src); + const unsigned d = static_cast(dest); + if (s >= 1 && s <= 8 && d >= 1 && d <= 8) + { + g_blendOverrideActive = true; + g_blendOverrideBits = BGFX_STATE_BLEND_FUNC(kBlendMap[s], kBlendMap[d]); + } +} + void BgfxBackend::Override_Blend(unsigned srcBlend, unsigned dstBlend) { static const uint64_t kBlendMap[9] = { @@ -3256,11 +3702,17 @@ void BgfxBackend::Override_Terrain_Blend(bool enable) void BgfxBackend::Override_Material_Opacity(float opacity) { + // TheSuperHackers @fix bobtista 20/04/2026 Only override opacity. + // Previously this also forced DESTALPHA blend as a bgfx-side stand-in + // for the D3D8 shoreline-alpha water feather. That stale DESTALPHA + + // matDiffuse.a=0.5 state leaked into the next draw (a small quad on + // the command-center bib, captured in RenderDoc as a water-textured + // draw producing pure black). The water code already sets DESTALPHA + // explicitly via Set_Blend_Factors when soft water edge is enabled, + // and BgfxBackend::Set_Blend_Factors now propagates that to bgfx. So + // this override only needs to handle the opacity uniform. g_currentMatDiffuse[3] = opacity; g_waterOverrideActive = true; - g_blendOverrideActive = true; - g_blendOverrideBits = BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_DST_ALPHA, - BGFX_STATE_BLEND_INV_DST_ALPHA); } void BgfxBackend::Begin_Effect_Overlay() @@ -3287,6 +3739,29 @@ void BgfxBackend::Set_Tree_Vertex_Shader_Active(bool active) g_treeShaderActive = active; } +void BgfxBackend::Set_Grayscale_Mode(bool enable) +{ + g_currentGrayscaleEnable[0] = enable ? 1.0f : 0.0f; +} + +void BgfxBackend::Set_Cloud_Shadow_Params(bool enable, float scroll_x, float scroll_y, + float stretch, TextureClass * cloud_tex) +{ + g_currentCloudParams[0] = scroll_x; + g_currentCloudParams[1] = scroll_y; + g_currentCloudParams[2] = stretch; + g_currentCloudParams[3] = enable ? 1.0f : 0.0f; + + if (enable && cloud_tex != nullptr) + { + g_currentCloudTex = EnsureBgfxTexture(cloud_tex); + } + else + { + g_currentCloudTex = BGFX_INVALID_HANDLE; + } +} + void BgfxBackend::Set_Color_Write_Enable(bool red, bool green, bool blue, bool alpha) { DX8Backend::Set_Color_Write_Enable(red, green, blue, alpha); @@ -3336,12 +3811,6 @@ void BgfxBackend::Set_Shadow_Light_Position(float x, float y, float z) g_shadowSunPosZ = z; g_shadowSunPosSet = true; g_shadowLightCaptured = false; - static int s_slpLog = 0; - if (s_slpLog++ < 5) - { - WWDEBUG_SAY(("[CSM] Set_Shadow_Light_Position(%.1f, %.1f, %.1f)", - x, y, z)); - } } void BgfxBackend::Set_Shadow_Volume_Shader_Active(bool active) @@ -3907,6 +4376,7 @@ void SubmitEngineDraw(unsigned short start_index, { submitView = kBgfxEngineView; } + const float * worldMtx = g_inSortFlush ? g_bgfxSortWorld : g_bgfxWorld; // Push the engine view+projection when they change. setViewTransform @@ -4021,6 +4491,34 @@ void SubmitEngineDraw(unsigned short start_index, // neighbor mip selection, breaking mipmap smoothing and making // distant terrain patches look blocky. Let bgfx pick the best // filter it can for each texture. + + // TheSuperHackers @fix bobtista 19/04/2026 During sort flush, the + // sorting renderer's Apply_Render_State calls DX8Wrapper::Set_Texture + // which has a cache check — if the texture pointer matches the cached + // one, it skips BgfxBackend::Set_Texture entirely. This leaves + // g_currentBgfxTexture0-3 stale (e.g., pointing at font glyphs instead + // of particle textures). Force-sync bgfx handles from DX8Wrapper's + // current state before each sorted draw. + if (g_inSortFlush) + { + RenderStateStruct sortRS; + DX8Wrapper::Get_Render_State(sortRS); + + for (int si = 0; si < 4; ++si) + { + bgfx::TextureHandle h = EnsureBgfxTexture( + static_cast(sortRS.Textures[si])); + if (!bgfx::isValid(h)) + h = g_defaultWhiteTexture; + switch (si) + { + case 0: g_currentBgfxTexture0 = h; break; + case 1: g_currentBgfxTexture1 = h; break; + case 2: g_currentBgfxTexture2 = h; break; + case 3: g_currentBgfxTexture3 = h; break; + } + } + } BindTextureStages(); UploadMaterialUniforms(); // Read current D3D light state per-draw. Set_Light_Environment and @@ -4213,9 +4711,7 @@ void SubmitEngineDraw(unsigned short start_index, float lightVP[16]; bx::mtxMul(lightVP, g_shadowLightView, g_shadowLightProj); - if (bgfx::isValid(g_uShadowLightViewProj) - && bgfx::isValid(g_shadowMapDepth) - && bgfx::isValid(g_sShadowMap)) + if (bgfx::isValid(g_uShadowLightViewProj)) { // Compose model * lightView * lightProj per-draw. // Use the CORRECT model matrix: g_bgfxSortWorldRaw for sorted @@ -4224,6 +4720,15 @@ void SubmitEngineDraw(unsigned short start_index, const float * modelMtx = g_inSortFlush ? g_bgfxSortWorldRaw : worldMtx; bx::mtxMul(shadowMVP, modelMtx, lightVP); bgfx::setUniform(g_uShadowLightViewProj, shadowMVP); + } + + // TheSuperHackers @fix bobtista 19/04/2026 bind s_shadowMap + // independently of the matrix uniform. fs_uber declares the + // comparison sampler unconditionally, so slot 4 must be bound for + // every submit of the uber program regardless of whether the + // lightViewProj uniform is present. + if (bgfx::isValid(g_shadowMapDepth) && bgfx::isValid(g_sShadowMap)) + { bgfx::setTexture(4, g_sShadowMap, g_shadowMapDepth); } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h index 470b2b5e1b4..16ebf9ed7c6 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h @@ -42,6 +42,10 @@ class BgfxBackend : public DX8Backend BgfxBackend(); virtual ~BgfxBackend(); + virtual bool Has_Shader_Pipeline() const override { return true; } + virtual void Invalidate_Cached_Texture(TextureBaseClass * texture) override; + virtual void Release_Cached_Texture(TextureBaseClass * texture) override; + // -- Backend lifecycle ---------------------------------------------------- // // Initialize creates the bgfx popup window and calls bgfx::init. @@ -57,6 +61,7 @@ class BgfxBackend : public DX8Backend virtual void Begin_Scene() override; virtual void End_Scene(bool flip_frame) override; + virtual void Set_Viewport(const RenderBackendViewport & viewport) override; // -- Vertex / index buffers ----------------------------------------------- // @@ -117,6 +122,7 @@ class BgfxBackend : public DX8Backend virtual void Set_Texture(unsigned int stage, TextureBaseClass * texture) override; virtual void Set_Light_Environment(LightEnvironmentClass * light_env) override; virtual void Set_Ambient(const Vector3 & color) override; + virtual void Set_Blend_Factors(BlendFactor src, BlendFactor dest) override; virtual void Override_Blend(unsigned srcBlend, unsigned dstBlend) override; virtual void Override_Alpha_Test(bool enable, unsigned ref, unsigned func) override; virtual void Override_Alpha_Blend_Enable(bool enable) override; @@ -131,6 +137,9 @@ class BgfxBackend : public DX8Backend const float shroudOffset[4], const float shroudScale[4]) override; virtual void Set_Tree_Vertex_Shader_Active(bool active) override; + virtual void Set_Grayscale_Mode(bool enable) override; + virtual void Set_Cloud_Shadow_Params(bool enable, float scroll_x, float scroll_y, + float stretch, TextureClass * cloud_tex) override; virtual void Set_Color_Write_Enable(bool red, bool green, bool blue, bool alpha) override; virtual void Set_Color_Write_Mask(unsigned mask) override; virtual void Skip_Next_Bgfx_Submit() override; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h index e3cd0f23905..1b0440df4d5 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h @@ -44,6 +44,8 @@ class LightEnvironmentClass; class Matrix4x4; class Matrix3D; class Vector3; +struct _D3DLIGHT8; +typedef struct _D3DLIGHT8 D3DLIGHT8; // ----------------------------------------------------------------------------- // POD types owned by the interface @@ -179,6 +181,25 @@ class IRenderBackend public: virtual ~IRenderBackend() {} + // TheSuperHackers @feature bobtista 19/04/2026 Runtime check for whether + // the backend uses its own shader pipeline (bgfx). When true, certain + // D3D8-specific rendering paths (pixel shaders, shroud passes) should be + // skipped or replaced with backend-compatible alternatives. + virtual bool Has_Shader_Pipeline() const { return false; } + + // TheSuperHackers @feature bobtista 19/04/2026 Invalidate a cached + // texture so the backend re-reads its data on next use. Called after + // _Copy_DX8_Rects updates a texture's GPU data (font atlas rebuilds). + virtual void Invalidate_Cached_Texture(TextureBaseClass * /*texture*/) {} + + // TheSuperHackers @feature bobtista 20/04/2026 Release a cached + // texture. Called from TextureBaseClass::~TextureBaseClass before the + // D3D8 texture is released, so bgfx's cache never holds a dangling + // TextureBaseClass* that a later allocation could alias (ABA). The + // backend must queue the handle for deferred destruction (in-flight + // draws may still reference it) and erase its cache entries. + virtual void Release_Cached_Texture(TextureBaseClass * /*texture*/) {} + // ------------------------------------------------------------------------- // Backend lifecycle // ------------------------------------------------------------------------- @@ -398,6 +419,24 @@ class IRenderBackend const float shroudScale[4]) {} virtual void Set_Tree_Vertex_Shader_Active(bool active) {} + // TheSuperHackers @feature bobtista 20/04/2026 grayscale + // output for disabled 2D UI elements (Render2DClass::Enable_Grayscale). + // DX8 backend is a no-op — render2d.cpp still programs the D3D8 + // DOTPRODUCT3/MODULATE TSS cascade directly for the legacy path. + // bgfx backend drives a luminance-conversion uniform in fs_uber. + virtual void Set_Grayscale_Mode(bool enable) {} + + // TheSuperHackers @feature bobtista 20/04/2026 Cloud-shadow + // modulation state. Engine calls this per frame to hand over the + // scrolling cloud offset (in world-XY units that match the + // TerrainShader2Stage::m_xOffset/m_yOffset domain) + the stretch + // factor (= 1 / (63 * MAP_XY_FACTOR / 2) on DX8). DX8 backend + // default no-op: DX8 still drives its own TSS-based cloud pass. + // bgfx backend wires these into u_cloudParams/s_cloudMap which + // fs_uber multiplies into the final terrain color. + virtual void Set_Cloud_Shadow_Params(bool enable, float scroll_x, float scroll_y, + float stretch, TextureClass * cloud_tex) {} + // ------------------------------------------------------------------------- // Transforms // ------------------------------------------------------------------------- diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp index 89f180d8b39..dc6f916722d 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp @@ -377,6 +377,17 @@ void DX8Wrapper::Shutdown() } +#if defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @fix bobtista 20/04/2026 Destroy the DX8 reference window and its WNDCLASS created in Init so repeated Init/Shutdown cycles don't leak. + if (_Hwnd != nullptr && _Hwnd != _GameHwndForBgfx) + { + ::DestroyWindow(_Hwnd); + } + _Hwnd = nullptr; + ::UnregisterClassW(L"GGC_DX8RefWindow", GetModuleHandleW(nullptr)); + _GameHwndForBgfx = nullptr; +#endif + if (CurrentCaps) { int max=CurrentCaps->Get_Max_Textures_Per_Pass(); @@ -1956,6 +1967,21 @@ void DX8Wrapper::Set_Viewport(CONST D3DVIEWPORT8* pViewport) { DX8_THREAD_ASSERT(); DX8CALL(SetViewport(pViewport)); + + // TheSuperHackers @fix bobtista 19/04/2026 Notify g_renderBackend so bgfx + // view rects stay in sync with the D3D8 viewport. CameraClass::Apply() + // calls this directly, bypassing g_renderBackend->Set_Viewport(). + if (g_renderBackend != nullptr && pViewport != nullptr) + { + RenderBackendViewport rbvp; + rbvp.x = pViewport->X; + rbvp.y = pViewport->Y; + rbvp.width = pViewport->Width; + rbvp.height = pViewport->Height; + rbvp.min_z = pViewport->MinZ; + rbvp.max_z = pViewport->MaxZ; + g_renderBackend->Set_Viewport(rbvp); + } } // ---------------------------------------------------------------------------- diff --git a/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp b/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp index 5fe9bf9a01a..2b26d01f44e 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp @@ -40,6 +40,8 @@ #include "WWDebug/wwprofile.h" #include "WWDebug/wwmemlog.h" #include "dx8wrapper.h" +#include "IRenderBackend.h" +#include "RenderBackend.h" //////////////////////////////////////////////////////////////////////////////////// @@ -373,6 +375,12 @@ Render2DSentenceClass::Build_Textures () DX8Wrapper::_Copy_DX8_Rects (curr_surface->Peek_D3D_Surface (), nullptr, 0, texture_surface->Peek_D3D_Surface (), nullptr); REF_PTR_RELEASE (texture_surface); + // TheSuperHackers @fix bobtista 19/04/2026 Invalidate the bgfx texture + // cache after CopyRects updates the font atlas. Without this, bgfx's + // cached copy has stale glyph data from the previous sentence build. + if (g_renderBackend != nullptr) + g_renderBackend->Invalidate_Cached_Texture(new_texture); + // // Assign this texture to any renderers that need it // diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc index ee18b475b86..f2e8d2b2b45 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc @@ -1,4 +1,4 @@ -$input v_color0, v_texcoord0, v_texcoord1, v_normal, v_lightspace +$input v_color0, v_texcoord0, v_texcoord1, v_normal, v_lightspace, v_cloudUV #include @@ -8,7 +8,10 @@ SAMPLER2D(s_tex2, 2); SAMPLER2D(s_tex3, 3); // CSM: D16 shadow map with hardware PCF comparison. SAMPLER2DSHADOW(s_shadowMap, 4); +// Terrain cloud-shadow scroll texture (BASE_NOISE1/NOISE12 paths on DX8). +SAMPLER2D(s_cloudMap, 5); uniform vec4 u_matDiffuse; +uniform vec4 u_matEmissive; // material emissive / self-illumination color uniform vec4 u_atestParams; uniform vec4 u_tssOps0; // (priColorOp, priAlphaOp, secColorOp, secAlphaOp) uniform vec4 u_tssOps1; // (priColorArg1Src, priAlphaArg1Src, secColorArg1Src, secAlphaArg1Src) @@ -17,6 +20,8 @@ uniform vec4 u_lightColors[4]; // per-light diffuse color (rgb) uniform vec4 u_sceneAmbient; // scene ambient color (rgb) uniform vec4 u_lightingEnabled; // .x > 0.5 = apply N.L lighting; else vertex is pre-lit uniform vec4 u_texcoordSelect; // .x > 0.5 = use v_texcoord1 for stage 0 sampling +uniform vec4 u_grayscaleEnable; // .x > 0.5 = convert final color to luminance (disabled button state) +uniform vec4 u_cloudParams; // xy = scroll, z = stretch, w > 0.5 = modulate cloud into output // TSS operation IDs (must match BgfxBackend.cpp encoding) #define TSS_DISABLE 0.0 @@ -199,14 +204,16 @@ void main() } // --- Lighting --- - if (priColorOp > 2.5 && priColorOp < 5.5 && u_lightingEnabled.x > 0.5) + // The lit path covers MODULATE/ADD and SELECTARG2 (priColorOp=2, texturing disabled). + // priColorOp=1 (SELECTARG1) is excluded: it outputs tex0 directly as a baked-lit texture. + bool needsLit = (priColorOp > 2.5 && priColorOp < 5.5) + || (priColorOp > 1.5 && priColorOp < 2.5); + if (needsLit && u_lightingEnabled.x > 0.5) { // Material has lighting enabled. D3D's T&L pipeline REPLACES the // vertex color with the computed lit result. Our vertex shader // passes through the raw vertex attribute which is meaningless - // for lit meshes. Undo the TSS modulate with vertex color by - // dividing it out, then apply proper lighting. - // Simpler approach: recompute current as tex-only * lighting. + // for lit meshes. Recompute current as tex-only * lighting. vec4 texOnly = tex0; if (secColorOp > 0.5) { @@ -234,6 +241,12 @@ void main() current *= u_matDiffuse; } + // Self-illumination / emissive. D3D fixed-function adds the material + // emissive on top of the lit output, which is how self-glowing meshes + // (like SCMoveHint.w3d "move here" indicator) get their color when + // no light reaches them. + current.rgb += u_matEmissive.rgb; + // --- Alpha test --- if (u_atestParams.x > 0.0 && current.a < u_atestParams.x) { @@ -269,5 +282,27 @@ void main() current.rgb *= shadowFactor; } + // Cloud-shadow modulation. DX8 ST_TERRAIN_BASE_NOISE1 / _NOISE12 + // renders a second pass with the cloud texture sampled in camera-space + // (via D3DTS_TEXTURE0 = inv(view) * scale + translate(scroll)) and + // multiplies it into the base color. We pre-compute the UV in the + // vertex shader (world-space XY * stretch + scroll offset) and + // modulate here. Enabled per-draw by the backend — grass/tree draws + // leave u_cloudParams.w = 0. + if (u_cloudParams.w > 0.5) + { + vec3 cloudSample = texture2D(s_cloudMap, v_cloudUV).rgb; + current.rgb *= cloudSample; + } + + // Grayscale output for disabled button state. Matches the D3D8 path + // (render2d.cpp) which used D3DTOP_DOTPRODUCT3 with TFACTOR=0x80A5CA8E + // to dot-product RGB with luminance weights (0.299, 0.587, 0.114). + if (u_grayscaleEnable.x > 0.5) + { + float luma = dot(current.rgb, vec3(0.299, 0.587, 0.114)); + current.rgb = vec3(luma, luma, luma); + } + gl_FragColor = current; } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc index cd209d5254f..971649ddacc 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc @@ -3,6 +3,7 @@ vec2 v_texcoord0 : TEXCOORD0 = vec2(0.0, 0.0); vec2 v_texcoord1 : TEXCOORD1 = vec2(0.0, 0.0); vec3 v_normal : NORMAL = vec3(0.0, 0.0, 1.0); vec4 v_lightspace: TEXCOORD2 = vec4(0.0, 0.0, 0.0, 1.0); +vec2 v_cloudUV : TEXCOORD3 = vec2(0.0, 0.0); vec3 a_position : POSITION; vec3 a_normal : NORMAL; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_trees.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_trees.sc index 206453e10b9..756758777d1 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_trees.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_trees.sc @@ -1,5 +1,5 @@ $input a_position, a_normal, a_color0, a_texcoord0 -$output v_color0, v_texcoord0, v_texcoord1, v_normal, v_lightspace +$output v_color0, v_texcoord0, v_texcoord1, v_normal, v_lightspace, v_cloudUV #include @@ -51,5 +51,11 @@ void main() v_normal = vec3(0.0, 0.0, 1.0); // grass billboards always face up + // Cloud UV — grass doesn't get cloud shadows on DX8 (it's a terrain- + // only effect) but fs_uber is shared, so we need to write something. + // u_cloudParams.w controls the enable flag and is gated per-draw by + // the backend; this value is effectively unused for grass draws. + v_cloudUV = a_position.xy; + v_lightspace = mul(u_shadowLightViewProj, vec4(a_position, 1.0)); } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_uber.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_uber.sc index 467663e8e0b..c50e04d5939 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_uber.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_uber.sc @@ -1,5 +1,5 @@ $input a_position, a_normal, a_color0, a_texcoord0, a_texcoord1 -$output v_color0, v_texcoord0, v_texcoord1, v_normal, v_lightspace +$output v_color0, v_texcoord0, v_texcoord1, v_normal, v_lightspace, v_cloudUV #include @@ -10,6 +10,7 @@ $output v_color0, v_texcoord0, v_texcoord1, v_normal, v_lightspace uniform mat4 u_shadowLightViewProj; uniform vec4 u_texcoordSelect; uniform vec4 u_shroudParams; // xy = offset, zw = scale +uniform vec4 u_cloudParams; // xy = scroll offset, z = stretch factor, w = enable flag void main() { @@ -20,16 +21,24 @@ void main() v_texcoord1 = a_texcoord1; v_normal = mul(u_model[0], vec4(a_normal, 0.0)).xyz; + vec4 worldPos = mul(u_model[0], vec4(a_position, 1.0)); + // Shroud pass: compute UV from world position using offset+scale. // The D3D8 path uses TCI_CAMERASPACEPOSITION with a texture matrix // inv(view)*offset*scale, but view cancels with camera-space input. // The net result is UV = (worldPos.xy + offset) * scale. if (u_texcoordSelect.z > 0.5) { - vec4 worldPos = mul(u_model[0], vec4(a_position, 1.0)); v_texcoord0 = (worldPos.xy + u_shroudParams.xy) * u_shroudParams.zw; } + // Cloud shadow UV: terrain-scrolling cloud texture applied over the + // base color. Matches the DX8 2-stage path in W3DShaderManager where + // D3DTS_TEXTURE0 is inv(view) * scale + translate(xOffset, yOffset) + // with TCI_CAMERASPACEPOSITION — equivalent to world-space XY times + // STRETCH_FACTOR plus an animating translation. + v_cloudUV = worldPos.xy * u_cloudParams.z + u_cloudParams.xy; + // Light-space position using the pre-composed matrix // that already includes the correct model-to-world transform. v_lightspace = mul(u_shadowLightViewProj, vec4(a_position, 1.0)); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/texture.cpp b/Core/Libraries/Source/WWVegas/WW3D2/texture.cpp index 35993b849bf..2a8e52c616a 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/texture.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/texture.cpp @@ -57,6 +57,7 @@ #include "WW3D2/meshmatdesc.h" #include "texturethumbnail.h" #include "WWDebug/wwprofile.h" +#include "WW3D2/RenderBackend.h" const unsigned DEFAULT_INACTIVATION_TIME=20000; @@ -119,6 +120,16 @@ TextureBaseClass::~TextureBaseClass() delete ThumbnailLoadTask; ThumbnailLoadTask=nullptr; + // TheSuperHackers @fix bobtista 20/04/2026 Notify the render backend + // before the D3D8 texture goes away. bgfx caches a handle keyed on + // this TextureBaseClass* address; if the memory is reallocated for + // a different texture later, the old handle would be served for the + // new object (ABA). Releasing the cache entry here closes that window. + if (g_renderBackend != nullptr) + { + g_renderBackend->Release_Cached_Texture(this); + } + if (D3DTexture) { D3DTexture->Release(); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp index 6cdd75827ea..67da43d6e6e 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp @@ -605,9 +605,16 @@ void W3DProjectedShadowManager::flushDecals(W3DShadowTexture *texture, ShadowTyp shadowTex ? (const char*)shadowTex->Get_Full_Path() : "null", (int)type)); } - // TheSuperHackers @refactor bobtista 16/04/2026 skip - // bgfx submit for projected shadow decals since CSM handles shadows. - g_renderBackend->Skip_Next_Bgfx_Submit(); + // TheSuperHackers @fix bobtista 19/04/2026 Skip bgfx submit only + // for SHADOW_DECAL (modulate-blend unit shadow blob) — CSM + // replaces that. SHADOW_ALPHA_DECAL and SHADOW_ADDITIVE_DECAL are + // UI decals (faction icons, satellite circle, move indicators, + // radius cursors) — their UVs are baked per-vertex in queueDecal, + // so they can submit directly to bgfx as regular textured tris. + if (type == SHADOW_DECAL) + { + g_renderBackend->Skip_Next_Bgfx_Submit(); + } g_renderBackend->Draw_Triangles(nShadowDecalStartBatchIndex, nShadowDecalPolysInBatch, 0, nShadowDecalVertsInBatch); } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 17a14c10a83..33aa48c9577 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -921,11 +921,11 @@ void W3DDisplay::init() // TheSuperHackers @fix bobtista 16/04/2026 D3D8 half-pixel UV bias // causes sub-pixel misalignment on D3D11/bgfx, producing visible // gaps in menu button outlines. Only apply on legacy D3D8. -#if defined(GGC_RENDER_BACKEND_BGFX) - WW3D::Set_Screen_UV_Bias( FALSE ); -#else - WW3D::Set_Screen_UV_Bias( TRUE ); ///< this makes text look good :) -#endif + // Runtime check keeps the =dx8 path byte-identical to main and + // avoids compile-time backend branching per project convention. + const bool shaderPipeline = + (g_renderBackend != nullptr && g_renderBackend->Has_Shader_Pipeline()); + WW3D::Set_Screen_UV_Bias( shaderPipeline ? FALSE : TRUE ); ///< TRUE makes text look good on D3D8 WW3D::Set_Texture_Bitdepth(32); setWindowed( TheGlobalData->m_windowed ); diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/render2d.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/render2d.cpp index 217b1d392f0..113a07f356a 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/render2d.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/render2d.cpp @@ -666,6 +666,10 @@ void Render2DClass::Render() { //special case added to draw grayscale non-alpha blended images. g_renderBackend->Set_Shader(ShaderClass::_PresetOpaqueShader); g_renderBackend->Apply_Render_State_Changes(); //force update of all regular W3D states. + // TheSuperHackers @feature bobtista 20/04/2026 bgfx grayscale path — DX8 TSS + // ops below are ignored by the bgfx backend, so drive luminance conversion + // via a shader uniform instead. + g_renderBackend->Set_Grayscale_Mode(true); if (DX8Wrapper::Get_Current_Caps()->Support_Dot3()) { //Override W3D states with customizations for grayscale DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR, 0x80A5CA8E); @@ -696,7 +700,10 @@ void Render2DClass::Render() g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW,view); g_renderBackend->Set_Transform(RB_TRANSFORM_PROJECTION,proj); if (IsGrayScale) + { ShaderClass::Invalidate(); //force both stages to be reset. + g_renderBackend->Set_Grayscale_Mode(false); + } } From f88ce3e510dff9f28cf81f98b46e5e86de56eddd Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 21 Apr 2026 14:02:48 -0400 Subject: [PATCH 057/523] refactor(bgfx): Quality pass - helper extraction, struct grouping, named constants, interface hygiene, shutdown leak fixes (cherry picked from commit 4f4dfcf7f4e914573a1bf3fe7c637eff64633c86) --- .../W3DDevice/Common/System/W3DRadar.cpp | 12 +- .../W3DDevice/GameClient/W3DShaderManager.cpp | 2 +- .../Source/WWVegas/WW3D2/BgfxBackend.cpp | 2257 ++++++----------- .../Source/WWVegas/WW3D2/BgfxBackend.h | 6 +- .../Source/WWVegas/WW3D2/BgfxBackendState.h | 307 +++ .../WWVegas/WW3D2/BgfxBackendTextures.cpp | 539 ++++ .../Source/WWVegas/WW3D2/CMakeLists.txt | 2 + .../Source/WWVegas/WW3D2/DX8Backend.cpp | 16 +- .../Source/WWVegas/WW3D2/DX8Backend.h | 16 +- .../Source/WWVegas/WW3D2/IRenderBackend.h | 31 +- .../Source/WWVegas/WW3D2/dx8wrapper.cpp | 41 +- .../Source/WWVegas/WW3D2/seglinerenderer.cpp | 2 +- .../Source/WWVegas/WW3D2/shaders/fs_uber.sc | 48 +- .../Source/WWVegas/WW3D2/sortingrenderer.cpp | 13 +- .../Source/WWVegas/WW3D2/streakRender.cpp | 2 +- .../GameClient/Shadow/W3DProjectedShadow.cpp | 4 +- .../W3DDevice/GameClient/W3DCustomEdging.cpp | 12 +- .../W3DDevice/GameClient/W3DDisplay.cpp | 13 +- 18 files changed, 1747 insertions(+), 1576 deletions(-) create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/BgfxBackendState.h create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/BgfxBackendTextures.cpp diff --git a/Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp b/Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp index 056af0d6b9b..9dd27ce007a 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp @@ -54,6 +54,8 @@ #include "W3DDevice/GameClient/W3DShroud.h" #include "WW3D2/texture.h" #include "WW3D2/dx8caps.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" #include "WWMath/vector2i.h" @@ -760,7 +762,9 @@ void W3DRadar::renderObjectList( const RadarObject *listHead, TextureClass *text surface->Unlock(); REF_PTR_RELEASE(surface); - + // TheSuperHackers @fix bobtista 21/04/2026 The object-overlay texture (team-colored unit / building dots) is updated via Lock / Draw_Pixel / Unlock. bgfx caches textures by TextureBaseClass* so an explicit invalidation is needed each frame to re-upload. + if (g_renderBackend != nullptr && texture != nullptr) + g_renderBackend->Invalidate_Cached_Texture(texture); } //------------------------------------------------------------------------------------------------- @@ -1381,6 +1385,9 @@ void W3DRadar::setShroudLevel(Int shroudX, Int shroudY, CellShroudStatus setting surface->Unlock(); REF_PTR_RELEASE(surface); + // TheSuperHackers @fix bobtista 21/04/2026 Per-cell shroud updates done outside a begin/endSetShroudLevel envelope need to invalidate bgfx's texture cache so modified pixels upload. Without this the radar shroud never reveals as units move. + if (g_renderBackend != nullptr && m_shroudTexture != nullptr) + g_renderBackend->Invalidate_Cached_Texture(m_shroudTexture); } else { @@ -1426,6 +1433,9 @@ void W3DRadar::endSetShroudLevel() m_shroudSurfacePixelSize = 0; } REF_PTR_RELEASE(m_shroudSurface); + // TheSuperHackers @fix bobtista 21/04/2026 The radar updates its shroud via direct Lock/Draw_Pixel/Unlock on the D3D8 surface. bgfx caches the texture by TextureBaseClass* and needs an explicit invalidation to re-upload the modified pixels. + if (g_renderBackend != nullptr && m_shroudTexture != nullptr) + g_renderBackend->Invalidate_Cached_Texture(m_shroudTexture); } //------------------------------------------------------------------------------------------------- diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp index f327f351ad5..3eb1936a25a 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp @@ -1721,7 +1721,7 @@ Int TerrainShader2Stage::set(Int pass) g_renderBackend->Override_Texcoord_Index(0, 1); // Blend the result using the alpha. (came from diffuse mod texture) g_renderBackend->Override_Alpha_Blend_Enable(true); - g_renderBackend->Override_Blend(D3DBLEND_SRCALPHA, D3DBLEND_INVSRCALPHA); + g_renderBackend->Override_Blend(RB_BLEND_SRC_ALPHA, RB_BLEND_INV_SRC_ALPHA); // Disable stage 2. DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_DISABLE ); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp index 727d5fd713c..522d9eb7224 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -90,6 +90,18 @@ #include "vs_shadow_caster_dx11.bin.h" #include "fs_shadow_caster_dx11.bin.h" +#include "BgfxBackendState.h" + +// Render-state globals. Defined here (external linkage), declared `extern` +// in BgfxBackendState.h so BgfxBackendTextures.cpp can reference them. +BgfxDevice g_device; +BgfxUniforms g_uniforms; +BgfxDraw g_draw; +BgfxOverrides g_overrides; +BgfxViewFlags g_views; +BgfxFrame g_frame; +BgfxCaches g_caches; + namespace { // Anchor a reference to one bgfx symbol so the linker must resolve bgfx @@ -133,102 +145,12 @@ class BgfxLoggingCallback : public bgfx::CallbackI void captureEnd() override {} void captureFrame(const void *, uint32_t) override {} }; -BgfxLoggingCallback g_bgfxCallback; -// TheSuperHackers @refactor bobtista 11/04/2026 Tracks -// whether bgfx::init has been called successfully, so Begin_Scene / -// End_Scene / Shutdown can skip bgfx calls if init was never reached. -bool g_bgfxInitialized = false; +BgfxLoggingCallback g_bgfxCallback; -// TheSuperHackers @refactor bobtista 16/04/2026 bgfx now renders -// into the main game window. The DX8 reference popup is a secondary window -// where the D3D8 device is moved so its output remains visible for debugging. -HWND g_bgfxWindow = nullptr; +// The DX8 reference popup's window class name. Registered / unregistered in DX8Wrapper's window setup when bgfx is the primary renderer. const wchar_t * const kBgfxWindowClass = L"GGC_BgfxDebugWindow"; -// TheSuperHackers @refactor bobtista 16/04/2026 bgfx resolution -// matches the main game window. Set during Initialize from GetClientRect. -int g_bgfxWidth = 800; -int g_bgfxHeight = 600; - -// TheSuperHackers @refactor bobtista 11/04/2026 program handle -// for the passthrough shader pair. Created once in Initialize after bgfx::init -// succeeds, destroyed in Shutdown before bgfx::shutdown. -bgfx::ProgramHandle g_passthroughProgram = BGFX_INVALID_HANDLE; - -// TheSuperHackers @fix bobtista 20/04/2026 Static vertex buffer used to -// force a fullscreen black draw on view 0 each frame. bgfx's setViewClear -// does not emit ClearRenderTargetView for our backbuffer view when the -// view is only activated via bgfx::touch — confirmed via RenderDoc export -// (zero ClearRenderTargetView calls per frame). A real submit on view 0 -// makes bgfx process the view fully, clearing and covering the backbuffer -// so persisted pixels from prior frames don't leak (e.g., flickering -// yellow strips under the control bar). -bgfx::VertexBufferHandle g_fullscreenClearVB = BGFX_INVALID_HANDLE; - -// TheSuperHackers @refactor bobtista 12/04/2026 uber shader -// program. Single program handles all TSS combinations via uniforms. -bgfx::ProgramHandle g_uberProgram = BGFX_INVALID_HANDLE; - -// TheSuperHackers @refactor bobtista 14/04/2026 tree / grass -// vertex shader program (port of Trees.nvv). Uses vs_trees + fs_uber. -// Activated by Set_Tree_Vertex_Shader_Active when W3DTreeBuffer wants -// to render swaying grass; reverts to g_uberProgram otherwise. -bgfx::ProgramHandle g_treeProgram = BGFX_INVALID_HANDLE; - -// TheSuperHackers @refactor bobtista 15/04/2026 stencil shadow -// volume program + active flag + captured stencil state bits. When active -// the next SubmitEngineDraw uses g_shadowVolumeProgram with color writes -// off and g_shadowStencilState applied. -bgfx::ProgramHandle g_shadowVolumeProgram = BGFX_INVALID_HANDLE; -bgfx::ProgramHandle g_shadowApplyProgram = BGFX_INVALID_HANDLE; -bgfx::UniformHandle g_uShadowColor = BGFX_INVALID_HANDLE; - -// CSM caster pass program + render target. -bgfx::ProgramHandle g_shadowCasterProgram = BGFX_INVALID_HANDLE; -bgfx::FrameBufferHandle g_shadowMapFB = BGFX_INVALID_HANDLE; -bgfx::TextureHandle g_shadowMapDepth = BGFX_INVALID_HANDLE; -// Light-space view+proj for the shadow map view (updated per frame). -float g_shadowLightView[16]; -float g_shadowLightProj[16]; -bool g_shadowLightCaptured = false; -// World-space sun position for shadow casting, set by the engine's -// W3DShadowManager::setLightPosition via Set_Shadow_Light_Position. -// Differs from the N.L shading light (g_currentLightDirs). -float g_shadowSunPosX = 0.0f; -float g_shadowSunPosY = 0.0f; -float g_shadowSunPosZ = 1500.0f; -bool g_shadowSunPosSet = false; -// Uniform + sampler binding point for the main-pass shader to sample -// the shadow map (hooked into fs_uber). -bgfx::UniformHandle g_uShadowLightViewProj = BGFX_INVALID_HANDLE; -bgfx::UniformHandle g_sShadowMap = BGFX_INVALID_HANDLE; -// TheSuperHackers @refactor bobtista 15/04/2026 polygon-offset -// equivalent for stencil shadow volume passes. bgfx has no state bit -// for polygon offset, so the bias is applied in the volume vertex -// shader (vs_shadow_volume.sc). .x is the post-projection Z offset; -// negative values push toward the camera (closer to near plane). -bgfx::UniformHandle g_uShadowBias = BGFX_INVALID_HANDLE; -bool g_shadowVolumeActive = false; -uint32_t g_shadowStencilFront = BGFX_STENCIL_NONE; -uint32_t g_shadowStencilBack = BGFX_STENCIL_NONE; - -// Engine stencil state captured from Set_Stencil_* calls. Encoded lazily -// into g_shadowStencilFront every time a component changes. -bool g_stencilEnabled = false; -uint32_t g_stencilRef = 0; -uint32_t g_stencilReadMask = 0xFF; -uint32_t g_stencilFuncBits = BGFX_STENCIL_TEST_ALWAYS; -uint32_t g_stencilPassOpBits = BGFX_STENCIL_OP_PASS_Z_KEEP; -uint32_t g_stencilFailOpBits = BGFX_STENCIL_OP_FAIL_S_KEEP; -uint32_t g_stencilZFailOpBits = BGFX_STENCIL_OP_FAIL_Z_KEEP; - -// Current engine cull mode (RB_CULL_NONE / CW / CCW). Captured by -// BgfxBackend::Set_Cull_Mode; consumed by the shadow volume submit so -// the two-pass stencil algorithm (CW INCR pass + CCW DECR pass) writes -// stencil counts that don't cancel each other out. -int g_cullModeBits = 0; // 0 = RB_CULL_NONE, 1 = CW, 2 = CCW - static uint32_t MapCmpFuncToBgfxStencilTest(int f) { switch (f) @@ -244,154 +166,87 @@ static uint32_t MapCmpFuncToBgfxStencilTest(int f) } } -static uint32_t MapStencilOpToBgfxPassZ(int op) +// TheSuperHackers @refactor bobtista 20/04/2026 Map a D3DSTENCILOP (1..8) to the bgfx stencil op ordinal, then shift into PASS_Z / FAIL_S / FAIL_Z bit positions. +static uint32_t MapStencilOpToBgfx(int op, uint32_t shift) { + uint32_t ord; switch (op) { - case 1: return BGFX_STENCIL_OP_PASS_Z_KEEP; - case 2: return BGFX_STENCIL_OP_PASS_Z_ZERO; - case 3: return BGFX_STENCIL_OP_PASS_Z_REPLACE; - case 4: return BGFX_STENCIL_OP_PASS_Z_INCRSAT; - case 5: return BGFX_STENCIL_OP_PASS_Z_DECRSAT; - case 6: return BGFX_STENCIL_OP_PASS_Z_INVERT; - case 7: return BGFX_STENCIL_OP_PASS_Z_INCR; - case 8: return BGFX_STENCIL_OP_PASS_Z_DECR; - default: return BGFX_STENCIL_OP_PASS_Z_KEEP; - } -} - -static uint32_t MapStencilOpToBgfxFailS(int op) -{ - switch (op) - { - case 1: return BGFX_STENCIL_OP_FAIL_S_KEEP; - case 2: return BGFX_STENCIL_OP_FAIL_S_ZERO; - case 3: return BGFX_STENCIL_OP_FAIL_S_REPLACE; - case 4: return BGFX_STENCIL_OP_FAIL_S_INCRSAT; - case 5: return BGFX_STENCIL_OP_FAIL_S_DECRSAT; - case 6: return BGFX_STENCIL_OP_FAIL_S_INVERT; - case 7: return BGFX_STENCIL_OP_FAIL_S_INCR; - case 8: return BGFX_STENCIL_OP_FAIL_S_DECR; - default: return BGFX_STENCIL_OP_FAIL_S_KEEP; - } -} - -static uint32_t MapStencilOpToBgfxFailZ(int op) -{ - switch (op) - { - case 1: return BGFX_STENCIL_OP_FAIL_Z_KEEP; - case 2: return BGFX_STENCIL_OP_FAIL_Z_ZERO; - case 3: return BGFX_STENCIL_OP_FAIL_Z_REPLACE; - case 4: return BGFX_STENCIL_OP_FAIL_Z_INCRSAT; - case 5: return BGFX_STENCIL_OP_FAIL_Z_DECRSAT; - case 6: return BGFX_STENCIL_OP_FAIL_Z_INVERT; - case 7: return BGFX_STENCIL_OP_FAIL_Z_INCR; - case 8: return BGFX_STENCIL_OP_FAIL_Z_DECR; - default: return BGFX_STENCIL_OP_FAIL_Z_KEEP; - } + case 1: ord = 1; break; // D3DSTENCILOP_KEEP + case 2: ord = 0; break; // D3DSTENCILOP_ZERO + case 3: ord = 2; break; // D3DSTENCILOP_REPLACE + case 4: ord = 4; break; // D3DSTENCILOP_INCRSAT + case 5: ord = 6; break; // D3DSTENCILOP_DECRSAT + case 6: ord = 7; break; // D3DSTENCILOP_INVERT + case 7: ord = 3; break; // D3DSTENCILOP_INCR + case 8: ord = 5; break; // D3DSTENCILOP_DECR + default: ord = 1; break; + } + return ord << shift; } static void UpdateShadowStencilState() { - if (!g_stencilEnabled) + if (!g_draw.stencilEnabled) { - g_shadowStencilFront = BGFX_STENCIL_NONE; - g_shadowStencilBack = BGFX_STENCIL_NONE; + g_draw.shadowStencilFront = BGFX_STENCIL_NONE; + g_draw.shadowStencilBack = BGFX_STENCIL_NONE; return; } - g_shadowStencilFront = g_stencilFuncBits - | BGFX_STENCIL_FUNC_REF(g_stencilRef & 0xFF) - | BGFX_STENCIL_FUNC_RMASK(g_stencilReadMask & 0xFF) - | g_stencilFailOpBits - | g_stencilZFailOpBits - | g_stencilPassOpBits; - g_shadowStencilBack = BGFX_STENCIL_NONE; + g_draw.shadowStencilFront = g_draw.stencilFuncBits + | BGFX_STENCIL_FUNC_REF(g_draw.stencilRef & 0xFF) + | BGFX_STENCIL_FUNC_RMASK(g_draw.stencilReadMask & 0xFF) + | g_draw.stencilFailOpBits + | g_draw.stencilZFailOpBits + | g_draw.stencilPassOpBits; + g_draw.shadowStencilBack = BGFX_STENCIL_NONE; } -bgfx::UniformHandle g_uSwayTable = BGFX_INVALID_HANDLE; -bgfx::UniformHandle g_uShroudOffset = BGFX_INVALID_HANDLE; -bgfx::UniformHandle g_uShroudScale = BGFX_INVALID_HANDLE; // Sway table: 11 entries (no-sway at index 0, MAX_SWAY_TYPES=10 active). -float g_currentSwayTable[11][4] = {{0}}; -float g_currentShroudOffset[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; -float g_currentShroudScale[4] = { 0.0f, 0.0f, 1.0f, 1.0f }; -bool g_treeShaderActive = false; // Sampler uniform shared by all textured fragment shaders. Bound to stage 0. -bgfx::UniformHandle g_sTex0 = BGFX_INVALID_HANDLE; -bgfx::UniformHandle g_sTex1 = BGFX_INVALID_HANDLE; -bgfx::UniformHandle g_sTex2 = BGFX_INVALID_HANDLE; -bgfx::UniformHandle g_sTex3 = BGFX_INVALID_HANDLE; // TheSuperHackers @refactor bobtista 11/04/2026 material // diffuse uniform. Carries the VertexMaterialClass::Get_Diffuse color // + opacity from Set_Material into the fragment shader so team colors // (which the W3D engine writes into the material diffuse channel) and // alpha fades modulate the output. -bgfx::UniformHandle g_uMatDiffuse = BGFX_INVALID_HANDLE; -float g_currentMatDiffuse[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; // TheSuperHackers @feature bobtista 20/04/2026 Material emissive uniform. // D3D8 fixed-function adds the material's emissive color to the lit // output (D3DRS_EMISSIVEMATERIALSOURCE, MATERIAL.Emissive). Self- // illuminating meshes like the "move here" hint rely on this to produce // their glow color even when no light reaches them. fs_uber needs this // term or such meshes render black. -bgfx::UniformHandle g_uMatEmissive = BGFX_INVALID_HANDLE; -float g_currentMatEmissive[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; // Grayscale output override for disabled 2D UI elements (Render2DClass::Enable_Grayscale). // D3D8 path uses D3DTOP_DOTPRODUCT3 + TFACTOR=0x80A5CA8E (luminance weights); bgfx path -// applies the dot-product at the end of fs_uber when g_currentGrayscaleEnable[0] > 0.5. -bgfx::UniformHandle g_uGrayscaleEnable = BGFX_INVALID_HANDLE; -float g_currentGrayscaleEnable[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; +// applies the dot-product at the end of fs_uber when g_draw.grayscaleEnable[0] > 0.5. // Alpha-test parameters consumed by fs_textured_lit_atest. .x is the // reference threshold in [0, 1]; engine writes ShaderClass alpha-ref / 255. // Named u_atestParams (not u_alphaRef) to avoid bgfx_shader.sh's internal // u_alphaRef4 conflict. See fs_textured_lit_atest.sc for details. -bgfx::UniformHandle g_uAtestParams = BGFX_INVALID_HANDLE; // TSS operation uniforms. Encode the DX8 texture stage state // operations so the uber fragment shader can evaluate them at runtime. -bgfx::UniformHandle g_uTssOps0 = BGFX_INVALID_HANDLE; -bgfx::UniformHandle g_uTssOps1 = BGFX_INVALID_HANDLE; -float g_currentTssOps0[4] = { 3.0f, 3.0f, 0.0f, 0.0f }; // default: MODULATE color+alpha, no secondary -float g_currentTssOps1[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; // default: arg1=TEXTURE for all -float g_currentAtestRef = 0.0f; // alpha test reference, 0 = disabled // Post-ShaderClass blend/alpha-test overrides. Set by Override_Blend / // Override_Alpha_Test, cleared by Clear_State_Overrides (called from Set_Shader). -bool g_blendOverrideActive = false; -uint64_t g_blendOverrideBits = 0; -bool g_atestOverrideActive = false; -float g_atestOverrideRef = 0.0f; -bool g_suppressBgfxDraw = false; -int g_colorWriteOverride = -1; -bool g_effectOverlayActive = false; // TheSuperHackers @feature bobtista 16/04/2026 2D overlay active flag. // Set by Set_View_Identity (Render2DClass enters 2D mode), cleared by // Set_Transform(VIEW) when a real camera view is restored or at Begin_Scene. -bool g_2DOverlayActive = false; // UV set selection: when > 0, the fragment shader samples stage 0 // from v_texcoord1 instead of v_texcoord0. Set by the terrain shader // when it changes D3DTSS_TEXCOORDINDEX to 1 for the blend pass. -bgfx::UniformHandle g_uTexcoordSelect = BGFX_INVALID_HANDLE; -float g_currentTexcoordSelect[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; // Shroud pass: the D3D8 path uses D3DTSS_TCI_CAMERASPACEPOSITION to auto- // generate UVs from camera-space vertex positions, combined with a texture // transform matrix. bgfx has no equivalent, so we upload the full texture // matrix and let the vertex shader compute the UVs explicitly. -bgfx::UniformHandle g_uShroudParams = BGFX_INVALID_HANDLE; // TheSuperHackers @feature bobtista 20/04/2026 Cloud-shadow modulation // for terrain (DX8 ST_TERRAIN_BASE_NOISE1 / _NOISE12). Scroll offset // advances each frame from TerrainShader2Stage::m_xOffset/m_yOffset; // the fragment shader multiplies the sampled cloud color into the base. // .xy = scroll offset, .z = world→UV stretch, .w > 0.5 = enable. -bgfx::UniformHandle g_uCloudParams = BGFX_INVALID_HANDLE; -bgfx::UniformHandle g_sCloudMap = BGFX_INVALID_HANDLE; -float g_currentCloudParams[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; -bgfx::TextureHandle g_currentCloudTex = BGFX_INVALID_HANDLE; // .y = terrain blend flag: when > 0, shader does lerp(tex0, tex1, vertex_alpha) // using UV set 0 for tex0 and UV set 1 for tex1 @@ -402,31 +257,12 @@ bgfx::TextureHandle g_currentCloudTex = BGFX_INVALID_HANDLE; // 4 lights packed into vec4 arrays (one element per light). // u_lightDirs[i].xyz = direction toward light, .w = enabled flag // u_lightColors[i].rgb = diffuse color -bgfx::UniformHandle g_uLightDirs = BGFX_INVALID_HANDLE; -bgfx::UniformHandle g_uLightColors = BGFX_INVALID_HANDLE; -bgfx::UniformHandle g_uSceneAmbient = BGFX_INVALID_HANDLE; -bgfx::UniformHandle g_uLightingEnabled = BGFX_INVALID_HANDLE; // Defaults match the old hardcoded sun: direction TOWARD light (positive), // white diffuse, reasonable ambient. These are used until the first // Set_Light_Environment call provides real game lights. -float g_currentLightDirs[4][4] = { - { 0.35f, 0.55f, 0.75f, 1.0f }, - { 0.0f, 0.0f, 0.0f, 0.0f }, - { 0.0f, 0.0f, 0.0f, 0.0f }, - { 0.0f, 0.0f, 0.0f, 0.0f } -}; -float g_currentLightColors[4][4] = { - { 0.75f, 0.75f, 0.75f, 1.0f }, - { 0.0f, 0.0f, 0.0f, 0.0f }, - { 0.0f, 0.0f, 0.0f, 0.0f }, - { 0.0f, 0.0f, 0.0f, 0.0f } -}; -float g_currentSceneAmbient[4] = { 0.45f, 0.45f, 0.45f, 1.0f }; - // Tracks whether the current material has lighting enabled. // When false, the vertex color contains pre-baked lighting (terrain) // and the fragment shader should NOT apply N.L lighting on top. -float g_currentLightingEnabled[4] = { 1.0f, 0.0f, 0.0f, 0.0f }; // TheSuperHackers @refactor bobtista 11/04/2026 default 1x1 // white texture. Real Set_Texture wiring is not in place yet, so the @@ -434,61 +270,40 @@ float g_currentLightingEnabled[4] = { 1.0f, 0.0f, 0.0f, 0.0f }; // undefined values. A 1x1 opaque white texture is a sensible default // because the fragment shader does texColor * v_color0 - white * vc // gives the vertex color through unmodified. -bgfx::TextureHandle g_defaultWhiteTexture = BGFX_INVALID_HANDLE; -bgfx::TextureHandle g_defaultTransparentTexture = BGFX_INVALID_HANDLE; // Track which engine textures are render targets so we can use the // transparent fallback instead of white. -std::unordered_map g_renderTargetSet; - -struct BgfxFramebufferEntry -{ - bgfx::FrameBufferHandle fb; - bgfx::TextureHandle colorTex; - uint16_t width; - uint16_t height; -}; -std::unordered_map g_framebufferCache; // Vertex layout used by the test triangle. Position + packed RGBA color. // Initialized in Initialize since bgfx::VertexLayout::begin needs bgfx to be // up and running (it queries the active renderer for the pos type). -bgfx::VertexLayout g_triangleLayout; // TheSuperHackers @refactor bobtista 11/04/2026 standard // vertex layouts. One per common FVF format // FVF table). Initialized in Initialize, used by the vertex // buffer creation path. Names follow the FVF tags - P=position, // N=normal, D=diffuse (color0), T=texcoord. -bgfx::VertexLayout g_layoutP; // XYZ -bgfx::VertexLayout g_layoutPN; // XYZ + NORMAL -bgfx::VertexLayout g_layoutPNT1; // XYZ + NORMAL + TEX1 -bgfx::VertexLayout g_layoutPNT2; // XYZ + NORMAL + TEX1 + TEX2 -bgfx::VertexLayout g_layoutPT1; // XYZ + TEX1 -bgfx::VertexLayout g_layoutPDT1; // XYZ + DIFFUSE + TEX1 -bgfx::VertexLayout g_layoutPNDT1; // XYZ + NORMAL + DIFFUSE + TEX1 -bgfx::VertexLayout g_layoutPNDT2; // XYZ + NORMAL + DIFFUSE + TEX1 + TEX2 void BuildStandardVertexLayouts() { - g_layoutP + g_device.layoutP .begin() .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) .end(); - g_layoutPN + g_device.layoutPN .begin() .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) .add(bgfx::Attrib::Normal, 3, bgfx::AttribType::Float) .end(); - g_layoutPNT1 + g_device.layoutPNT1 .begin() .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) .add(bgfx::Attrib::Normal, 3, bgfx::AttribType::Float) .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float) .end(); - g_layoutPNT2 + g_device.layoutPNT2 .begin() .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) .add(bgfx::Attrib::Normal, 3, bgfx::AttribType::Float) @@ -496,20 +311,20 @@ void BuildStandardVertexLayouts() .add(bgfx::Attrib::TexCoord1, 2, bgfx::AttribType::Float) .end(); - g_layoutPT1 + g_device.layoutPT1 .begin() .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float) .end(); - g_layoutPDT1 + g_device.layoutPDT1 .begin() .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) .add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true) .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float) .end(); - g_layoutPNDT1 + g_device.layoutPNDT1 .begin() .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) .add(bgfx::Attrib::Normal, 3, bgfx::AttribType::Float) @@ -517,7 +332,7 @@ void BuildStandardVertexLayouts() .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float) .end(); - g_layoutPNDT2 + g_device.layoutPNDT2 .begin() .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) .add(bgfx::Attrib::Normal, 3, bgfx::AttribType::Float) @@ -606,7 +421,7 @@ bool RegisterBgfxDebugWindowClass() // - textured & lit -> g_texturedLitProgram // - textured & !lit -> g_texturedUnlitProgram // - !textured & lit -> g_solidLitProgram -// - else -> g_passthroughProgram (debug) +// - else -> g_device.passthroughProgram (debug) // // State bit translation is mechanical: depth compare, depth write, color // write, blend factors, cull. Detail-blend / fog / specular gradient are @@ -665,6 +480,9 @@ uint64_t TranslateDepthCompare(ShaderClass::DepthCompareType cmp) // 5=ADD 6=ADDSIGNED 7=SUBTRACT 8=BLENDTEXALPHA 9=BLENDCURALPHA 10=ADDSMOOTH // Arg source IDs: 0=TEXTURE 1=DIFFUSE 2=CURRENT +// Default D3D8 alpha-test reference (0x60/255 = 0.376) used when a shader has ALPHATEST enabled without an explicit reference. Matches the implicit D3D8 behavior preserved by ShaderClass presets. +const float kDefaultAlphaTestRef = 0x60 / 255.0f; + void BuildTssOpsForShader(const ShaderClass & shader, float * ops0, float * ops1, float * atestRef) { @@ -806,7 +624,7 @@ void BuildTssOpsForShader(const ShaderClass & shader, if (shader.Get_Alpha_Test() != ShaderClass::ALPHATEST_DISABLE) { - *atestRef = 0x60 / 255.0f; + *atestRef = kDefaultAlphaTestRef; } else { @@ -990,56 +808,27 @@ bool BuildBgfxLayoutForFVF(const FVFInfoClass & fvf, bgfx::VertexLayout & out) // engine destroys a VertexBufferClass and reuses the memory address for // a new VB with different dimensions — otherwise we'd hand back a stale // handle and bgfx would truncate writes / crash on staging creation. -struct BgfxVbCacheEntry { - bgfx::DynamicVertexBufferHandle handle; - uint32_t num_verts; - uint32_t stride; -}; -struct BgfxIbCacheEntry { - bgfx::DynamicIndexBufferHandle handle; - uint32_t num_indices; -}; -std::unordered_map g_vbCache; -std::unordered_map g_ibCache; -std::unordered_map g_textureCache; - // TheSuperHackers @fix bobtista 19/04/2026 Track D3D8 texture pointers // and dimensions alongside TextureClass* to detect stale cache entries // (address reuse) and enable in-place updates for same-sized textures. -struct D3DPtrInfo { IDirect3DBaseTexture8 * ptr; uint16_t w; uint16_t h; }; -std::unordered_map g_d3dPtrCache; - // TheSuperHackers @fix bobtista 19/04/2026 Deferred texture destruction. // When a stale texture cache entry is detected (D3D8 pointer changed), the // old bgfx handle can't be destroyed immediately because in-flight draws // may still reference it. Double-buffer: collect in current frame, destroy // after the NEXT bgfx::frame() (2 frames later = guaranteed safe). -static std::vector g_deferredTextureDestroys; // current frame -static std::vector g_deferredTextureDestroysPrev; // previous frame (safe to destroy) // The bgfx texture currently bound to stage 0 by Set_Texture. Used by -// SubmitEngineDraw - falls back to g_defaultWhiteTexture if invalid. -bgfx::TextureHandle g_currentBgfxTexture0 = BGFX_INVALID_HANDLE; -bgfx::TextureHandle g_currentBgfxTexture1 = BGFX_INVALID_HANDLE; -bgfx::TextureHandle g_currentBgfxTexture2 = BGFX_INVALID_HANDLE; -bgfx::TextureHandle g_currentBgfxTexture3 = BGFX_INVALID_HANDLE; +// SubmitEngineDraw - falls back to g_device.defaultWhiteTexture if invalid. // Per-stage sampler flags captured from the source TextureClass's // Get_U/V_Addr_Mode in Set_Texture. Default 0 = use bgfx's creation-time // default (usually linear filter + wrap). Shoreline LUT needs CLAMP // because its U coord can exceed [0,1] and WRAP produces a visible // stripe/checker artifact at the boundary. -uint32_t g_currentSamplerFlags0 = 0; -uint32_t g_currentSamplerFlags1 = 0; -uint32_t g_currentSamplerFlags2 = 0; -uint32_t g_currentSamplerFlags3 = 0; // The most recent buffers and offsets cached from Set_Vertex_Buffer / // Set_Index_Buffer. Read by Draw_Triangles when it issues the bgfx // submit. Cleared (made invalid) on Shutdown. -bgfx::DynamicVertexBufferHandle g_currentBgfxVB = BGFX_INVALID_HANDLE; -bgfx::DynamicIndexBufferHandle g_currentBgfxIB = BGFX_INVALID_HANDLE; -unsigned short g_currentIBOffset = 0; // TheSuperHackers @refactor bobtista 11/04/2026 transient // (dynamic) buffer state. Capture_Dynamic_Vertex_Data allocs a bgfx @@ -1047,28 +836,9 @@ unsigned short g_currentIBOffset = 0; // the matching Set_Vertex_Buffer(DynamicVBAccessClass&) call can claim // it. The transient buffers are auto-freed at bgfx::frame time; we // only track validity within the current frame. -struct PendingTransientVB -{ - bool valid; - const DynamicVBAccessClass * owner; - bgfx::TransientVertexBuffer tvb; -}; -struct PendingTransientIB -{ - bool valid; - const DynamicIBAccessClass * owner; - bgfx::TransientIndexBuffer tib; -}; -PendingTransientVB g_pendingDynVB = { false, nullptr, {} }; -PendingTransientIB g_pendingDynIB = { false, nullptr, {} }; - // Current draw call uses transient buffers if these are set. They // shadow the static VB/IB handles above - SubmitEngineDraw picks the // transient path when these are true. -bool g_currentUseTransientVB = false; -bgfx::TransientVertexBuffer g_currentTransientVB = {}; -bool g_currentUseTransientIB = false; -bgfx::TransientIndexBuffer g_currentTransientIB = {}; // TheSuperHackers @refactor bobtista 11/04/2026 transform // capture. The engine calls Set_Transform with world / view / projection @@ -1082,18 +852,11 @@ bgfx::TransientIndexBuffer g_currentTransientIB = {}; // We capture all three matrices and apply them per-submit. View and // projection are written via setViewTransform on view 1; world is set // per-submit via setTransform. -float g_bgfxWorld[16]; -float g_bgfxView[16]; -float g_bgfxProj[16]; -bool g_bgfxViewProjDirty = true; -// Snapshot of g_bgfxView and g_bgfxProj captured at the first opaque +// Snapshot of g_frame.view and g_frame.proj captured at the first opaque // draw of each frame. Re-applied to view 1 at End_Scene to prevent // later Set_Projection calls (water, shadows, sneak attack) from // retroactively stomping the camera projection via setViewTransform. -float g_bgfxCameraView[16]; -float g_bgfxCameraProj[16]; -bool g_bgfxCameraCaptured = false; // Engine geometry submits to its own view so it does not collide with // the test triangle on view 0. View 0 keeps the test triangle for the @@ -1105,7 +868,7 @@ const bgfx::ViewId kBgfxEngineView = 1; // TheSuperHackers @refactor bobtista 11/04/2026 dedicated // view id for sorted draws. View 2's view matrix is permanently // identity and its projection tracks view 1's. Per-batch sort -// transforms get pre-multiplied into g_bgfxSortWorld so view 2 never +// transforms get pre-multiplied into g_frame.sortWorld so view 2 never // needs setViewTransform updates per batch - which is critical, // because bgfx::setViewTransform is per-view-for-the-whole-frame and // would otherwise stomp view 1's camera view if shared. @@ -1146,26 +909,24 @@ const float kShadowOrthoSize = 1200.0f; const float kShadowCameraDistance = 2000.0f; const float kShadowOrthoFarMargin = 1000.0f; const int kSwayTableEntries = 11; +// Frames to wait before showing the DX8 reference popup. The game's input system and shell menu need a beat after device init to fully settle; popping the ref window earlier steals focus and blocks mouse capture. ~0.5s at 60fps. +const int kDX8RefWindowShowDelayFrames = 30; // Render-to-texture state. Set by Set_Render_Target_With_Z, cleared // when the back buffer is restored. SubmitEngineDraw routes to // kBgfxRTTView while this is true. -bool g_renderToTexture = false; // True between Begin_Sorted_Batch_Pass and End_Sorted_Batch_Pass; // SubmitEngineDraw routes to kBgfxEngineSortView and uses -// g_bgfxSortWorld while this is set. -bool g_inSortFlush = false; +// g_frame.sortWorld while this is set. // Per-batch effective world for sorted draws: the pre-multiplied // sortView * sortWorld (in bgfx column-major form) captured from the // engine's render_state by Capture_Sorted_Batch_Transforms. -float g_bgfxSortWorld[16]; // CSM: raw model-to-world matrix for sorted draws, WITHOUT // the camera view baked in. Used for shadow caster submissions where -// the light's view+proj replaces the camera's. g_bgfxSortWorld has +// the light's view+proj replaces the camera's. g_frame.sortWorld has // model*cameraView which contaminates the light-space transform. -float g_bgfxSortWorldRaw[16]; // TheSuperHackers @refactor bobtista 11/04/2026 Set by // Submit_Sorted_Draw after it emits the bgfx submit for a sorting VB @@ -1173,20 +934,16 @@ float g_bgfxSortWorldRaw[16]; // flag to skip its SubmitEngineDraw - the draw was already issued // with correctly remapped args against the inner dynamic buffers, // so falling through would emit a second, incorrect submit. -bool g_skipNextSubmitEngineDraw = false; // Water override — set by Override_Material_Opacity, consumed by // SubmitEngineDraw to route to the water view and apply DESTALPHA blend. -bool g_waterOverrideActive = false; -// Snapshot of g_bgfxProj at the time the sort flush runs. The engine +// Snapshot of g_frame.proj at the time the sort flush runs. The engine // calls Set_Projection_Transform_With_Z_Bias multiple times per frame // (camera, water reflections, shadows). The LAST call may use a tiny // near-field frustum that clips all sort geometry. We capture the // projection at sort-flush time (when it's still the camera projection) // and re-apply it to view 2 at End_Scene time. -float g_bgfxSortProj[16]; -bool g_bgfxSortProjCaptured = false; void IdentityMatrix(float * out) { @@ -1238,406 +995,17 @@ constexpr bool kBgfxSkipBufferRead = true; // (render targets, dynamic textures) are skipped to avoid the same // corruption that hit vertex buffers. -bgfx::TextureFormat::Enum TranslateWW3DFormat(WW3DFormat fmt) -{ - switch (fmt) - { - case WW3D_FORMAT_A8R8G8B8: - case WW3D_FORMAT_X8R8G8B8: - // D3D8 stores ARGB as 0xAARRGGBB which on little-endian - // memory is BB GG RR AA - matches bgfx BGRA8 byte order. - return bgfx::TextureFormat::BGRA8; - case WW3D_FORMAT_R5G6B5: return bgfx::TextureFormat::R5G6B5; - case WW3D_FORMAT_A1R5G5B5: return bgfx::TextureFormat::BGR5A1; - case WW3D_FORMAT_A4R4G4B4: return bgfx::TextureFormat::BGRA4; - case WW3D_FORMAT_A8: return bgfx::TextureFormat::A8; - case WW3D_FORMAT_L8: return bgfx::TextureFormat::R8; - case WW3D_FORMAT_DXT1: return bgfx::TextureFormat::BC1; - case WW3D_FORMAT_DXT2: - case WW3D_FORMAT_DXT3: return bgfx::TextureFormat::BC2; - case WW3D_FORMAT_DXT4: - case WW3D_FORMAT_DXT5: return bgfx::TextureFormat::BC3; - default: return bgfx::TextureFormat::Unknown; - } -} - -bgfx::TextureHandle EnsureBgfxTexture(TextureBaseClass * tex) -{ - if (tex == nullptr) - { - return BGFX_INVALID_HANDLE; - } - IDirect3DBaseTexture8 * curD3D = tex->Peek_D3D_Base_Texture(); - - auto it = g_textureCache.find(tex); - if (it != g_textureCache.end()) - { - auto d3dIt = g_d3dPtrCache.find(tex); - bool d3dPtrMatch = (d3dIt != g_d3dPtrCache.end() && d3dIt->second.ptr == curD3D); - if (d3dPtrMatch) - { - return it->second; - } - // D3D8 texture pointer changed — the TextureClass* address was reused - // for a different texture, OR the engine invalidated via - // Invalidate_Cached_Texture after writing new pixels (e.g. video - // frame, font atlas). Try to UPDATE the existing bgfx handle in - // place (no handle churn) if dimensions and format match. - // Only fall back to destroy+recreate if they differ. - // TheSuperHackers @bugfix bobtista 20/04/2026 Capture the stored - // dimensions BEFORE overwriting the cache entry — the in-place - // update path below checks against them, and the iterator aliases - // the same entry we are about to stomp. - uint16_t cachedW = 0; - uint16_t cachedH = 0; - if (d3dIt != g_d3dPtrCache.end()) - { - cachedW = d3dIt->second.w; - cachedH = d3dIt->second.h; - } - g_d3dPtrCache[tex] = { curD3D, 0, 0 }; - if (bgfx::isValid(it->second)) - { - TextureClass * tex2d = tex->As_TextureClass(); - if (tex2d != nullptr && tex->Get_Pool() != TextureBaseClass::POOL_DEFAULT) - { - IDirect3DTexture8 * d3dTex = static_cast(curD3D); - D3DSURFACE_DESC desc; - if (SUCCEEDED(d3dTex->GetLevelDesc(0, &desc))) - { - const bgfx::TextureFormat::Enum bgfxFmt = TranslateWW3DFormat(tex2d->Get_Texture_Format()); - // Check if dimensions match the existing bgfx handle - if (desc.Width == cachedW - && desc.Height == cachedH - && bgfxFmt != bgfx::TextureFormat::Unknown) - { - D3DLOCKED_RECT locked = { 0 }; - if (SUCCEEDED(d3dTex->LockRect(0, &locked, NULL, D3DLOCK_READONLY))) - { - const bool isCompressed = (bgfxFmt >= bgfx::TextureFormat::BC1 && bgfxFmt <= bgfx::TextureFormat::BC7); - const unsigned bpp = isCompressed ? 0 : - (bgfxFmt == bgfx::TextureFormat::BGRA4 || bgfxFmt == bgfx::TextureFormat::R5G6B5 || bgfxFmt == bgfx::TextureFormat::BGR5A1) ? 2 : - (bgfxFmt == bgfx::TextureFormat::A8 || bgfxFmt == bgfx::TextureFormat::R8) ? 1 : 4; - const unsigned expectedPitch = isCompressed ? 0 : desc.Width * bpp; - const unsigned numRows = isCompressed ? ((desc.Height + 3) / 4) : desc.Height; - const unsigned srcPitch = static_cast(locked.Pitch); - const unsigned rowBytes = (expectedPitch > 0) ? expectedPitch : srcPitch; - const unsigned totalBytes = numRows * rowBytes; - const bgfx::Memory * mem = bgfx::alloc(totalBytes); - if (srcPitch == expectedPitch || isCompressed) - std::memcpy(mem->data, locked.pBits, totalBytes); - else - for (unsigned row = 0; row < numRows; ++row) - std::memcpy(mem->data + row * rowBytes, - static_cast(locked.pBits) + row * srcPitch, rowBytes); - d3dTex->UnlockRect(0); - - // TheSuperHackers @bugfix bobtista 20/04/2026 - // D3D8 samples the alpha channel of X8R8G8B8 - // textures as 1.0 regardless of memory content - // (the "X" component is ignored). bgfx's BGRA8 - // format has no such convention and samples the - // raw memory byte, which for FFmpeg video - // frames (AV_PIX_FMT_BGR0) is 0 — making - // SRC_ALPHA blended draws invisible. Force - // alpha to 0xFF ONLY for procedurally-created - // X8R8G8B8 textures (no file path — e.g. video - // buffers). TGA-loaded X8R8G8B8 textures may - // have real alpha data the game relies on - // (scorch marks, decals), so leave those alone. - if (tex2d->Get_Texture_Format() == WW3D_FORMAT_X8R8G8B8 - && bgfxFmt == bgfx::TextureFormat::BGRA8 - && tex2d->Get_Full_Path().Is_Empty()) - { - uint8_t * px = mem->data; - const unsigned pixelCount = (expectedPitch / 4) * numRows; - for (unsigned i = 0; i < pixelCount; ++i) - px[i * 4 + 3] = 0xff; - } - - bgfx::updateTexture2D(it->second, 0, 0, 0, 0, - static_cast(desc.Width), - static_cast(desc.Height), mem); - // Store dimensions so subsequent invalidations - // can still match the in-place update path. - g_d3dPtrCache[tex] = { curD3D, - static_cast(desc.Width), - static_cast(desc.Height) }; - return it->second; // Reused handle, no churn - } - } - } - } - // Dimensions or format changed — must destroy and recreate - g_deferredTextureDestroys.push_back(it->second); - } - g_textureCache.erase(it); - } - else - { - g_d3dPtrCache[tex] = { curD3D, 0, 0 }; - } - - // Only handle TextureClass (regular 2D) for now. Cube and volume - // textures take a different path and would need their own helpers. - TextureClass * tex2d = tex->As_TextureClass(); - if (tex2d == nullptr) - { - g_textureCache[tex] = BGFX_INVALID_HANDLE; - return BGFX_INVALID_HANDLE; - } - - if (tex->Get_Pool() == TextureBaseClass::POOL_DEFAULT) - { - auto fbIt = g_framebufferCache.find(tex); - if (fbIt != g_framebufferCache.end()) - { - static bool s_loggedRTTResolve = false; - if (!s_loggedRTTResolve) - { - s_loggedRTTResolve = true; - WWDEBUG_SAY(("[BgfxBackend] RTT RESOLVE: POOL_DEFAULT tex=%p " - "resolved to framebuffer color texture %dx%d", - tex, fbIt->second.width, fbIt->second.height)); - } - return fbIt->second.colorTex; - } - - g_renderTargetSet[tex] = true; - return BGFX_INVALID_HANDLE; - } +} // end anonymous namespace (helpers moved to BgfxBackendTextures.cpp) - const bgfx::TextureFormat::Enum bgfxFmt = TranslateWW3DFormat(tex2d->Get_Texture_Format()); - if (bgfxFmt == bgfx::TextureFormat::Unknown) - { - static bool s_loggedUnknownFmt = false; - if (!s_loggedUnknownFmt) - { - s_loggedUnknownFmt = true; - WWDEBUG_SAY(("[BgfxBackend] UNKNOWN texture format: %s ww3dfmt=%d", - tex2d->Get_Full_Path().str(), - static_cast(tex2d->Get_Texture_Format()))); - } - g_textureCache[tex] = BGFX_INVALID_HANDLE; - return BGFX_INVALID_HANDLE; - } - - IDirect3DBaseTexture8 * baseTex = tex->Peek_D3D_Base_Texture(); - if (baseTex == nullptr) - { - return BGFX_INVALID_HANDLE; - } - - IDirect3DTexture8 * d3dTex = static_cast(baseTex); - - D3DSURFACE_DESC desc; - if (FAILED(d3dTex->GetLevelDesc(0, &desc))) - { - g_textureCache[tex] = BGFX_INVALID_HANDLE; - return BGFX_INVALID_HANDLE; - } - - D3DLOCKED_RECT locked = { 0 }; - HRESULT hr = d3dTex->LockRect(0, &locked, NULL, D3DLOCK_READONLY); - if (FAILED(hr) || locked.pBits == NULL) - { - WWDEBUG_SAY(("[BgfxBackend] EnsureBgfxTexture LockRect failed hr=0x%08x", static_cast(hr))); - g_textureCache[tex] = BGFX_INVALID_HANDLE; - return BGFX_INVALID_HANDLE; - } - - // TheSuperHackers @bugfix bobtista 16/04/2026 Compute tightly-packed row - // pitch that bgfx expects and re-pack if the D3D8 locked pitch differs - // (drivers may add row padding for alignment). bgfx asserts that the - // memory size equals its internal storageSize, so we must match exactly. - const bool isCompressed = - bgfxFmt == bgfx::TextureFormat::BC1 || - bgfxFmt == bgfx::TextureFormat::BC2 || - bgfxFmt == bgfx::TextureFormat::BC3; - - unsigned expectedPitch = 0; - unsigned numRows = 0; - if (isCompressed) - { - const unsigned blockWidth = 4; - const unsigned blockHeight = 4; - const unsigned blockSize = (bgfxFmt == bgfx::TextureFormat::BC1) ? 8 : 16; - expectedPitch = ((desc.Width + blockWidth - 1) / blockWidth) * blockSize; - numRows = (desc.Height + blockHeight - 1) / blockHeight; - } - else - { - unsigned bpp = 0; - switch (bgfxFmt) - { - case bgfx::TextureFormat::BGRA8: bpp = 32; break; - case bgfx::TextureFormat::R5G6B5: bpp = 16; break; - case bgfx::TextureFormat::BGR5A1: bpp = 16; break; - case bgfx::TextureFormat::BGRA4: bpp = 16; break; - case bgfx::TextureFormat::A8: bpp = 8; break; - case bgfx::TextureFormat::R8: bpp = 8; break; - default: bpp = 32; break; - } - expectedPitch = desc.Width * bpp / 8; - numRows = desc.Height; - } - - const unsigned totalBytes = numRows * expectedPitch; - const unsigned srcPitch = static_cast(locked.Pitch); - - const bgfx::Memory * mem = bgfx::alloc(totalBytes); - if (srcPitch == expectedPitch) - { - std::memcpy(mem->data, locked.pBits, totalBytes); - } - else - { - const unsigned copyPitch = (srcPitch < expectedPitch) ? srcPitch : expectedPitch; - const uint8_t * src = static_cast(locked.pBits); - uint8_t * dst = mem->data; - for (unsigned row = 0; row < numRows; ++row) - { - std::memcpy(dst, src, copyPitch); - if (copyPitch < expectedPitch) - { - std::memset(dst + copyPitch, 0, expectedPitch - copyPitch); - } - src += srcPitch; - dst += expectedPitch; - } - } - d3dTex->UnlockRect(0); - - // TheSuperHackers @fix bobtista 20/04/2026 Team-colored textures - // (name prefixed with "##" via W3DAssetManager's Munge_Texture_Name) - // go through Remap_Palette which forces alpha to 0xff on every pixel. - // The original TGA stored its "transparent" regions as pure-black RGB - // with full alpha, relying on a D3D8-era color-key-on-RGB mechanism - // (or implicit alpha-test on luminance) that we haven't reproduced in - // bgfx. Without it, the black background paints over the stone bib - // around sub-faction emblems (the long-standing "black rectangle" - // bug). Emulate color-keying at upload time: for #-prefixed BGRA8 - // textures, scan the pixel data and set alpha=0 on any pixel whose - // RGB is all zero. Scorch marks / shadows use different names so are - // unaffected. - if (!isCompressed && bgfxFmt == bgfx::TextureFormat::BGRA8) - { - TextureClass * t = tex->As_TextureClass(); - const char * texName = t ? t->Get_Full_Path().str() : nullptr; - if (texName != nullptr && texName[0] == '#') - { - uint8_t * pix = mem->data; - const unsigned pixelCount = expectedPitch / 4 * numRows; - for (unsigned i = 0; i < pixelCount; ++i) - { - // BGRA8: byte order B, G, R, A. Transparent if B=G=R=0. - if (pix[i * 4 + 0] == 0 && pix[i * 4 + 1] == 0 && pix[i * 4 + 2] == 0) - { - pix[i * 4 + 3] = 0; - } - } - } - } - - // TheSuperHackers @bugfix bobtista 20/04/2026 See matching in-place - // path above. D3D8's X8R8G8B8 ignores the "X" byte and samples alpha - // as 1.0; bgfx BGRA8 samples it literally. Video buffers (FFmpeg - // BGR0) are written with alpha=0 and would draw as transparent. - // Force 0xFF for procedurally-created X8R8G8B8 textures only (empty - // path); TGA-loaded X8R8G8B8 textures may carry real alpha data the - // game relies on (scorch marks, decals) so leave those untouched. - if (!isCompressed - && bgfxFmt == bgfx::TextureFormat::BGRA8 - && tex2d->Get_Texture_Format() == WW3D_FORMAT_X8R8G8B8 - && tex2d->Get_Full_Path().Is_Empty()) - { - uint8_t * px = mem->data; - const unsigned pixelCount = (expectedPitch / 4) * numRows; - for (unsigned i = 0; i < pixelCount; ++i) - px[i * 4 + 3] = 0xff; - } - - // TheSuperHackers @fix bobtista 19/04/2026 Create textures WITHOUT - // initial data so they are mutable. Passing data to createTexture2D - // makes the texture immutable, silently rejecting later updates (font - // atlas glyph additions, dynamic texture changes). Create empty, then - // upload via updateTexture2D. - bgfx::TextureHandle h = bgfx::createTexture2D( - static_cast(desc.Width), - static_cast(desc.Height), - false, 1, - bgfxFmt, - BGFX_TEXTURE_NONE | BGFX_SAMPLER_MIN_ANISOTROPIC | BGFX_SAMPLER_MAG_ANISOTROPIC, - nullptr); - if (bgfx::isValid(h) && mem != nullptr) - { - bgfx::updateTexture2D(h, 0, 0, 0, 0, - static_cast(desc.Width), - static_cast(desc.Height), - mem); - } - - g_textureCache[tex] = h; - // Record dimensions so future reuse can update in place - g_d3dPtrCache[tex] = { curD3D, static_cast(desc.Width), static_cast(desc.Height) }; - return h; -} -} // close anonymous namespace for Invalidate_Cached_Texture - -void BgfxBackend::Invalidate_Cached_Texture(TextureBaseClass * texture) -{ - if (texture == nullptr) - return; - // Set the D3D pointer to nullptr (sentinel) so the next EnsureBgfxTexture - // call detects a "change" and re-uploads pixel data. KEEP the dimensions - // so the in-place update path can check if the bgfx handle is reusable. - auto d3dIt = g_d3dPtrCache.find(texture); - if (d3dIt != g_d3dPtrCache.end()) - d3dIt->second.ptr = nullptr; -} - -void BgfxBackend::Release_Cached_Texture(TextureBaseClass * texture) -{ - if (texture == nullptr) - return; - // Called from TextureBaseClass::~TextureBaseClass before the D3D8 - // texture is released. Queue the bgfx handle for deferred destruction - // (in-flight draws may still reference it this frame) and erase the - // cache entries so a later allocation reusing this TextureBaseClass* - // address cannot inherit the stale handle. - auto it = g_textureCache.find(texture); - if (it != g_textureCache.end()) - { - if (bgfx::isValid(it->second)) - g_deferredTextureDestroys.push_back(it->second); - g_textureCache.erase(it); - } - g_d3dPtrCache.erase(texture); - g_renderTargetSet.erase(texture); - - // Framebuffer-backed textures (render targets) own a framebuffer whose - // color attachment IS the cached handle above. Destroying the - // framebuffer also destroys its attached textures, so we do that - // immediately and do NOT queue the colorTex for deferred destroy. - auto fbIt = g_framebufferCache.find(texture); - if (fbIt != g_framebufferCache.end()) - { - if (bgfx::isValid(fbIt->second.fb)) - bgfx::destroy(fbIt->second.fb); - g_framebufferCache.erase(fbIt); - } -} namespace { // reopen anonymous namespace -bgfx::ProgramHandle g_currentBgfxProgram = BGFX_INVALID_HANDLE; -uint64_t g_currentBgfxState = 0; // TheSuperHackers @refactor bobtista 15/04/2026 CSM light // transform computation. Called once per frame to build an ortho // projection from a hardcoded sun direction, fitted to a generous // world-space box centered on the tactical view. Stores results in -// g_shadowLightView / g_shadowLightProj (bgfx column-major), then +// g_frame.shadowLightView / g_frame.shadowLightProj (bgfx column-major), then // pushes them to the shadow map view. Not yet tied to the engine's // actual sun direction — hooked up with the CSM work. static void UpdateShadowLightTransform() @@ -1652,10 +1020,10 @@ static void UpdateShadowLightTransform() // that restore TOD without going through W3DTerrainLogic::loadMap). // The D3D light direction points FROM sun TOWARD ground; negate // to get the direction TOWARD the sun (matching the convention - // used by g_shadowSunPosX/Y/Z from setLightPosition). - float sunX = g_shadowSunPosX; - float sunY = g_shadowSunPosY; - float sunZ = g_shadowSunPosZ; + // used by g_frame.shadowSunPosX/Y/Z from setLightPosition). + float sunX = g_frame.shadowSunPosX; + float sunY = g_frame.shadowSunPosY; + float sunZ = g_frame.shadowSunPosZ; { RenderStateStruct rs; DX8Wrapper::Get_Render_State(rs); @@ -1685,21 +1053,21 @@ static void UpdateShadowLightTransform() const float sdz = sunZ / sunLen; // Extract camera position from the captured view matrix. - float viewTx = g_bgfxView[12]; - float viewTy = g_bgfxView[13]; - float viewTz = g_bgfxView[14]; - float camPosX = -(g_bgfxView[0]*viewTx + g_bgfxView[1]*viewTy + g_bgfxView[2]*viewTz); - float camPosY = -(g_bgfxView[4]*viewTx + g_bgfxView[5]*viewTy + g_bgfxView[6]*viewTz); - float camPosZ = -(g_bgfxView[8]*viewTx + g_bgfxView[9]*viewTy + g_bgfxView[10]*viewTz); + float viewTx = g_frame.view[12]; + float viewTy = g_frame.view[13]; + float viewTz = g_frame.view[14]; + float camPosX = -(g_frame.view[0]*viewTx + g_frame.view[1]*viewTy + g_frame.view[2]*viewTz); + float camPosY = -(g_frame.view[4]*viewTx + g_frame.view[5]*viewTy + g_frame.view[6]*viewTz); + float camPosZ = -(g_frame.view[8]*viewTx + g_frame.view[9]*viewTy + g_frame.view[10]*viewTz); // Compute where the camera looks at on the ground plane (Z=0). // The 3rd row of the bgfx view matrix (column-major: indices // 2, 6, 10) is the view-space Z axis in world coordinates. // For this engine (Z-up, DX8 left-handed), it points BACKWARD // (away from the scene), so negate to get FORWARD (toward scene). - float fwdX = -g_bgfxView[2]; - float fwdY = -g_bgfxView[6]; - float fwdZ = -g_bgfxView[10]; + float fwdX = -g_frame.view[2]; + float fwdY = -g_frame.view[6]; + float fwdZ = -g_frame.view[10]; // Ray-plane intersection: t = -camPosZ / fwdZ float atX = camPosX; float atY = camPosY; @@ -1724,7 +1092,7 @@ static void UpdateShadowLightTransform() // (positive Z = forward, near/far > 0). Right-handed (bx default) // flips Z, putting the scene at negative view-space Z which falls // outside the [near, far] clip range → refZ ≈ 1.0 for everything. - bx::mtxLookAt(g_shadowLightView, + bx::mtxLookAt(g_frame.shadowLightView, bx::Vec3(eyeX, eyeY, eyeZ), bx::Vec3(atX, atY, atZ), bx::Vec3(upX, upY, upZ), @@ -1736,7 +1104,7 @@ static void UpdateShadowLightTransform() const float orthoNear = 1.0f; const float orthoFar = kShadowCameraDistance + kShadowOrthoFarMargin; const bgfx::Caps * caps = bgfx::getCaps(); - bx::mtxOrtho(g_shadowLightProj, + bx::mtxOrtho(g_frame.shadowLightProj, -kShadowOrthoSize, +kShadowOrthoSize, -kShadowOrthoSize, +kShadowOrthoSize, orthoNear, orthoFar, @@ -1751,7 +1119,7 @@ static void UpdateShadowLightTransform() // apply the fractional offset back into the projection matrix. { float lightVP[16]; - bx::mtxMul(lightVP, g_shadowLightView, g_shadowLightProj); + bx::mtxMul(lightVP, g_frame.shadowLightView, g_frame.shadowLightProj); // Row-vector: (0,0,0,1) * VP = (VP[12], VP[13], VP[14], VP[15]) // VP[12..13] are the clip-space position of the world origin. const float halfRes = static_cast(kShadowMapResolution) * 0.5f; @@ -1761,19 +1129,19 @@ static void UpdateShadowLightTransform() const float snappedY = std::round(texelY); const float offsetX = (snappedX - texelX) / halfRes; const float offsetY = (snappedY - texelY) / halfRes; - g_shadowLightProj[12] += offsetX; - g_shadowLightProj[13] += offsetY; + g_frame.shadowLightProj[12] += offsetX; + g_frame.shadowLightProj[13] += offsetY; } - bgfx::setViewTransform(kBgfxShadowMapView, g_shadowLightView, g_shadowLightProj); - g_shadowLightCaptured = true; + bgfx::setViewTransform(kBgfxShadowMapView, g_frame.shadowLightView, g_frame.shadowLightProj); + g_frame.shadowLightCaptured = true; } } void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) { - if (g_bgfxInitialized) + if (g_device.initialized) { WWDEBUG_SAY(("[BgfxBackend] Initialize called twice; ignoring.")); return; @@ -1781,35 +1149,35 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) // TheSuperHackers @feature bobtista 16/04/2026 bgfx takes the // main game window; DX8 moves to a secondary popup for reference. - g_bgfxWindow = static_cast(hwnd); - if (g_bgfxWindow == nullptr) + g_device.window = static_cast(hwnd); + if (g_device.window == nullptr) { WWDEBUG_SAY(("[BgfxBackend] hwnd is null. Backend will remain dormant.")); return; } RECT clientRect; - if (GetClientRect(g_bgfxWindow, &clientRect)) + if (GetClientRect(g_device.window, &clientRect)) { - g_bgfxWidth = clientRect.right - clientRect.left; - g_bgfxHeight = clientRect.bottom - clientRect.top; + g_device.width = clientRect.right - clientRect.left; + g_device.height = clientRect.bottom - clientRect.top; } - if (g_bgfxWidth <= 0) + if (g_device.width <= 0) { - g_bgfxWidth = 800; + g_device.width = 800; } - if (g_bgfxHeight <= 0) + if (g_device.height <= 0) { - g_bgfxHeight = 600; + g_device.height = 600; } WWDEBUG_SAY(("[BgfxBackend] Using main game window %p (%dx%d) for bgfx.", - g_bgfxWindow, g_bgfxWidth, g_bgfxHeight)); + g_device.window, g_device.width, g_device.height)); bgfx::renderFrame(); bgfx::PlatformData pd; pd.ndt = nullptr; - pd.nwh = g_bgfxWindow; + pd.nwh = g_device.window; pd.context = nullptr; pd.backBuffer = nullptr; pd.backBufferDS = nullptr; @@ -1818,19 +1186,19 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) bgfx::Init initArgs; initArgs.type = bgfx::RendererType::Count; initArgs.callback = &g_bgfxCallback; - initArgs.resolution.width = static_cast(g_bgfxWidth); - initArgs.resolution.height = static_cast(g_bgfxHeight); + initArgs.resolution.width = static_cast(g_device.width); + initArgs.resolution.height = static_cast(g_device.height); initArgs.resolution.reset = BGFX_RESET_NONE; initArgs.platformData = pd; if (!bgfx::init(initArgs)) { WWDEBUG_SAY(("[BgfxBackend] bgfx::init FAILED. Backend will remain dormant.")); - g_bgfxWindow = nullptr; + g_device.window = nullptr; return; } - g_bgfxInitialized = true; + g_device.initialized = true; // TheSuperHackers @refactor bobtista 16/04/2026 The explicit // bgfx::reset() after init is removed because it triggers a DXGI @@ -1847,8 +1215,8 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) 0); bgfx::setViewFrameBuffer(kBgfxDebugView, BGFX_INVALID_HANDLE); bgfx::setViewRect(kBgfxDebugView, 0, 0, - static_cast(g_bgfxWidth), - static_cast(g_bgfxHeight)); + static_cast(g_device.width), + static_cast(g_device.height)); // View 1 is the engine geometry view. Same render target, but its // own clear/depth and (eventually) its own view+projection matrices @@ -1869,8 +1237,8 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) // Default sort can place decals before terrain → overwritten. bgfx::setViewMode(kBgfxEngineView, bgfx::ViewMode::Sequential); bgfx::setViewRect(kBgfxEngineView, 0, 0, - static_cast(g_bgfxWidth), - static_cast(g_bgfxHeight)); + static_cast(g_device.width), + static_cast(g_device.height)); // TheSuperHackers @refactor bobtista 11/04/2026 sorted // draws view. No clear (reuses view 1's color + depth so sorted @@ -1883,8 +1251,8 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) 1.0f, 0); bgfx::setViewRect(kBgfxEngineSortView, 0, 0, - static_cast(g_bgfxWidth), - static_cast(g_bgfxHeight)); + static_cast(g_device.width), + static_cast(g_device.height)); // Effect overlay view for dazzle draws with NDC-space vertices. // Permanent identity view + identity projection; reuses the @@ -1895,8 +1263,8 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) 1.0f, 0); bgfx::setViewRect(kBgfxEffectOverlayView, 0, 0, - static_cast(g_bgfxWidth), - static_cast(g_bgfxHeight)); + static_cast(g_device.width), + static_cast(g_device.height)); { float identityMtx[16]; IdentityMatrix(identityMtx); @@ -1909,16 +1277,16 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) // dirty-flag logic alongside view 1. bgfx::setViewClear(kBgfxShadowVolumeView, BGFX_CLEAR_NONE, 0, 1.0f, 0); bgfx::setViewRect(kBgfxShadowVolumeView, 0, 0, - static_cast(g_bgfxWidth), - static_cast(g_bgfxHeight)); + static_cast(g_device.width), + static_cast(g_device.height)); bgfx::setViewMode(kBgfxShadowVolumeView, bgfx::ViewMode::Sequential); // shadow darken apply pass. Sequential, identity transforms // (the fullscreen quad is authored in clip space). bgfx::setViewClear(kBgfxShadowApplyView, BGFX_CLEAR_NONE, 0, 1.0f, 0); bgfx::setViewRect(kBgfxShadowApplyView, 0, 0, - static_cast(g_bgfxWidth), - static_cast(g_bgfxHeight)); + static_cast(g_device.width), + static_cast(g_device.height)); bgfx::setViewMode(kBgfxShadowApplyView, bgfx::ViewMode::Sequential); { float identityMtx[16]; @@ -1931,8 +1299,8 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) // the 3D scene. bgfx::setViewClear(kBgfxUIView, BGFX_CLEAR_NONE, 0, 1.0f, 0); bgfx::setViewRect(kBgfxUIView, 0, 0, - static_cast(g_bgfxWidth), - static_cast(g_bgfxHeight)); + static_cast(g_device.width), + static_cast(g_device.height)); bgfx::setViewMode(kBgfxUIView, bgfx::ViewMode::Sequential); { float identityMtx[16]; @@ -1944,11 +1312,11 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) // real values via Set_Transform. This keeps the first few engine // submits well-defined even if they fire before any matrices are // captured. - IdentityMatrix(g_bgfxWorld); - IdentityMatrix(g_bgfxView); - IdentityMatrix(g_bgfxProj); - IdentityMatrix(g_bgfxSortWorld); - g_bgfxViewProjDirty = true; + IdentityMatrix(g_frame.world); + IdentityMatrix(g_frame.view); + IdentityMatrix(g_frame.proj); + IdentityMatrix(g_frame.sortWorld); + g_frame.cameraProjDirty = true; // Sort view gets identity view + current projection. setViewTransform // persists for the life of the bgfx view; we re-apply the projection @@ -1956,14 +1324,14 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) { float identityView[16]; IdentityMatrix(identityView); - bgfx::setViewTransform(kBgfxEngineSortView, identityView, g_bgfxProj); + bgfx::setViewTransform(kBgfxEngineSortView, identityView, g_frame.proj); } // TheSuperHackers @refactor bobtista 11/04/2026 create the // passthrough shader program and vertex layout so End_Scene can submit // a test triangle. If shader creation fails the backend still runs but // the triangle is skipped. - g_triangleLayout + g_device.triangleLayout .begin() .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) .add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true) @@ -1981,7 +1349,7 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) { bgfx::setName(vsHandle, "vs_passthrough"); bgfx::setName(fsHandle, "fs_passthrough"); - g_passthroughProgram = bgfx::createProgram(vsHandle, fsHandle, true); + g_device.passthroughProgram = bgfx::createProgram(vsHandle, fsHandle, true); } else { @@ -1999,33 +1367,33 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) { -1.0f, 1.0f, 0.0f, 0xff000000u }, { 3.0f, 1.0f, 0.0f, 0xff000000u }, }; - g_fullscreenClearVB = bgfx::createVertexBuffer( - bgfx::makeRef(verts, sizeof(verts)), g_triangleLayout); - } - - g_sTex0 = bgfx::createUniform("s_tex0", bgfx::UniformType::Sampler); - g_sTex1 = bgfx::createUniform("s_tex1", bgfx::UniformType::Sampler); - g_sTex2 = bgfx::createUniform("s_tex2", bgfx::UniformType::Sampler); - g_sTex3 = bgfx::createUniform("s_tex3", bgfx::UniformType::Sampler); - g_uMatDiffuse = bgfx::createUniform("u_matDiffuse", bgfx::UniformType::Vec4); - g_uMatEmissive = bgfx::createUniform("u_matEmissive", bgfx::UniformType::Vec4); - g_uAtestParams = bgfx::createUniform("u_atestParams", bgfx::UniformType::Vec4); - g_uTssOps0 = bgfx::createUniform("u_tssOps0", bgfx::UniformType::Vec4); - g_uTssOps1 = bgfx::createUniform("u_tssOps1", bgfx::UniformType::Vec4); - g_uLightDirs = bgfx::createUniform("u_lightDirs", bgfx::UniformType::Vec4, 4); - g_uLightColors = bgfx::createUniform("u_lightColors", bgfx::UniformType::Vec4, 4); - g_uSceneAmbient = bgfx::createUniform("u_sceneAmbient", bgfx::UniformType::Vec4); - g_uLightingEnabled = bgfx::createUniform("u_lightingEnabled", bgfx::UniformType::Vec4); - g_uTexcoordSelect = bgfx::createUniform("u_texcoordSelect", bgfx::UniformType::Vec4); - g_uGrayscaleEnable = bgfx::createUniform("u_grayscaleEnable", bgfx::UniformType::Vec4); - g_uShroudParams = bgfx::createUniform("u_shroudParams", bgfx::UniformType::Vec4); - g_uCloudParams = bgfx::createUniform("u_cloudParams", bgfx::UniformType::Vec4); - g_sCloudMap = bgfx::createUniform("s_cloudMap", bgfx::UniformType::Sampler); + g_device.fullscreenClearVB = bgfx::createVertexBuffer( + bgfx::makeRef(verts, sizeof(verts)), g_device.triangleLayout); + } + + g_uniforms.sTex0 = bgfx::createUniform("s_tex0", bgfx::UniformType::Sampler); + g_uniforms.sTex1 = bgfx::createUniform("s_tex1", bgfx::UniformType::Sampler); + g_uniforms.sTex2 = bgfx::createUniform("s_tex2", bgfx::UniformType::Sampler); + g_uniforms.sTex3 = bgfx::createUniform("s_tex3", bgfx::UniformType::Sampler); + g_uniforms.uMatDiffuse = bgfx::createUniform("u_matDiffuse", bgfx::UniformType::Vec4); + g_uniforms.uMatEmissive = bgfx::createUniform("u_matEmissive", bgfx::UniformType::Vec4); + g_uniforms.uAtestParams = bgfx::createUniform("u_atestParams", bgfx::UniformType::Vec4); + g_uniforms.uTssOps0 = bgfx::createUniform("u_tssOps0", bgfx::UniformType::Vec4); + g_uniforms.uTssOps1 = bgfx::createUniform("u_tssOps1", bgfx::UniformType::Vec4); + g_uniforms.uLightDirs = bgfx::createUniform("u_lightDirs", bgfx::UniformType::Vec4, 4); + g_uniforms.uLightColors = bgfx::createUniform("u_lightColors", bgfx::UniformType::Vec4, 4); + g_uniforms.uSceneAmbient = bgfx::createUniform("u_sceneAmbient", bgfx::UniformType::Vec4); + g_uniforms.uLightingEnabled = bgfx::createUniform("u_lightingEnabled", bgfx::UniformType::Vec4); + g_uniforms.uTexcoordSelect = bgfx::createUniform("u_texcoordSelect", bgfx::UniformType::Vec4); + g_uniforms.uGrayscaleEnable = bgfx::createUniform("u_grayscaleEnable", bgfx::UniformType::Vec4); + g_uniforms.uShroudParams = bgfx::createUniform("u_shroudParams", bgfx::UniformType::Vec4); + g_uniforms.uCloudParams = bgfx::createUniform("u_cloudParams", bgfx::UniformType::Vec4); + g_uniforms.sCloudMap = bgfx::createUniform("s_cloudMap", bgfx::UniformType::Sampler); // Default 1x1 white texture. Used as fallback for missing textures. // Multiplying by white is the identity operation. static const uint8_t kWhitePixel[4] = { 0xff, 0xff, 0xff, 0xff }; - g_defaultWhiteTexture = bgfx::createTexture2D( + g_device.defaultWhiteTexture = bgfx::createTexture2D( 1, 1, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_NONE | BGFX_SAMPLER_POINT, @@ -2035,7 +1403,7 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) // is partially hidden and water ripple particles blend naturally. // RGBA: (30, 50, 70, 180) = dark blue-grey, ~70% opaque. static const uint8_t kWaterPixel[4] = { 0x1e, 0x32, 0x46, 0xb4 }; - g_defaultTransparentTexture = bgfx::createTexture2D( + g_device.defaultTransparentTexture = bgfx::createTexture2D( 1, 1, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_NONE | BGFX_SAMPLER_POINT, @@ -2050,7 +1418,7 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) { bgfx::setName(vsUber, "vs_uber"); bgfx::setName(fsUber, "fs_uber"); - g_uberProgram = bgfx::createProgram(vsUber, fsUber, true); + g_device.uberProgram = bgfx::createProgram(vsUber, fsUber, true); } else { @@ -2067,7 +1435,7 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) if (bgfx::isValid(vsTrees) && bgfx::isValid(fsUber2)) { bgfx::setName(vsTrees, "vs_trees"); - g_treeProgram = bgfx::createProgram(vsTrees, fsUber2, true); + g_device.treeProgram = bgfx::createProgram(vsTrees, fsUber2, true); } else { @@ -2084,7 +1452,7 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) { bgfx::setName(vsShadow, "vs_shadow_volume"); bgfx::setName(fsShadow, "fs_shadow_volume"); - g_shadowVolumeProgram = bgfx::createProgram(vsShadow, fsShadow, true); + g_device.shadowVolumeProgram = bgfx::createProgram(vsShadow, fsShadow, true); } else { @@ -2102,7 +1470,7 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) { bgfx::setName(vsApply, "vs_shadow_apply"); bgfx::setName(fsApply, "fs_shadow_apply"); - g_shadowApplyProgram = bgfx::createProgram(vsApply, fsApply, true); + g_device.shadowApplyProgram = bgfx::createProgram(vsApply, fsApply, true); } else { @@ -2110,8 +1478,8 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) if (bgfx::isValid(fsApply)) bgfx::destroy(fsApply); WWDEBUG_SAY(("[BgfxBackend] shadow apply shader createShader FAILED.")); } - g_uShadowColor = bgfx::createUniform("u_shadowColor", bgfx::UniformType::Vec4); - g_uShadowBias = bgfx::createUniform("u_shadowBias", bgfx::UniformType::Vec4); + g_uniforms.uShadowColor = bgfx::createUniform("u_shadowColor", bgfx::UniformType::Vec4); + g_uniforms.uShadowBias = bgfx::createUniform("u_shadowBias", bgfx::UniformType::Vec4); // CSM caster program + shadow map render target. const bgfx::Memory * vsCasterMem = bgfx::makeRef(vs_shadow_caster_dx11, sizeof(vs_shadow_caster_dx11)); @@ -2122,7 +1490,7 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) { bgfx::setName(vsCaster, "vs_shadow_caster"); bgfx::setName(fsCaster, "fs_shadow_caster"); - g_shadowCasterProgram = bgfx::createProgram(vsCaster, fsCaster, true); + g_device.shadowCasterProgram = bgfx::createProgram(vsCaster, fsCaster, true); } else { @@ -2140,13 +1508,13 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) | BGFX_SAMPLER_COMPARE_LEQUAL | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP; - g_shadowMapDepth = bgfx::createTexture2D( + g_device.shadowMapDepth = bgfx::createTexture2D( kShadowMapResolution, kShadowMapResolution, false, 1, bgfx::TextureFormat::D32F, depthFlags); - if (bgfx::isValid(g_shadowMapDepth)) + if (bgfx::isValid(g_device.shadowMapDepth)) { - bgfx::setName(g_shadowMapDepth, "shadowMapD32F"); - g_shadowMapFB = bgfx::createFrameBuffer(1, &g_shadowMapDepth, true); + bgfx::setName(g_device.shadowMapDepth, "shadowMapD32F"); + g_device.shadowMapFB = bgfx::createFrameBuffer(1, &g_device.shadowMapDepth, true); // Clear both color (R32F=1.0 = far plane) and depth for the // caster pass's own depth test. 0x3f800000 = float 1.0 as RGBA. bgfx::setViewClear(kBgfxShadowMapView, @@ -2154,11 +1522,11 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) 0xffffffffu, // RGBA all bits set ≈ max depth 1.0f, 0); bgfx::setViewRect(kBgfxShadowMapView, 0, 0, kShadowMapResolution, kShadowMapResolution); - bgfx::setViewFrameBuffer(kBgfxShadowMapView, g_shadowMapFB); + bgfx::setViewFrameBuffer(kBgfxShadowMapView, g_device.shadowMapFB); bgfx::setViewMode(kBgfxShadowMapView, bgfx::ViewMode::Sequential); WWDEBUG_SAY(("[CSM] shadow map FB=%u tex=%u valid=%d", - g_shadowMapFB.idx, g_shadowMapDepth.idx, - bgfx::isValid(g_shadowMapFB) ? 1 : 0)); + g_device.shadowMapFB.idx, g_device.shadowMapDepth.idx, + bgfx::isValid(g_device.shadowMapFB) ? 1 : 0)); // View ORDER: shadow map (view 8) MUST render before // the engine view (view 1) samples it in fs_uber. By default @@ -2187,24 +1555,24 @@ void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) WWDEBUG_SAY(("[BgfxBackend] CSM shadow map texture creation FAILED.")); } - g_uShadowLightViewProj = bgfx::createUniform("u_shadowLightViewProj", + g_uniforms.uShadowLightViewProj = bgfx::createUniform("u_shadowLightViewProj", bgfx::UniformType::Mat4); - g_sShadowMap = bgfx::createUniform("s_shadowMap", + g_uniforms.sShadowMap = bgfx::createUniform("s_shadowMap", bgfx::UniformType::Sampler); - g_uSwayTable = bgfx::createUniform("u_swayTable", bgfx::UniformType::Vec4, kSwayTableEntries); - g_uShroudOffset = bgfx::createUniform("u_shroudOffset", bgfx::UniformType::Vec4); - g_uShroudScale = bgfx::createUniform("u_shroudScale", bgfx::UniformType::Vec4); + g_uniforms.uSwayTable = bgfx::createUniform("u_swayTable", bgfx::UniformType::Vec4, kSwayTableEntries); + g_uniforms.uShroudOffset = bgfx::createUniform("u_shroudOffset", bgfx::UniformType::Vec4); + g_uniforms.uShroudScale = bgfx::createUniform("u_shroudScale", bgfx::UniformType::Vec4); const bgfx::RendererType::Enum selected = bgfx::getRendererType(); const char * rendererName = bgfx::getRendererName(selected); const bgfx::Caps * caps = bgfx::getCaps(); WWDEBUG_SAY(("[BgfxBackend] bgfx::init OK on main window " "(renderer=%s, %dx%d, hwnd=%p, passthrough=%s, uber=%s).", - rendererName, g_bgfxWidth, g_bgfxHeight, - g_bgfxWindow, - bgfx::isValid(g_passthroughProgram) ? "ok" : "FAILED", - bgfx::isValid(g_uberProgram) ? "ok" : "FAILED")); + rendererName, g_device.width, g_device.height, + g_device.window, + bgfx::isValid(g_device.passthroughProgram) ? "ok" : "FAILED", + bgfx::isValid(g_device.uberProgram) ? "ok" : "FAILED")); // Log whether RGBA8 is supported as a render target (needed for // DESTALPHA water technique — back buffer must have alpha channel). const bool rgba8Supported = (caps->formats[bgfx::TextureFormat::RGBA8] & @@ -2234,96 +1602,105 @@ static void DestroyBgfxHandle(H & h) void BgfxBackend::Shutdown() { - if (g_bgfxInitialized) - { - DestroyBgfxHandle(g_passthroughProgram); - DestroyBgfxHandle(g_fullscreenClearVB); - DestroyBgfxHandle(g_uberProgram); - DestroyBgfxHandle(g_treeProgram); - DestroyBgfxHandle(g_uSwayTable); - DestroyBgfxHandle(g_uShroudOffset); - DestroyBgfxHandle(g_uShroudScale); - DestroyBgfxHandle(g_sTex0); - DestroyBgfxHandle(g_sTex1); - DestroyBgfxHandle(g_sTex2); - DestroyBgfxHandle(g_sTex3); - DestroyBgfxHandle(g_uMatDiffuse); - DestroyBgfxHandle(g_uAtestParams); - DestroyBgfxHandle(g_uTssOps0); - DestroyBgfxHandle(g_uTssOps1); - DestroyBgfxHandle(g_uLightDirs); - DestroyBgfxHandle(g_uLightColors); - DestroyBgfxHandle(g_uSceneAmbient); - DestroyBgfxHandle(g_uLightingEnabled); - DestroyBgfxHandle(g_uTexcoordSelect); - DestroyBgfxHandle(g_uShroudParams); - DestroyBgfxHandle(g_uCloudParams); - DestroyBgfxHandle(g_sCloudMap); - DestroyBgfxHandle(g_shadowVolumeProgram); - DestroyBgfxHandle(g_shadowApplyProgram); - DestroyBgfxHandle(g_shadowCasterProgram); - DestroyBgfxHandle(g_shadowMapFB); - DestroyBgfxHandle(g_shadowMapDepth); - DestroyBgfxHandle(g_uShadowColor); - DestroyBgfxHandle(g_uShadowBias); - DestroyBgfxHandle(g_uShadowLightViewProj); - DestroyBgfxHandle(g_sShadowMap); - DestroyBgfxHandle(g_defaultWhiteTexture); - DestroyBgfxHandle(g_defaultTransparentTexture); - g_renderTargetSet.clear(); - for (auto & kv : g_framebufferCache) + if (g_device.initialized) + { + DestroyBgfxHandle(g_device.passthroughProgram); + DestroyBgfxHandle(g_device.fullscreenClearVB); + DestroyBgfxHandle(g_device.uberProgram); + DestroyBgfxHandle(g_device.treeProgram); + DestroyBgfxHandle(g_uniforms.uSwayTable); + DestroyBgfxHandle(g_uniforms.uShroudOffset); + DestroyBgfxHandle(g_uniforms.uShroudScale); + DestroyBgfxHandle(g_uniforms.sTex0); + DestroyBgfxHandle(g_uniforms.sTex1); + DestroyBgfxHandle(g_uniforms.sTex2); + DestroyBgfxHandle(g_uniforms.sTex3); + DestroyBgfxHandle(g_uniforms.uMatDiffuse); + DestroyBgfxHandle(g_uniforms.uAtestParams); + DestroyBgfxHandle(g_uniforms.uTssOps0); + DestroyBgfxHandle(g_uniforms.uTssOps1); + DestroyBgfxHandle(g_uniforms.uLightDirs); + DestroyBgfxHandle(g_uniforms.uLightColors); + DestroyBgfxHandle(g_uniforms.uSceneAmbient); + DestroyBgfxHandle(g_uniforms.uLightingEnabled); + DestroyBgfxHandle(g_uniforms.uTexcoordSelect); + DestroyBgfxHandle(g_uniforms.uShroudParams); + DestroyBgfxHandle(g_uniforms.uCloudParams); + DestroyBgfxHandle(g_uniforms.sCloudMap); + DestroyBgfxHandle(g_device.shadowVolumeProgram); + DestroyBgfxHandle(g_device.shadowApplyProgram); + DestroyBgfxHandle(g_device.shadowCasterProgram); + DestroyBgfxHandle(g_device.shadowMapFB); + DestroyBgfxHandle(g_device.shadowMapDepth); + DestroyBgfxHandle(g_uniforms.uShadowColor); + DestroyBgfxHandle(g_uniforms.uShadowBias); + DestroyBgfxHandle(g_uniforms.uShadowLightViewProj); + DestroyBgfxHandle(g_uniforms.sShadowMap); + DestroyBgfxHandle(g_uniforms.uMatEmissive); + DestroyBgfxHandle(g_uniforms.uGrayscaleEnable); + DestroyBgfxHandle(g_device.defaultWhiteTexture); + DestroyBgfxHandle(g_device.defaultTransparentTexture); + g_caches.renderTarget.clear(); + for (auto & kv : g_caches.framebuffer) { if (bgfx::isValid(kv.second.fb)) { bgfx::destroy(kv.second.fb); } } - g_framebufferCache.clear(); + g_caches.framebuffer.clear(); // cached engine buffers. Destroy before bgfx::shutdown // so the handles outlive nothing. - for (auto & kv : g_vbCache) + for (auto & kv : g_caches.vb) { if (bgfx::isValid(kv.second.handle)) { bgfx::destroy(kv.second.handle); } } - g_vbCache.clear(); - for (auto & kv : g_ibCache) + g_caches.vb.clear(); + for (auto & kv : g_caches.ib) { if (bgfx::isValid(kv.second.handle)) { bgfx::destroy(kv.second.handle); } } - g_ibCache.clear(); - for (auto & kv : g_textureCache) + g_caches.ib.clear(); + for (auto & kv : g_caches.texture) { if (bgfx::isValid(kv.second)) { bgfx::destroy(kv.second); } } - g_textureCache.clear(); - g_currentBgfxVB = BGFX_INVALID_HANDLE; - g_currentBgfxIB = BGFX_INVALID_HANDLE; - g_currentBgfxTexture0 = BGFX_INVALID_HANDLE; - g_currentBgfxTexture1 = BGFX_INVALID_HANDLE; - g_currentBgfxTexture2 = BGFX_INVALID_HANDLE; - g_currentBgfxTexture3 = BGFX_INVALID_HANDLE; - g_currentIBOffset = 0; - g_currentUseTransientVB = false; - g_currentUseTransientIB = false; - g_pendingDynVB.valid = false; - g_pendingDynIB.valid = false; + g_caches.texture.clear(); + g_draw.vb = BGFX_INVALID_HANDLE; + g_draw.ib = BGFX_INVALID_HANDLE; + g_draw.tex[0] = BGFX_INVALID_HANDLE; + g_draw.tex[1] = BGFX_INVALID_HANDLE; + g_draw.tex[2] = BGFX_INVALID_HANDLE; + g_draw.tex[3] = BGFX_INVALID_HANDLE; + g_draw.ibOffset = 0; + g_draw.useTransientVB = false; + g_draw.useTransientIB = false; + g_draw.pendingVB.valid = false; + g_draw.pendingIB.valid = false; + // Flush both deferred-destroy queues — bgfx::shutdown() tolerates stale handles but strict debug builds may assert. + for (auto & h : g_caches.deferredDestroys) + if (bgfx::isValid(h)) bgfx::destroy(h); + for (auto & h : g_caches.deferredDestroysPrev) + if (bgfx::isValid(h)) bgfx::destroy(h); + g_caches.deferredDestroys.clear(); + g_caches.deferredDestroysPrev.clear(); bgfx::shutdown(); - g_bgfxInitialized = false; + g_device.initialized = false; WWDEBUG_SAY(("[BgfxBackend] bgfx::shutdown complete.")); } // The bgfx window is the main game window, do not destroy it. // DX8's secondary reference window is owned by DX8Wrapper. - g_bgfxWindow = nullptr; + g_device.window = nullptr; } // Device state queries (Is_Device_Lost / Has_Stencil / Get_Back_Buffer_Format @@ -2337,7 +1714,7 @@ void BgfxBackend::Set_Viewport(const RenderBackendViewport & viewport) // FROM DX8Wrapper::Set_Viewport, so the D3D8 viewport is already set. // Calling the base class would cause infinite recursion. - if (!g_bgfxInitialized) + if (!g_device.initialized) return; // TheSuperHackers @fix bobtista 19/04/2026 Sync bgfx view rects with the @@ -2365,8 +1742,8 @@ void BgfxBackend::Set_Viewport(const RenderBackendViewport & viewport) // tactical rect set by CameraClass::Apply. const bool isFullCanvas = (x == 0 && y == 0 && - static_cast(w) == g_bgfxWidth && - static_cast(h) == g_bgfxHeight); + static_cast(w) == g_device.width && + static_cast(h) == g_device.height); if (isFullCanvas) { return; @@ -2385,7 +1762,7 @@ void BgfxBackend::Set_Viewport(const RenderBackendViewport & viewport) void BgfxBackend::Begin_Scene() { - if (!g_bgfxInitialized) + if (!g_device.initialized) { return; } @@ -2393,12 +1770,12 @@ void BgfxBackend::Begin_Scene() // Destroy PREVIOUS frame's deferred textures. These were queued in // frame N-1, survived through bgfx::frame() at End_Scene of frame N-1, // so all in-flight draws referencing them are guaranteed complete. - for (auto & h : g_deferredTextureDestroysPrev) + for (auto & h : g_caches.deferredDestroysPrev) { if (bgfx::isValid(h)) bgfx::destroy(h); } - g_deferredTextureDestroysPrev.clear(); + g_caches.deferredDestroysPrev.clear(); // Show the DX8 reference popup after a few frames, giving the game's // input system time to fully initialize. Showing too early steals focus // and permanently blocks mouse capture. @@ -2407,7 +1784,7 @@ void BgfxBackend::Begin_Scene() if (s_dx8RefFrameCount >= 0) { s_dx8RefFrameCount++; - if (s_dx8RefFrameCount > 30) + if (s_dx8RefFrameCount > kDX8RefWindowShowDelayFrames) { s_dx8RefFrameCount = -1; HWND dx8Hwnd = FindWindowW(L"GGC_DX8RefWindow", nullptr); @@ -2418,10 +1795,10 @@ void BgfxBackend::Begin_Scene() // Re-assert focus on the main game window. Use SetFocus // instead of SetForegroundWindow which has restrictions // on Windows that can cause it to silently fail. - if (g_bgfxWindow) + if (g_device.window) { - SetForegroundWindow(g_bgfxWindow); - SetFocus(g_bgfxWindow); + SetForegroundWindow(g_device.window); + SetFocus(g_device.window); } } } @@ -2430,20 +1807,20 @@ void BgfxBackend::Begin_Scene() // Check if the game window was resized (e.g., by Set_Render_Device) and // update bgfx's swapchain to match. Without this, bgfx renders at the // old resolution while the game expects the new one. - if (g_bgfxWindow) + if (g_device.window) { RECT cr; - if (GetClientRect(g_bgfxWindow, &cr)) + if (GetClientRect(g_device.window, &cr)) { int w = cr.right - cr.left; int h = cr.bottom - cr.top; - if (w > 0 && h > 0 && (w != g_bgfxWidth || h != g_bgfxHeight)) + if (w > 0 && h > 0 && (w != g_device.width || h != g_device.height)) { WWDEBUG_SAY(("[BgfxBackend] Window resized %dx%d -> %dx%d, calling bgfx::reset.", - g_bgfxWidth, g_bgfxHeight, w, h)); - g_bgfxWidth = w; - g_bgfxHeight = h; - bgfx::reset(g_bgfxWidth, g_bgfxHeight, BGFX_RESET_NONE); + g_device.width, g_device.height, w, h)); + g_device.width = w; + g_device.height = h; + bgfx::reset(g_device.width, g_device.height, BGFX_RESET_NONE); } } } @@ -2453,18 +1830,18 @@ void BgfxBackend::Begin_Scene() // backbuffer. Without this, touch-only activation leaves the backbuffer // with stale content from prior frames — visible as flickering yellow // strips from the chat button animation leaking under the control bar. - if (bgfx::isValid(g_passthroughProgram) && bgfx::isValid(g_fullscreenClearVB)) + if (bgfx::isValid(g_device.passthroughProgram) && bgfx::isValid(g_device.fullscreenClearVB)) { float identity[16]; IdentityMatrix(identity); bgfx::setViewRect(kBgfxDebugView, 0, 0, - static_cast(g_bgfxWidth), - static_cast(g_bgfxHeight)); + static_cast(g_device.width), + static_cast(g_device.height)); bgfx::setViewTransform(kBgfxDebugView, identity, identity); - bgfx::setVertexBuffer(0, g_fullscreenClearVB); + bgfx::setVertexBuffer(0, g_device.fullscreenClearVB); bgfx::setState(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_DEPTH_TEST_ALWAYS); - bgfx::submit(kBgfxDebugView, g_passthroughProgram); + bgfx::submit(kBgfxDebugView, g_device.passthroughProgram); } else { @@ -2479,19 +1856,19 @@ void BgfxBackend::Begin_Scene() bgfx::touch(kBgfxShadowMapView); bgfx::touch(kBgfxUIView); // CSM: light transform updated lazily at first draw - // (see SubmitEngineDraw), not here, because g_bgfxView is still + // (see SubmitEngineDraw), not here, because g_frame.view is still // identity at Begin_Scene time. - g_shadowLightCaptured = false; - g_2DOverlayActive = false; - bgfx::setViewRect(kBgfxWaterView, 0, 0, g_bgfxWidth, g_bgfxHeight); + g_frame.shadowLightCaptured = false; + g_views.overlay2DActive = false; + bgfx::setViewRect(kBgfxWaterView, 0, 0, g_device.width, g_device.height); // No clear on water view — it composites over the opaque scene. bgfx::setViewClear(kBgfxWaterView, BGFX_CLEAR_NONE, 0, 1.0f, 0); - g_renderToTexture = false; + g_views.renderToTexture = false; } void BgfxBackend::End_Scene(bool /*flip_frame*/) { - if (!g_bgfxInitialized) + if (!g_device.initialized) { return; } @@ -2502,19 +1879,19 @@ void BgfxBackend::End_Scene(bool /*flip_frame*/) // would stomp earlier draws. We re-apply the camera projection that // was active at the first opaque draw (view 1) and at sort-flush // time (view 2). - if (g_bgfxCameraCaptured) + if (g_frame.cameraCaptured) { - bgfx::setViewTransform(kBgfxEngineView, g_bgfxCameraView, g_bgfxCameraProj); - bgfx::setViewTransform(kBgfxWaterView, g_bgfxCameraView, g_bgfxCameraProj); - bgfx::setViewTransform(kBgfxShadowVolumeView, g_bgfxCameraView, g_bgfxCameraProj); - g_bgfxCameraCaptured = false; + bgfx::setViewTransform(kBgfxEngineView, g_frame.cameraView, g_frame.cameraProj); + bgfx::setViewTransform(kBgfxWaterView, g_frame.cameraView, g_frame.cameraProj); + bgfx::setViewTransform(kBgfxShadowVolumeView, g_frame.cameraView, g_frame.cameraProj); + g_frame.cameraCaptured = false; } - if (g_bgfxSortProjCaptured) + if (g_frame.sortProjCaptured) { float identityView[16]; IdentityMatrix(identityView); - bgfx::setViewTransform(kBgfxEngineSortView, identityView, g_bgfxSortProj); - g_bgfxSortProjCaptured = false; + bgfx::setViewTransform(kBgfxEngineSortView, identityView, g_frame.sortProj); + g_frame.sortProjCaptured = false; } // Push identity transforms and current rect to the UI view @@ -2525,8 +1902,8 @@ void BgfxBackend::End_Scene(bool /*flip_frame*/) bgfx::setViewTransform(kBgfxUIView, identityMtx, identityMtx); } bgfx::setViewRect(kBgfxUIView, 0, 0, - static_cast(g_bgfxWidth), - static_cast(g_bgfxHeight)); + static_cast(g_device.width), + static_cast(g_device.height)); // The debug view (0) runs FIRST to emit the backbuffer // clear quad, then shadow map (view 8) so its depth texture is @@ -2557,17 +1934,17 @@ void BgfxBackend::End_Scene(bool /*flip_frame*/) // Rotate deferred texture destroy buffers. Current frame's deferred // handles move to "prev" — they'll be destroyed at the NEXT Begin_Scene // after one more bgfx::frame() guarantees all references are gone. - g_deferredTextureDestroysPrev.insert(g_deferredTextureDestroysPrev.end(), - g_deferredTextureDestroys.begin(), g_deferredTextureDestroys.end()); - g_deferredTextureDestroys.clear(); + g_caches.deferredDestroysPrev.insert(g_caches.deferredDestroysPrev.end(), + g_caches.deferredDestroys.begin(), g_caches.deferredDestroys.end()); + g_caches.deferredDestroys.clear(); // Transient buffers are freed at bgfx::frame time. Invalidate the // pending and current slots so nothing next frame tries to reuse // a dead handle. - g_pendingDynVB.valid = false; - g_pendingDynIB.valid = false; - g_currentUseTransientVB = false; - g_currentUseTransientIB = false; + g_draw.pendingVB.valid = false; + g_draw.pendingIB.valid = false; + g_draw.useTransientVB = false; + g_draw.useTransientIB = false; } // Flip_To_Primary, Clear, Set_Viewport are inherited from DX8Backend. @@ -2583,20 +1960,20 @@ void BgfxBackend::Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int s // there. Engine VBs that have not been written via the WriteLockClass // path yet (e.g. those filled by raw d3d8 calls) will miss the cache // and the bgfx submit will be skipped. - g_currentUseTransientVB = false; - auto it = g_vbCache.find(vb); - if (it != g_vbCache.end()) + g_draw.useTransientVB = false; + auto it = g_caches.vb.find(vb); + if (it != g_caches.vb.end()) { - g_currentBgfxVB = it->second.handle; + g_draw.vb = it->second.handle; } else { - g_currentBgfxVB = BGFX_INVALID_HANDLE; + g_draw.vb = BGFX_INVALID_HANDLE; // On-demand capture for static VBs that were never captured // via WriteLockClass (e.g. rotor meshes loaded at startup). // Lock the D3D VB, copy the data into a bgfx dynamic VB, and // cache it for future use. - if (vb != nullptr && g_bgfxInitialized + if (vb != nullptr && g_device.initialized && (vb->Type() == BUFFER_TYPE_DX8)) { DX8VertexBufferClass * dx8vb = @@ -2615,10 +1992,10 @@ void BgfxBackend::Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int s Capture_Vertex_Data(vb, data, bytes); d3dvb->Unlock(); // Re-lookup after capture - auto it2 = g_vbCache.find(vb); - if (it2 != g_vbCache.end()) + auto it2 = g_caches.vb.find(vb); + if (it2 != g_caches.vb.end()) { - g_currentBgfxVB = it2->second.handle; + g_draw.vb = it2->second.handle; } } } @@ -2633,16 +2010,16 @@ void BgfxBackend::Set_Vertex_Buffer(const DynamicVBAccessClass & vba) // If the matching Capture_Dynamic_Vertex_Data already // allocated a transient VB for this access class, claim it for the // next draw. Otherwise miss the cache and skip the bgfx submit. - if (g_pendingDynVB.valid && g_pendingDynVB.owner == &vba) + if (g_draw.pendingVB.valid && g_draw.pendingVB.owner == &vba) { - g_currentUseTransientVB = true; - g_currentTransientVB = g_pendingDynVB.tvb; - g_pendingDynVB.valid = false; + g_draw.useTransientVB = true; + g_draw.transientVB = g_draw.pendingVB.tvb; + g_draw.pendingVB.valid = false; } else { - g_currentUseTransientVB = false; - g_currentBgfxVB = BGFX_INVALID_HANDLE; + g_draw.useTransientVB = false; + g_draw.vb = BGFX_INVALID_HANDLE; } } @@ -2650,17 +2027,17 @@ void BgfxBackend::Set_Index_Buffer(const IndexBufferClass * ib, unsigned short i { DX8Backend::Set_Index_Buffer(ib, index_base_offset); - g_currentUseTransientIB = false; - auto it = g_ibCache.find(ib); - if (it != g_ibCache.end()) + g_draw.useTransientIB = false; + auto it = g_caches.ib.find(ib); + if (it != g_caches.ib.end()) { - g_currentBgfxIB = it->second.handle; + g_draw.ib = it->second.handle; } else { - g_currentBgfxIB = BGFX_INVALID_HANDLE; + g_draw.ib = BGFX_INVALID_HANDLE; // On-demand capture for static IBs not yet in cache. - if (ib != nullptr && g_bgfxInitialized + if (ib != nullptr && g_device.initialized && (ib->Type() == BUFFER_TYPE_DX8)) { DX8IndexBufferClass * dx8ib = @@ -2677,34 +2054,34 @@ void BgfxBackend::Set_Index_Buffer(const IndexBufferClass * ib, unsigned short i { Capture_Index_Data(ib, data, bytes); d3dib->Unlock(); - auto it2 = g_ibCache.find(ib); - if (it2 != g_ibCache.end()) + auto it2 = g_caches.ib.find(ib); + if (it2 != g_caches.ib.end()) { - g_currentBgfxIB = it2->second.handle; + g_draw.ib = it2->second.handle; } } } } } - g_currentIBOffset = index_base_offset; + g_draw.ibOffset = index_base_offset; } void BgfxBackend::Set_Index_Buffer(const DynamicIBAccessClass & iba, unsigned short index_base_offset) { DX8Backend::Set_Index_Buffer(iba, index_base_offset); - if (g_pendingDynIB.valid && g_pendingDynIB.owner == &iba) + if (g_draw.pendingIB.valid && g_draw.pendingIB.owner == &iba) { - g_currentUseTransientIB = true; - g_currentTransientIB = g_pendingDynIB.tib; - g_pendingDynIB.valid = false; + g_draw.useTransientIB = true; + g_draw.transientIB = g_draw.pendingIB.tib; + g_draw.pendingIB.valid = false; } else { - g_currentUseTransientIB = false; - g_currentBgfxIB = BGFX_INVALID_HANDLE; + g_draw.useTransientIB = false; + g_draw.ib = BGFX_INVALID_HANDLE; } - g_currentIBOffset = index_base_offset; + g_draw.ibOffset = index_base_offset; } // TheSuperHackers @refactor bobtista 11/04/2026 override @@ -2719,7 +2096,7 @@ void BgfxBackend::Set_Index_Buffer(const DynamicIBAccessClass & iba, unsigned sh void BgfxBackend::Set_Index_Buffer_Index_Offset(unsigned int offset) { DX8Backend::Set_Index_Buffer_Index_Offset(offset); - g_currentIBOffset = static_cast(offset); + g_draw.ibOffset = static_cast(offset); } // -- write-side capture ------------------------------------------- @@ -2749,8 +2126,8 @@ bgfx::DynamicVertexBufferHandle EnsureDynamicVertexBuffer(const VertexBufferClas const uint32_t num_verts = static_cast(vb->Get_Vertex_Count()); const uint32_t engine_stride = vb->FVF_Info().Get_FVF_Size(); - auto it = g_vbCache.find(vb); - if (it != g_vbCache.end()) + auto it = g_caches.vb.find(vb); + if (it != g_caches.vb.end()) { // Stale pointer reuse detection: engine can destroy a VB and // allocate a new one at the same address with different @@ -2763,7 +2140,7 @@ bgfx::DynamicVertexBufferHandle EnsureDynamicVertexBuffer(const VertexBufferClas { bgfx::destroy(it->second.handle); } - g_vbCache.erase(it); + g_caches.vb.erase(it); } if (num_verts == 0) @@ -2783,17 +2160,22 @@ bgfx::DynamicVertexBufferHandle EnsureDynamicVertexBuffer(const VertexBufferClas const uint32_t layout_stride = layout.getStride(); if (layout_stride == 0 || layout_stride != engine_stride) { - WWDEBUG_SAY(("[BgfxBackend] skip VB cache: layout stride=%u != " - "engine stride=%u (fvf=0x%x num_verts=%u) - unsupported FVF", - layout_stride, engine_stride, - vb->FVF_Info().Get_FVF(), num_verts)); + static bool s_loggedVbStrideSkip = false; + if (!s_loggedVbStrideSkip) + { + s_loggedVbStrideSkip = true; + WWDEBUG_SAY(("[BgfxBackend] skip VB cache: layout stride=%u != " + "engine stride=%u (fvf=0x%x num_verts=%u) - unsupported FVF", + layout_stride, engine_stride, + vb->FVF_Info().Get_FVF(), num_verts)); + } BgfxVbCacheEntry e{ BGFX_INVALID_HANDLE, num_verts, engine_stride }; - g_vbCache[vb] = e; + g_caches.vb[vb] = e; return BGFX_INVALID_HANDLE; } bgfx::DynamicVertexBufferHandle h = bgfx::createDynamicVertexBuffer(num_verts, layout); BgfxVbCacheEntry e{ h, num_verts, engine_stride }; - g_vbCache[vb] = e; + g_caches.vb[vb] = e; return h; } @@ -2801,8 +2183,8 @@ bgfx::DynamicIndexBufferHandle EnsureDynamicIndexBuffer(const IndexBufferClass * { const uint32_t num_indices = static_cast(ib->Get_Index_Count()); - auto it = g_ibCache.find(ib); - if (it != g_ibCache.end()) + auto it = g_caches.ib.find(ib); + if (it != g_caches.ib.end()) { if (it->second.num_indices == num_indices && bgfx::isValid(it->second.handle)) { @@ -2812,7 +2194,7 @@ bgfx::DynamicIndexBufferHandle EnsureDynamicIndexBuffer(const IndexBufferClass * { bgfx::destroy(it->second.handle); } - g_ibCache.erase(it); + g_caches.ib.erase(it); } if (num_indices == 0) { @@ -2820,7 +2202,7 @@ bgfx::DynamicIndexBufferHandle EnsureDynamicIndexBuffer(const IndexBufferClass * } bgfx::DynamicIndexBufferHandle h = bgfx::createDynamicIndexBuffer(num_indices); BgfxIbCacheEntry e{ h, num_indices }; - g_ibCache[ib] = e; + g_caches.ib[ib] = e; return h; } } @@ -2830,120 +2212,15 @@ bgfx::DynamicIndexBufferHandle EnsureDynamicIndexBuffer(const IndexBufferClass * // texture-upload path in EnsureBgfxTexture skips (cannot lock). Instead, // the shroud system pushes its system-memory pixel data here every frame // after CopyRects. We create a bgfx texture on first call and updateTexture2D -// on subsequent frames, storing the handle in g_textureCache keyed by the +// on subsequent frames, storing the handle in g_caches.texture keyed by the // engine's destination TextureClass so EnsureBgfxTexture finds it on lookup // before reaching the POOL_DEFAULT early-out. -void BgfxBackend::Capture_Shroud_Texture(TextureClass * dst_texture, - const void * pixel_data, - unsigned dst_width, - unsigned dst_height, - unsigned src_width, - unsigned src_height, - unsigned dst_x, - unsigned dst_y, - unsigned pitch, - WW3DFormat format) -{ - if (!g_bgfxInitialized || dst_texture == nullptr || pixel_data == nullptr) - { - return; - } - - const bgfx::TextureFormat::Enum bgfxFmt = TranslateWW3DFormat(format); - if (bgfxFmt == bgfx::TextureFormat::Unknown) - { - return; - } - - const unsigned bpp = (bgfxFmt == bgfx::TextureFormat::BGRA4 || - bgfxFmt == bgfx::TextureFormat::R5G6B5) ? 2 : 4; - - // TheSuperHackers @bugfix bobtista 17/04/2026 invalidate stale cache - // entries when the shroud texture pointer changes (save/load destroys - // and recreates m_pDstTexture). Without this, the old bgfx handle - // stays in g_textureCache keyed by the freed pointer, and if the - // address is reused, updateTexture2D crashes with mismatched dimensions. - static TextureClass * s_lastShroudDst = nullptr; - static unsigned s_lastShroudW = 0; - static unsigned s_lastShroudH = 0; - if (dst_texture != s_lastShroudDst - || dst_width != s_lastShroudW - || dst_height != s_lastShroudH) - { - if (s_lastShroudDst != nullptr) - { - auto oldIt = g_textureCache.find(s_lastShroudDst); - if (oldIt != g_textureCache.end()) - { - if (bgfx::isValid(oldIt->second)) - bgfx::destroy(oldIt->second); - g_textureCache.erase(oldIt); - } - g_renderTargetSet.erase(s_lastShroudDst); - } - s_lastShroudDst = dst_texture; - s_lastShroudW = dst_width; - s_lastShroudH = dst_height; - } - - auto it = g_textureCache.find(dst_texture); - if (it == g_textureCache.end() || !bgfx::isValid(it->second)) - { - bgfx::TextureHandle h = bgfx::createTexture2D( - static_cast(dst_width), - static_cast(dst_height), - false, 1, - bgfxFmt, - BGFX_TEXTURE_NONE | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); - g_textureCache[dst_texture] = h; - - WWDEBUG_SAY(("[BgfxBackend] Shroud texture created: dst=%ux%u src=%ux%u " - "fmt=%d bgfxFmt=%d bpp=%u off=(%u,%u)", - dst_width, dst_height, src_width, src_height, - static_cast(format), static_cast(bgfxFmt), - bpp, dst_x, dst_y)); - } - - bgfx::TextureHandle h = g_textureCache[dst_texture]; - if (!bgfx::isValid(h)) - { - return; - } - - // Build the shroud image into a persistent buffer and use makeRef - // to avoid copying into bgfx's command buffer (which has a 64KB - // default limit and overflows when combined with texture creation - // bursts like the satellite reveal). The persistent buffer stays - // valid until the next frame's update overwrites it. - const unsigned fullSize = dst_width * dst_height * bpp; - if (fullSize == 0 || src_width == 0 || src_height == 0 - || dst_width > 0xFFFF || dst_height > 0xFFFF) - { - return; - } - const bgfx::Memory * mem = bgfx::alloc(fullSize); - std::memset(mem->data, 0xFF, fullSize); - const unsigned rowBytes = src_width * bpp; - for (unsigned row = 0; row < src_height; ++row) - { - const unsigned dstOffset = ((dst_y + row) * dst_width + dst_x) * bpp; - const unsigned srcOffset = row * pitch; - if (dstOffset + rowBytes <= fullSize && srcOffset + rowBytes <= src_height * pitch) - { - std::memcpy(mem->data + dstOffset, static_cast(pixel_data) + srcOffset, rowBytes); - } - } - bgfx::updateTexture2D(h, 0, 0, 0, 0, - static_cast(dst_width), - static_cast(dst_height), - mem, static_cast(dst_width * bpp)); -} void BgfxBackend::Capture_Vertex_Data(const VertexBufferClass * vb, const void * data, unsigned int size_bytes) { - if (!g_bgfxInitialized || vb == nullptr || data == nullptr || size_bytes == 0) + if (!g_device.initialized || vb == nullptr || data == nullptr || size_bytes == 0) { return; } @@ -2954,9 +2231,14 @@ void BgfxBackend::Capture_Vertex_Data(const VertexBufferClass * vb, const uint32_t buffer_bytes = static_cast(vb->Get_Vertex_Count()) * stride; if (stride == 0 || buffer_bytes == 0 || size_bytes > buffer_bytes) { - WWDEBUG_SAY(("[BgfxBackend] skip VB full-capture: " - "size_bytes=%u stride=%u total=%u", - size_bytes, stride, buffer_bytes)); + static bool s_loggedVbCaptureSkip = false; + if (!s_loggedVbCaptureSkip) + { + s_loggedVbCaptureSkip = true; + WWDEBUG_SAY(("[BgfxBackend] skip VB full-capture: " + "size_bytes=%u stride=%u total=%u", + size_bytes, stride, buffer_bytes)); + } return; } @@ -2973,15 +2255,20 @@ void BgfxBackend::Capture_Index_Data(const IndexBufferClass * ib, const void * data, unsigned int size_bytes) { - if (!g_bgfxInitialized || ib == nullptr || data == nullptr || size_bytes == 0) + if (!g_device.initialized || ib == nullptr || data == nullptr || size_bytes == 0) { return; } const uint32_t buffer_bytes = static_cast(ib->Get_Index_Count()) * sizeof(uint16_t); if (buffer_bytes == 0 || size_bytes > buffer_bytes) { - WWDEBUG_SAY(("[BgfxBackend] skip IB full-capture: " - "size_bytes=%u total=%u", size_bytes, buffer_bytes)); + static bool s_loggedIbCaptureSkip = false; + if (!s_loggedIbCaptureSkip) + { + s_loggedIbCaptureSkip = true; + WWDEBUG_SAY(("[BgfxBackend] skip IB full-capture: " + "size_bytes=%u total=%u", size_bytes, buffer_bytes)); + } return; } bgfx::DynamicIndexBufferHandle h = EnsureDynamicIndexBuffer(ib); @@ -2998,7 +2285,7 @@ void BgfxBackend::Capture_Vertex_Sub_Range(const VertexBufferClass * vb, unsigned int start_vertex, unsigned int size_bytes) { - if (!g_bgfxInitialized || vb == nullptr || data == nullptr || size_bytes == 0) + if (!g_device.initialized || vb == nullptr || data == nullptr || size_bytes == 0) { return; } @@ -3006,16 +2293,26 @@ void BgfxBackend::Capture_Vertex_Sub_Range(const VertexBufferClass * vb, const uint32_t stride = vb->FVF_Info().Get_FVF_Size(); if (stride == 0) { - WWDEBUG_SAY(("[BgfxBackend] skip VB capture: stride=0 vb=%p", vb)); + static bool s_loggedVbStrideZero = false; + if (!s_loggedVbStrideZero) + { + s_loggedVbStrideZero = true; + WWDEBUG_SAY(("[BgfxBackend] skip VB capture: stride=0 vb=%p", vb)); + } return; } const uint32_t buffer_bytes = static_cast(vb->Get_Vertex_Count()) * stride; const uint64_t end_byte = static_cast(start_vertex) * stride + size_bytes; if (end_byte > buffer_bytes) { - WWDEBUG_SAY(("[BgfxBackend] skip VB capture: out-of-range " - "start_vert=%u size_bytes=%u stride=%u total=%u", - start_vertex, size_bytes, stride, buffer_bytes)); + static bool s_loggedVbSubRangeOor = false; + if (!s_loggedVbSubRangeOor) + { + s_loggedVbSubRangeOor = true; + WWDEBUG_SAY(("[BgfxBackend] skip VB capture: out-of-range " + "start_vert=%u size_bytes=%u stride=%u total=%u", + start_vertex, size_bytes, stride, buffer_bytes)); + } return; } bgfx::DynamicVertexBufferHandle h = EnsureDynamicVertexBuffer(vb); @@ -3033,7 +2330,7 @@ void BgfxBackend::Capture_Index_Sub_Range(const IndexBufferClass * ib, unsigned int start_index, unsigned int size_bytes) { - if (!g_bgfxInitialized || ib == nullptr || data == nullptr || size_bytes == 0) + if (!g_device.initialized || ib == nullptr || data == nullptr || size_bytes == 0) { return; } @@ -3041,9 +2338,14 @@ void BgfxBackend::Capture_Index_Sub_Range(const IndexBufferClass * ib, const uint64_t end_byte = static_cast(start_index) * sizeof(uint16_t) + size_bytes; if (end_byte > buffer_bytes) { - WWDEBUG_SAY(("[BgfxBackend] skip IB capture: out-of-range " - "start_idx=%u size_bytes=%u total=%u", - start_index, size_bytes, buffer_bytes)); + static bool s_loggedIbSubRangeOor = false; + if (!s_loggedIbSubRangeOor) + { + s_loggedIbSubRangeOor = true; + WWDEBUG_SAY(("[BgfxBackend] skip IB capture: out-of-range " + "start_idx=%u size_bytes=%u total=%u", + start_index, size_bytes, buffer_bytes)); + } return; } bgfx::DynamicIndexBufferHandle h = EnsureDynamicIndexBuffer(ib); @@ -3060,7 +2362,7 @@ void BgfxBackend::Capture_Index_Sub_Range(const IndexBufferClass * ib, // draw pass routing. The sort flush calls Begin / Capture / End // around its per-batch draw loop. Begin flips the routing flag so // SubmitEngineDraw pushes submits into kBgfxEngineSortView using -// g_bgfxSortWorld; End flips it back. Capture computes the per-batch +// g_frame.sortWorld; End flips it back. Capture computes the per-batch // effective world matrix and stores it in bgfx column-major form. // // Matrix math: W3D Matrix4x4 is row-major with row vectors, so the @@ -3073,17 +2375,17 @@ void BgfxBackend::Capture_Index_Sub_Range(const IndexBufferClass * ib, void BgfxBackend::Begin_Sorted_Batch_Pass() { - g_inSortFlush = true; - if (!g_bgfxSortProjCaptured) + g_views.inSortFlush = true; + if (!g_frame.sortProjCaptured) { - std::memcpy(g_bgfxSortProj, g_bgfxProj, sizeof(g_bgfxSortProj)); - g_bgfxSortProjCaptured = true; + std::memcpy(g_frame.sortProj, g_frame.proj, sizeof(g_frame.sortProj)); + g_frame.sortProjCaptured = true; } } void BgfxBackend::End_Sorted_Batch_Pass() { - g_inSortFlush = false; + g_views.inSortFlush = false; } void BgfxBackend::Capture_Sorted_Batch_Transforms(const Matrix4x4 & sortWorld, @@ -3106,7 +2408,7 @@ void BgfxBackend::Capture_Sorted_Batch_Transforms(const Matrix4x4 & sortWorld, { s += sortWorld[r][k] * sortView[k][c]; } - g_bgfxSortWorld[r * 4 + c] = s; + g_frame.sortWorld[r * 4 + c] = s; // Store raw model (no camera view baked in) // for shadow caster submissions. } @@ -3116,107 +2418,107 @@ void BgfxBackend::Capture_Sorted_Batch_Transforms(const Matrix4x4 & sortWorld, { for (int c = 0; c < 4; ++c) { - g_bgfxSortWorldRaw[r * 4 + c] = sortWorld[r][c]; + g_frame.sortWorldRaw[r * 4 + c] = sortWorld[r][c]; } } } -void BgfxBackend::Capture_Sorted_Batch_Light(const D3DLIGHT8 & light, bool enabled) +void BgfxBackend::Capture_Sorted_Batch_Light(const RenderBackendLight & light, bool enabled) { // Sort batch lights are always light 0 (the primary directional). // Scene ambient was already captured by Set_Light_Environment. - // D3D8 Direction points FROM light toward surface; negate for N.L. + // D3D8 direction points FROM light toward surface; negate for N.L. if (enabled) { - g_currentLightDirs[0][0] = -light.Direction.x; - g_currentLightDirs[0][1] = -light.Direction.y; - g_currentLightDirs[0][2] = -light.Direction.z; - g_currentLightDirs[0][3] = 1.0f; - g_currentLightColors[0][0] = light.Diffuse.r; - g_currentLightColors[0][1] = light.Diffuse.g; - g_currentLightColors[0][2] = light.Diffuse.b; + g_draw.lightDirs[0][0] = -light.direction[0]; + g_draw.lightDirs[0][1] = -light.direction[1]; + g_draw.lightDirs[0][2] = -light.direction[2]; + g_draw.lightDirs[0][3] = 1.0f; + g_draw.lightColors[0][0] = light.diffuse[0]; + g_draw.lightColors[0][1] = light.diffuse[1]; + g_draw.lightColors[0][2] = light.diffuse[2]; } else { - g_currentLightDirs[0][3] = 0.0f; + g_draw.lightDirs[0][3] = 0.0f; } } static void BindTextureStages() { // bgfx default (flags=0) is bilinear filtering. No explicit flags needed. - if (bgfx::isValid(g_sTex0)) + if (bgfx::isValid(g_uniforms.sTex0)) { const bgfx::TextureHandle bound = - bgfx::isValid(g_currentBgfxTexture0) ? g_currentBgfxTexture0 : g_defaultWhiteTexture; + bgfx::isValid(g_draw.tex[0]) ? g_draw.tex[0] : g_device.defaultWhiteTexture; if (bgfx::isValid(bound)) { - bgfx::setTexture(0, g_sTex0, bound, g_currentSamplerFlags0); + bgfx::setTexture(0, g_uniforms.sTex0, bound, g_draw.samplerFlags[0]); } } - if (bgfx::isValid(g_sTex1)) + if (bgfx::isValid(g_uniforms.sTex1)) { const bgfx::TextureHandle bound = - bgfx::isValid(g_currentBgfxTexture1) ? g_currentBgfxTexture1 : g_defaultWhiteTexture; + bgfx::isValid(g_draw.tex[1]) ? g_draw.tex[1] : g_device.defaultWhiteTexture; if (bgfx::isValid(bound)) { - bgfx::setTexture(1, g_sTex1, bound, g_currentSamplerFlags1); + bgfx::setTexture(1, g_uniforms.sTex1, bound, g_draw.samplerFlags[1]); } } - if (bgfx::isValid(g_sTex2)) + if (bgfx::isValid(g_uniforms.sTex2)) { const bgfx::TextureHandle bound = - bgfx::isValid(g_currentBgfxTexture2) ? g_currentBgfxTexture2 : g_defaultWhiteTexture; + bgfx::isValid(g_draw.tex[2]) ? g_draw.tex[2] : g_device.defaultWhiteTexture; if (bgfx::isValid(bound)) { - bgfx::setTexture(2, g_sTex2, bound, g_currentSamplerFlags2); + bgfx::setTexture(2, g_uniforms.sTex2, bound, g_draw.samplerFlags[2]); } } - if (bgfx::isValid(g_sTex3)) + if (bgfx::isValid(g_uniforms.sTex3)) { const bgfx::TextureHandle bound = - bgfx::isValid(g_currentBgfxTexture3) ? g_currentBgfxTexture3 : g_defaultWhiteTexture; + bgfx::isValid(g_draw.tex[3]) ? g_draw.tex[3] : g_device.defaultWhiteTexture; if (bgfx::isValid(bound)) { - bgfx::setTexture(3, g_sTex3, bound, g_currentSamplerFlags3); + bgfx::setTexture(3, g_uniforms.sTex3, bound, g_draw.samplerFlags[3]); } } } static void UploadMaterialUniforms() { - if (bgfx::isValid(g_uMatDiffuse)) + if (bgfx::isValid(g_uniforms.uMatDiffuse)) { - bgfx::setUniform(g_uMatDiffuse, g_currentMatDiffuse); - if (bgfx::isValid(g_uMatEmissive)) - bgfx::setUniform(g_uMatEmissive, g_currentMatEmissive); + bgfx::setUniform(g_uniforms.uMatDiffuse, g_draw.matDiffuse); + if (bgfx::isValid(g_uniforms.uMatEmissive)) + bgfx::setUniform(g_uniforms.uMatEmissive, g_draw.matEmissive); } - if (bgfx::isValid(g_uTssOps0)) + if (bgfx::isValid(g_uniforms.uTssOps0)) { - bgfx::setUniform(g_uTssOps0, g_currentTssOps0); + bgfx::setUniform(g_uniforms.uTssOps0, g_draw.tssOps0); } - if (bgfx::isValid(g_uTssOps1)) + if (bgfx::isValid(g_uniforms.uTssOps1)) { - bgfx::setUniform(g_uTssOps1, g_currentTssOps1); + bgfx::setUniform(g_uniforms.uTssOps1, g_draw.tssOps1); } - if (bgfx::isValid(g_uAtestParams)) + if (bgfx::isValid(g_uniforms.uAtestParams)) { - float atestParams[4] = { g_currentAtestRef, 0.0f, 0.0f, 0.0f }; - bgfx::setUniform(g_uAtestParams, atestParams); + float atestParams[4] = { g_draw.atestRef, 0.0f, 0.0f, 0.0f }; + bgfx::setUniform(g_uniforms.uAtestParams, atestParams); } - if (bgfx::isValid(g_uGrayscaleEnable)) + if (bgfx::isValid(g_uniforms.uGrayscaleEnable)) { - bgfx::setUniform(g_uGrayscaleEnable, g_currentGrayscaleEnable); + bgfx::setUniform(g_uniforms.uGrayscaleEnable, g_draw.grayscaleEnable); } - if (bgfx::isValid(g_uCloudParams)) + if (bgfx::isValid(g_uniforms.uCloudParams)) { - bgfx::setUniform(g_uCloudParams, g_currentCloudParams); + bgfx::setUniform(g_uniforms.uCloudParams, g_draw.cloudParams); } - if (bgfx::isValid(g_sCloudMap) && bgfx::isValid(g_currentCloudTex)) + if (bgfx::isValid(g_uniforms.sCloudMap) && bgfx::isValid(g_draw.cloudTex)) { // WRAP addressing matches the DX8 cloud pass at W3DShaderManager.cpp:1742. - bgfx::setTexture(5, g_sCloudMap, g_currentCloudTex, BGFX_SAMPLER_NONE); + bgfx::setTexture(5, g_uniforms.sCloudMap, g_draw.cloudTex, BGFX_SAMPLER_NONE); } } @@ -3226,13 +2528,13 @@ static void UploadMaterialUniforms() // of the sorting VB/IB with the correct vba_offset / iba_offset / // index_base_offset / min_vertex_index arithmetic. The engine's lock // destructors already fired Capture_Dynamic_* for those inner buffers, -// so g_pendingDynVB / g_pendingDynIB hold their transients keyed by +// so g_draw.pendingVB / g_draw.pendingIB hold their transients keyed by // the exact access-class pointers we were just handed. // // We pull those transients into local draw state, emit a single bgfx // submit to the sorted view with the inner buffers and remapped args // (start_index=0, min_vertex_index=0, count relative to the inner -// buffers), and set g_skipNextSubmitEngineDraw so the outer Draw_Triangles +// buffers), and set g_views.skipNextSubmitEngineDraw so the outer Draw_Triangles // does not emit a second, incorrect submit using the old sorting-VB // args. @@ -3241,7 +2543,7 @@ void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, unsigned short polygon_count, unsigned short vertex_count) { - if (!g_bgfxInitialized) + if (!g_device.initialized) { return; } @@ -3249,7 +2551,7 @@ void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, // The inner dynamic buffers' WriteLockClass dtors already ran, so // Capture_Dynamic_Vertex_Data / Capture_Dynamic_Index_Data should // have stashed their transients keyed by &dyn_vb / &dyn_ib. - if (!g_pendingDynVB.valid || g_pendingDynVB.owner != &dyn_vb) + if (!g_draw.pendingVB.valid || g_draw.pendingVB.owner != &dyn_vb) { static bool s_loggedSkipVB = false; if (!s_loggedSkipVB) @@ -3257,12 +2559,12 @@ void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, s_loggedSkipVB = true; WWDEBUG_SAY(("[BgfxBackend] Submit_Sorted_Draw SKIP: pendingDynVB not " "claimable (valid=%d ownerMatch=%d)", - int(g_pendingDynVB.valid), - int(g_pendingDynVB.owner == &dyn_vb))); + int(g_draw.pendingVB.valid), + int(g_draw.pendingVB.owner == &dyn_vb))); } return; } - if (!g_pendingDynIB.valid || g_pendingDynIB.owner != &dyn_ib) + if (!g_draw.pendingIB.valid || g_draw.pendingIB.owner != &dyn_ib) { static bool s_loggedSkipIB = false; if (!s_loggedSkipIB) @@ -3270,30 +2572,30 @@ void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, s_loggedSkipIB = true; WWDEBUG_SAY(("[BgfxBackend] Submit_Sorted_Draw SKIP: pendingDynIB not " "claimable (valid=%d ownerMatch=%d)", - int(g_pendingDynIB.valid), - int(g_pendingDynIB.owner == &dyn_ib))); + int(g_draw.pendingIB.valid), + int(g_draw.pendingIB.owner == &dyn_ib))); } return; } - const bgfx::TransientVertexBuffer vb = g_pendingDynVB.tvb; - const bgfx::TransientIndexBuffer ib = g_pendingDynIB.tib; - g_pendingDynVB.valid = false; - g_pendingDynIB.valid = false; + const bgfx::TransientVertexBuffer vb = g_draw.pendingVB.tvb; + const bgfx::TransientIndexBuffer ib = g_draw.pendingIB.tib; + g_draw.pendingVB.valid = false; + g_draw.pendingIB.valid = false; - if (!bgfx::isValid(g_currentBgfxProgram)) + if (!bgfx::isValid(g_draw.program)) { - g_skipNextSubmitEngineDraw = true; + g_views.skipNextSubmitEngineDraw = true; return; } // Sort view's view+proj were set up at init (identity view, // projection tracks opaque view via Set_Projection_Transform_With_Z_Bias). - // World is the current g_bgfxSortWorld if we are inside a sort batch, - // otherwise the regular g_bgfxWorld (rigid FVF category with sorting=true + // World is the current g_frame.sortWorld if we are inside a sort batch, + // otherwise the regular g_frame.world (rigid FVF category with sorting=true // has no batch-wrapped Apply_Render_State - it uses the per-mesh world // set by the caller via g_renderBackend->Set_Transform). - const float * worldMtx = g_inSortFlush ? g_bgfxSortWorld : g_bgfxWorld; + const float * worldMtx = g_views.inSortFlush ? g_frame.sortWorld : g_frame.world; bgfx::setTransform(worldMtx); bgfx::setVertexBuffer(0, &vb, 0, vertex_count); @@ -3302,8 +2604,8 @@ void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, BindTextureStages(); UploadMaterialUniforms(); - uint64_t state = (g_currentBgfxState != 0) - ? g_currentBgfxState + uint64_t state = (g_draw.state != 0) + ? g_draw.state : (BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); // TheSuperHackers @fix bobtista 16/04/2026 Override cull mode from @@ -3320,10 +2622,10 @@ void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, // Apply color write mask override — same as SubmitEngineDraw. // Without this, sorted particles/effects write alpha, destroying // the shoreline depth gradient used by DESTALPHA water blending. - if (g_colorWriteOverride >= 0) + if (g_overrides.colorWriteOverride >= 0) { state &= ~(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); - state |= static_cast(g_colorWriteOverride); + state |= static_cast(g_overrides.colorWriteOverride); } bgfx::setState(state); @@ -3335,30 +2637,30 @@ void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, // debug layer raises DEVICE_DRAW_SAMPLER_MISMATCH (#390, 0x87A) when // a draw reaches the shader with slot 4 empty, because the default // sampler is not comparison-capable. - if (bgfx::isValid(g_shadowMapDepth) && bgfx::isValid(g_sShadowMap)) + if (bgfx::isValid(g_device.shadowMapDepth) && bgfx::isValid(g_uniforms.sShadowMap)) { - bgfx::setTexture(4, g_sShadowMap, g_shadowMapDepth); + bgfx::setTexture(4, g_uniforms.sShadowMap, g_device.shadowMapDepth); } - bgfx::submit(kBgfxEngineSortView, g_currentBgfxProgram); + bgfx::submit(kBgfxEngineSortView, g_draw.program); // CSM: also submit sorted geometry as shadow caster. - if (bgfx::isValid(g_shadowCasterProgram) - && bgfx::isValid(g_shadowMapFB)) + if (bgfx::isValid(g_device.shadowCasterProgram) + && bgfx::isValid(g_device.shadowMapFB)) { bgfx::setVertexBuffer(0, &vb); bgfx::setIndexBuffer(&ib, 0, static_cast(polygon_count) * 3); - bgfx::setTransform(g_bgfxSortWorldRaw); + bgfx::setTransform(g_frame.sortWorldRaw); const uint64_t casterState = BGFX_STATE_WRITE_Z | BGFX_STATE_DEPTH_TEST_LESS | BGFX_STATE_CULL_CW; bgfx::setState(casterState); - bgfx::submit(kBgfxShadowMapView, g_shadowCasterProgram); + bgfx::submit(kBgfxShadowMapView, g_device.shadowCasterProgram); } - g_skipNextSubmitEngineDraw = true; + g_views.skipNextSubmitEngineDraw = true; } // TheSuperHackers @refactor bobtista 11/04/2026 dynamic @@ -3372,13 +2674,13 @@ void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, // sub-range. We alloc a transient buffer of exactly that size, memcpy // the data in, and stash the handle keyed by the access class pointer. // The matching Set_Vertex_Buffer(DynamicVBAccessClass&) later sees its -// own pointer in g_pendingDynVB and claims the transient for the draw. +// own pointer in g_draw.pendingVB and claims the transient for the draw. void BgfxBackend::Capture_Dynamic_Vertex_Data(const DynamicVBAccessClass * vba, const void * data, unsigned int size_bytes) { - if (!g_bgfxInitialized || vba == nullptr || data == nullptr || size_bytes == 0) + if (!g_device.initialized || vba == nullptr || data == nullptr || size_bytes == 0) { return; } @@ -3396,16 +2698,16 @@ void BgfxBackend::Capture_Dynamic_Vertex_Data(const DynamicVBAccessClass * vba, } if (bgfx::getAvailTransientVertexBuffer(num_verts, layout) < num_verts) { - g_pendingDynVB.valid = false; + g_draw.pendingVB.valid = false; return; } - bgfx::allocTransientVertexBuffer(&g_pendingDynVB.tvb, num_verts, layout); + bgfx::allocTransientVertexBuffer(&g_draw.pendingVB.tvb, num_verts, layout); const uint32_t copy_bytes = num_verts * layout.getStride(); const uint32_t bytes = (size_bytes < copy_bytes) ? size_bytes : copy_bytes; - std::memcpy(g_pendingDynVB.tvb.data, data, bytes); - g_pendingDynVB.owner = vba; - g_pendingDynVB.valid = true; + std::memcpy(g_draw.pendingVB.tvb.data, data, bytes); + g_draw.pendingVB.owner = vba; + g_draw.pendingVB.valid = true; } @@ -3413,7 +2715,7 @@ void BgfxBackend::Capture_Dynamic_Index_Data(const DynamicIBAccessClass * iba, const void * data, unsigned int size_bytes) { - if (!g_bgfxInitialized || iba == nullptr || data == nullptr || size_bytes == 0) + if (!g_device.initialized || iba == nullptr || data == nullptr || size_bytes == 0) { return; } @@ -3425,16 +2727,16 @@ void BgfxBackend::Capture_Dynamic_Index_Data(const DynamicIBAccessClass * iba, } if (bgfx::getAvailTransientIndexBuffer(num_indices) < num_indices) { - g_pendingDynIB.valid = false; + g_draw.pendingIB.valid = false; return; } - bgfx::allocTransientIndexBuffer(&g_pendingDynIB.tib, num_indices); + bgfx::allocTransientIndexBuffer(&g_draw.pendingIB.tib, num_indices); const uint32_t copy_bytes = num_indices * sizeof(uint16_t); const uint32_t bytes = (size_bytes < copy_bytes) ? size_bytes : copy_bytes; - std::memcpy(g_pendingDynIB.tib.data, data, bytes); - g_pendingDynIB.owner = iba; - g_pendingDynIB.valid = true; + std::memcpy(g_draw.pendingIB.tib.data, data, bytes); + g_draw.pendingIB.owner = iba; + g_draw.pendingIB.valid = true; } // -- State: shaders, materials, textures ------------------------------------ @@ -3443,9 +2745,9 @@ void BgfxBackend::Set_Shader(const ShaderClass & shader) { DX8Backend::Set_Shader(shader); - g_currentBgfxProgram = g_uberProgram; - g_currentBgfxState = BuildBgfxStateForShader(shader); - BuildTssOpsForShader(shader, g_currentTssOps0, g_currentTssOps1, &g_currentAtestRef); + g_draw.program = g_device.uberProgram; + g_draw.state = BuildBgfxStateForShader(shader); + BuildTssOpsForShader(shader, g_draw.tssOps0, g_draw.tssOps1, &g_draw.atestRef); Clear_State_Overrides(); } @@ -3478,14 +2780,14 @@ void BgfxBackend::Set_Material(const VertexMaterialClass * material) { material->Get_Diffuse(&diffuse); } - g_currentMatDiffuse[0] = diffuse.X; - g_currentMatDiffuse[1] = diffuse.Y; - g_currentMatDiffuse[2] = diffuse.Z; - g_currentMatDiffuse[3] = material->Get_Opacity(); + g_draw.matDiffuse[0] = diffuse.X; + g_draw.matDiffuse[1] = diffuse.Y; + g_draw.matDiffuse[2] = diffuse.Z; + g_draw.matDiffuse[3] = material->Get_Opacity(); // Track whether this material uses hardware lighting. When false // (terrain, pre-lit meshes), vertex colors already contain the // lit result and the shader must NOT apply N.L on top. - g_currentLightingEnabled[0] = material->Get_Lighting() ? 1.0f : 0.0f; + g_draw.lightingEnabled[0] = material->Get_Lighting() ? 1.0f : 0.0f; // Emissive color — self-illumination. Self-glow meshes (e.g. // SCMoveHint.w3d "move here" indicator) set the material emissive // to their glow color and rely on D3D fixed-function adding it @@ -3493,28 +2795,28 @@ void BgfxBackend::Set_Material(const VertexMaterialClass * material) // because the lit math produces 0 and there's no emissive term. Vector3 emissive(0.0f, 0.0f, 0.0f); material->Get_Emissive(&emissive); - g_currentMatEmissive[0] = emissive.X; - g_currentMatEmissive[1] = emissive.Y; - g_currentMatEmissive[2] = emissive.Z; - g_currentMatEmissive[3] = 0.0f; + g_draw.matEmissive[0] = emissive.X; + g_draw.matEmissive[1] = emissive.Y; + g_draw.matEmissive[2] = emissive.Z; + g_draw.matEmissive[3] = 0.0f; } else { - g_currentMatDiffuse[0] = 1.0f; - g_currentMatDiffuse[1] = 1.0f; - g_currentMatDiffuse[2] = 1.0f; - g_currentMatDiffuse[3] = 1.0f; - g_currentMatEmissive[0] = 0.0f; - g_currentMatEmissive[1] = 0.0f; - g_currentMatEmissive[2] = 0.0f; - g_currentMatEmissive[3] = 0.0f; + g_draw.matDiffuse[0] = 1.0f; + g_draw.matDiffuse[1] = 1.0f; + g_draw.matDiffuse[2] = 1.0f; + g_draw.matDiffuse[3] = 1.0f; + g_draw.matEmissive[0] = 0.0f; + g_draw.matEmissive[1] = 0.0f; + g_draw.matEmissive[2] = 0.0f; + g_draw.matEmissive[3] = 0.0f; // Null material means no material-driven lighting. Dazzle and // similar effect overlays call Set_Material(nullptr) and bake // their per-vertex intensity into diffuse.rgb; the lit path in // fs_uber would ignore that baked color and output tex * lit, // producing bright flashes where the dazzle should be invisible. // Force the unlit path so vertex diffuse modulates the output. - g_currentLightingEnabled[0] = 0.0f; + g_draw.lightingEnabled[0] = 0.0f; } } @@ -3536,9 +2838,9 @@ void BgfxBackend::Set_Texture(unsigned int stage, TextureBaseClass * texture) // vertex/material color and lets the scene look correct until the // RT texture is properly captured. if (!bgfx::isValid(h) && texture != nullptr && - g_renderTargetSet.count(texture) > 0) + g_caches.renderTarget.count(texture) > 0) { - h = g_defaultWhiteTexture; + h = g_device.defaultWhiteTexture; static bool s_loggedRTFallback = false; if (!s_loggedRTFallback) { @@ -3550,7 +2852,7 @@ void BgfxBackend::Set_Texture(unsigned int stage, TextureBaseClass * texture) } } if (!bgfx::isValid(h) && texture != nullptr && - g_renderTargetSet.count(texture) == 0) + g_caches.renderTarget.count(texture) == 0) { static bool s_loggedWhiteFallback = false; if (!s_loggedWhiteFallback) @@ -3580,12 +2882,12 @@ void BgfxBackend::Set_Texture(unsigned int stage, TextureBaseClass * texture) } switch (stage) { - case 0: g_currentBgfxTexture0 = h; - g_currentSamplerFlags0 = samplerFlags; break; - case 1: g_currentBgfxTexture1 = h; - g_currentSamplerFlags1 = samplerFlags; break; - case 2: g_currentBgfxTexture2 = h; g_currentSamplerFlags2 = samplerFlags; break; - case 3: g_currentBgfxTexture3 = h; g_currentSamplerFlags3 = samplerFlags; break; + case 0: g_draw.tex[0] = h; + g_draw.samplerFlags[0] = samplerFlags; break; + case 1: g_draw.tex[1] = h; + g_draw.samplerFlags[1] = samplerFlags; break; + case 2: g_draw.tex[2] = h; g_draw.samplerFlags[2] = samplerFlags; break; + case 3: g_draw.tex[3] = h; g_draw.samplerFlags[3] = samplerFlags; break; default: break; } } @@ -3594,63 +2896,44 @@ void BgfxBackend::Set_Texture(unsigned int stage, TextureBaseClass * texture) void BgfxBackend::Set_Ambient(const Vector3 & color) { DX8Backend::Set_Ambient(color); - g_currentSceneAmbient[0] = color.X; - g_currentSceneAmbient[1] = color.Y; - g_currentSceneAmbient[2] = color.Z; + g_draw.sceneAmbient[0] = color.X; + g_draw.sceneAmbient[1] = color.Y; + g_draw.sceneAmbient[2] = color.Z; } -// TheSuperHackers @fix bobtista 20/04/2026 Set_Blend_Factors goes through -// the g_renderBackend abstraction so callers (water code in particular) -// can change the blend mode between draws. The DX8Backend default only -// updates D3D render state, leaving bgfx's own g_blendOverrideBits stale. -// Water rendering relies on this to restore SRC_ALPHA / INV_SRC_ALPHA -// blending after its DESTALPHA shoreline pass. Without this override the -// DESTALPHA state set by Override_Material_Opacity() persists into the -// next draw (e.g. the small faction-emblem quad on the command-center -// bib), producing a black rectangle where the emblem background should -// be transparent. Translate the BlendFactor enum values to bgfx bits and -// update the blend override so subsequent bgfx submits see the change. +// Maps WW3D BlendFactor enum (1..8) to bgfx blend-factor bits. Index 0 unused. +static const uint64_t kBgfxBlendMap[9] = { + 0, + BGFX_STATE_BLEND_ZERO, // 1 = RB_BLEND_ZERO + BGFX_STATE_BLEND_ONE, // 2 = RB_BLEND_ONE + BGFX_STATE_BLEND_SRC_COLOR, // 3 + BGFX_STATE_BLEND_INV_SRC_COLOR, // 4 + BGFX_STATE_BLEND_SRC_ALPHA, // 5 = RB_BLEND_SRC_ALPHA + BGFX_STATE_BLEND_INV_SRC_ALPHA, // 6 = RB_BLEND_INV_SRC_ALPHA + BGFX_STATE_BLEND_DST_ALPHA, // 7 = RB_BLEND_DEST_ALPHA + BGFX_STATE_BLEND_INV_DST_ALPHA // 8 = RB_BLEND_INV_DEST_ALPHA +}; + +// TheSuperHackers @fix bobtista 20/04/2026 The DX8Backend default only updates D3D render state, leaving bgfx's g_overrides.blendBits stale. Water rendering relies on this to restore SRC_ALPHA / INV_SRC_ALPHA blending after its DESTALPHA shoreline pass — otherwise the DESTALPHA state set by Override_Material_Opacity() persists into the next draw (e.g. the faction-emblem quad on the command-center bib), painting it black. void BgfxBackend::Set_Blend_Factors(BlendFactor src, BlendFactor dest) { DX8Backend::Set_Blend_Factors(src, dest); - static const uint64_t kBlendMap[9] = { - 0, - BGFX_STATE_BLEND_ZERO, // 1 = RB_BLEND_ZERO - BGFX_STATE_BLEND_ONE, // 2 = RB_BLEND_ONE - BGFX_STATE_BLEND_SRC_COLOR, // 3 - BGFX_STATE_BLEND_INV_SRC_COLOR, // 4 - BGFX_STATE_BLEND_SRC_ALPHA, // 5 = RB_BLEND_SRC_ALPHA - BGFX_STATE_BLEND_INV_SRC_ALPHA, // 6 = RB_BLEND_INV_SRC_ALPHA - BGFX_STATE_BLEND_DST_ALPHA, // 7 = RB_BLEND_DEST_ALPHA - BGFX_STATE_BLEND_INV_DST_ALPHA // 8 = RB_BLEND_INV_DEST_ALPHA - }; const unsigned s = static_cast(src); const unsigned d = static_cast(dest); if (s >= 1 && s <= 8 && d >= 1 && d <= 8) { - g_blendOverrideActive = true; - g_blendOverrideBits = BGFX_STATE_BLEND_FUNC(kBlendMap[s], kBlendMap[d]); + g_overrides.SetBlend(BGFX_STATE_BLEND_FUNC(kBgfxBlendMap[s], kBgfxBlendMap[d])); } } -void BgfxBackend::Override_Blend(unsigned srcBlend, unsigned dstBlend) +void BgfxBackend::Override_Blend(BlendFactor srcBlend, BlendFactor dstBlend) { - static const uint64_t kBlendMap[9] = { - 0, - BGFX_STATE_BLEND_ZERO, // 1 - BGFX_STATE_BLEND_ONE, // 2 - BGFX_STATE_BLEND_SRC_COLOR, // 3 - BGFX_STATE_BLEND_INV_SRC_COLOR, // 4 - BGFX_STATE_BLEND_SRC_ALPHA, // 5 - BGFX_STATE_BLEND_INV_SRC_ALPHA, // 6 - BGFX_STATE_BLEND_DST_ALPHA, // 7 - BGFX_STATE_BLEND_INV_DST_ALPHA // 8 - }; - if (srcBlend >= 1 && srcBlend <= 8 && dstBlend >= 1 && dstBlend <= 8) + const unsigned srcIdx = static_cast(srcBlend); + const unsigned dstIdx = static_cast(dstBlend); + if (srcIdx >= 1 && srcIdx <= 8 && dstIdx >= 1 && dstIdx <= 8) { - g_blendOverrideActive = true; - g_blendOverrideBits = BGFX_STATE_BLEND_FUNC(kBlendMap[srcBlend], kBlendMap[dstBlend]); + g_overrides.SetBlend(BGFX_STATE_BLEND_FUNC(kBgfxBlendMap[srcIdx], kBgfxBlendMap[dstIdx])); } else { @@ -3659,29 +2942,30 @@ void BgfxBackend::Override_Blend(unsigned srcBlend, unsigned dstBlend) { s_loggedBlendBad = true; WWDEBUG_SAY(("[BgfxBackend] BLEND OVERRIDE BAD VALUES: src=%u dst=%u (out of range 1-8)", - srcBlend, dstBlend)); + srcIdx, dstIdx)); } } - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, srcBlend); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, dstBlend); + // RB_BLEND_* enum values match D3DBLEND_* (verified in IRenderBackend.h comments), so the integer passed to D3D8 is correct. + DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, srcIdx); + DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, dstIdx); } -void BgfxBackend::Override_Alpha_Test(bool enable, unsigned ref, unsigned func) +void BgfxBackend::Override_Alpha_Test(bool enable, unsigned ref, CompareFunc func) { - g_atestOverrideActive = enable; - g_atestOverrideRef = enable ? (ref / 255.0f) : 0.0f; + g_overrides.atestActive = enable; + g_overrides.atestRef = enable ? (ref / 255.0f) : 0.0f; DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHATESTENABLE, enable ? TRUE : FALSE); DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF, ref); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC, func); + // RB_CMP_* enum values match D3DCMP_*, so the integer passed to D3D8 is correct. + DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC, static_cast(func)); } void BgfxBackend::Override_Alpha_Blend_Enable(bool enable) { if (enable) { - g_blendOverrideActive = true; - g_blendOverrideBits = BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_SRC_ALPHA, - BGFX_STATE_BLEND_INV_SRC_ALPHA); + g_overrides.SetBlend(BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_SRC_ALPHA, + BGFX_STATE_BLEND_INV_SRC_ALPHA)); } DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE, enable ? TRUE : FALSE); } @@ -3690,14 +2974,14 @@ void BgfxBackend::Override_Texcoord_Index(unsigned stage, unsigned uvIndex) { if (stage == 0) { - g_currentTexcoordSelect[0] = (uvIndex == 1) ? 1.0f : 0.0f; + g_draw.texcoordSelect[0] = (uvIndex == 1) ? 1.0f : 0.0f; } DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_TEXCOORDINDEX, uvIndex); } void BgfxBackend::Override_Terrain_Blend(bool enable) { - g_currentTexcoordSelect[1] = enable ? 1.0f : 0.0f; + g_draw.texcoordSelect[1] = enable ? 1.0f : 0.0f; } void BgfxBackend::Override_Material_Opacity(float opacity) @@ -3711,54 +2995,54 @@ void BgfxBackend::Override_Material_Opacity(float opacity) // explicitly via Set_Blend_Factors when soft water edge is enabled, // and BgfxBackend::Set_Blend_Factors now propagates that to bgfx. So // this override only needs to handle the opacity uniform. - g_currentMatDiffuse[3] = opacity; - g_waterOverrideActive = true; + g_draw.matDiffuse[3] = opacity; + g_views.waterOverrideActive = true; } void BgfxBackend::Begin_Effect_Overlay() { - g_effectOverlayActive = true; + g_views.effectOverlayActive = true; } void BgfxBackend::End_Effect_Overlay() { - g_effectOverlayActive = false; + g_views.effectOverlayActive = false; } void BgfxBackend::Set_Tree_Shader_Constants(const float swayTable[11][4], const float shroudOffset[4], const float shroudScale[4]) { - std::memcpy(g_currentSwayTable, swayTable, sizeof(g_currentSwayTable)); - std::memcpy(g_currentShroudOffset, shroudOffset, sizeof(g_currentShroudOffset)); - std::memcpy(g_currentShroudScale, shroudScale, sizeof(g_currentShroudScale)); + std::memcpy(g_draw.swayTable, swayTable, sizeof(g_draw.swayTable)); + std::memcpy(g_draw.shroudOffset, shroudOffset, sizeof(g_draw.shroudOffset)); + std::memcpy(g_draw.shroudScale, shroudScale, sizeof(g_draw.shroudScale)); } void BgfxBackend::Set_Tree_Vertex_Shader_Active(bool active) { - g_treeShaderActive = active; + g_views.treeShaderActive = active; } void BgfxBackend::Set_Grayscale_Mode(bool enable) { - g_currentGrayscaleEnable[0] = enable ? 1.0f : 0.0f; + g_draw.grayscaleEnable[0] = enable ? 1.0f : 0.0f; } void BgfxBackend::Set_Cloud_Shadow_Params(bool enable, float scroll_x, float scroll_y, float stretch, TextureClass * cloud_tex) { - g_currentCloudParams[0] = scroll_x; - g_currentCloudParams[1] = scroll_y; - g_currentCloudParams[2] = stretch; - g_currentCloudParams[3] = enable ? 1.0f : 0.0f; + g_draw.cloudParams[0] = scroll_x; + g_draw.cloudParams[1] = scroll_y; + g_draw.cloudParams[2] = stretch; + g_draw.cloudParams[3] = enable ? 1.0f : 0.0f; if (enable && cloud_tex != nullptr) { - g_currentCloudTex = EnsureBgfxTexture(cloud_tex); + g_draw.cloudTex = EnsureBgfxTexture(cloud_tex); } else { - g_currentCloudTex = BGFX_INVALID_HANDLE; + g_draw.cloudTex = BGFX_INVALID_HANDLE; } } @@ -3770,12 +3054,12 @@ void BgfxBackend::Set_Color_Write_Enable(bool red, bool green, bool blue, bool a if (green) mask |= BGFX_STATE_WRITE_G; if (blue) mask |= BGFX_STATE_WRITE_B; if (alpha) mask |= BGFX_STATE_WRITE_A; - g_colorWriteOverride = static_cast(mask); - g_suppressBgfxDraw = false; + g_overrides.colorWriteOverride = static_cast(mask); + g_overrides.suppressDraw = false; } // TheSuperHackers @refactor bobtista 15/04/2026 mirror the -// DWORD variant into g_colorWriteOverride so stencil shadow volume +// DWORD variant into g_overrides.colorWriteOverride so stencil shadow volume // passes that call Set_Color_Write_Mask(0) actually disable bgfx color // writes. Previously the call fell through to DX8Backend only, leaving // shadow volumes drawing as solid black geometry. @@ -3787,16 +3071,16 @@ void BgfxBackend::Set_Color_Write_Mask(unsigned mask) if (mask & D3DCOLORWRITEENABLE_GREEN) bgfxMask |= BGFX_STATE_WRITE_G; if (mask & D3DCOLORWRITEENABLE_BLUE) bgfxMask |= BGFX_STATE_WRITE_B; if (mask & D3DCOLORWRITEENABLE_ALPHA) bgfxMask |= BGFX_STATE_WRITE_A; - g_colorWriteOverride = static_cast(bgfxMask); - g_suppressBgfxDraw = false; + g_overrides.colorWriteOverride = static_cast(bgfxMask); + g_overrides.suppressDraw = false; } void BgfxBackend::Skip_Next_Bgfx_Submit() { - g_skipNextSubmitEngineDraw = true; + g_views.skipNextSubmitEngineDraw = true; } -// TheSuperHackers @fix bobtista 16/04/2026 Remove g_currentMatDiffuse aliasing from texture factor. +// TheSuperHackers @fix bobtista 16/04/2026 Remove g_draw.matDiffuse aliasing from texture factor. // The shadow decal draw is now skipped via Skip_Next_Bgfx_Submit so the aliasing // is unnecessary and it clobbers team colors. void BgfxBackend::Set_Texture_Factor(unsigned argb) @@ -3806,16 +3090,16 @@ void BgfxBackend::Set_Texture_Factor(unsigned argb) void BgfxBackend::Set_Shadow_Light_Position(float x, float y, float z) { - g_shadowSunPosX = x; - g_shadowSunPosY = y; - g_shadowSunPosZ = z; - g_shadowSunPosSet = true; - g_shadowLightCaptured = false; + g_frame.shadowSunPosX = x; + g_frame.shadowSunPosY = y; + g_frame.shadowSunPosZ = z; + g_frame.shadowSunPosSet = true; + g_frame.shadowLightCaptured = false; } void BgfxBackend::Set_Shadow_Volume_Shader_Active(bool active) { - g_shadowVolumeActive = active; + g_views.shadowVolumeActive = active; UpdateShadowStencilState(); } @@ -3830,10 +3114,10 @@ void BgfxBackend::Submit_Shadow_Volume_Caps(unsigned strip_start_vertex, // vertex buffer. Submits to view 6 with the same stencil/cull state // the side walls used — the caller is mid-pass (INCR or DECRSAT). - if (!g_bgfxInitialized - || !g_shadowVolumeActive - || !bgfx::isValid(g_shadowVolumeProgram) - || !bgfx::isValid(g_currentBgfxVB) + if (!g_device.initialized + || !g_views.shadowVolumeActive + || !bgfx::isValid(g_device.shadowVolumeProgram) + || !bgfx::isValid(g_draw.vb) || num_silhouette_verts < 3) { return; @@ -3870,8 +3154,8 @@ void BgfxBackend::Submit_Shadow_Volume_Caps(unsigned strip_start_vertex, } // Skip second pass — single-pass two-sided handles both faces. - if (g_stencilPassOpBits == BGFX_STENCIL_OP_PASS_Z_DECR - || g_stencilPassOpBits == BGFX_STENCIL_OP_PASS_Z_DECRSAT) + if (g_draw.stencilPassOpBits == BGFX_STENCIL_OP_PASS_Z_DECR + || g_draw.stencilPassOpBits == BGFX_STENCIL_OP_PASS_Z_DECRSAT) { return; } @@ -3879,19 +3163,19 @@ void BgfxBackend::Submit_Shadow_Volume_Caps(unsigned strip_start_vertex, // No face culling, two-sided stencil matching the side-wall submit. uint64_t state = BGFX_STATE_DEPTH_TEST_LEQUAL; bgfx::setState(state); - const uint32_t commonBits = g_stencilFuncBits - | BGFX_STENCIL_FUNC_REF(g_stencilRef & 0xFF) - | BGFX_STENCIL_FUNC_RMASK(g_stencilReadMask & 0xFF) - | g_stencilFailOpBits - | g_stencilZFailOpBits; + const uint32_t commonBits = g_draw.stencilFuncBits + | BGFX_STENCIL_FUNC_REF(g_draw.stencilRef & 0xFF) + | BGFX_STENCIL_FUNC_RMASK(g_draw.stencilReadMask & 0xFF) + | g_draw.stencilFailOpBits + | g_draw.stencilZFailOpBits; bgfx::setStencil(commonBits | BGFX_STENCIL_OP_PASS_Z_DECRSAT, commonBits | BGFX_STENCIL_OP_PASS_Z_INCRSAT); - bgfx::setVertexBuffer(0, g_currentBgfxVB); + bgfx::setVertexBuffer(0, g_draw.vb); bgfx::setIndexBuffer(&tib); - bgfx::setTransform(g_bgfxWorld); + bgfx::setTransform(g_frame.world); - bgfx::submit(kBgfxShadowVolumeView, g_shadowVolumeProgram); + bgfx::submit(kBgfxShadowVolumeView, g_device.shadowVolumeProgram); } void BgfxBackend::Submit_Shadow_Volume_Triangulated_Caps( @@ -3899,10 +3183,10 @@ void BgfxBackend::Submit_Shadow_Volume_Triangulated_Caps( const short * local_cap_indices, unsigned cap_index_count) { - if (!g_bgfxInitialized - || !g_shadowVolumeActive - || !bgfx::isValid(g_shadowVolumeProgram) - || !bgfx::isValid(g_currentBgfxVB) + if (!g_device.initialized + || !g_views.shadowVolumeActive + || !bgfx::isValid(g_device.shadowVolumeProgram) + || !bgfx::isValid(g_draw.vb) || local_cap_indices == nullptr || cap_index_count < 3) { @@ -3947,16 +3231,16 @@ void BgfxBackend::Submit_Shadow_Volume_Triangulated_Caps( // Mirror the side-wall submit's state. uint64_t state = BGFX_STATE_DEPTH_TEST_LEQUAL; - if (g_cullModeBits == 1) state |= BGFX_STATE_CULL_CW; - else if (g_cullModeBits == 2) state |= BGFX_STATE_CULL_CCW; + if (g_draw.cullModeBits == 1) state |= BGFX_STATE_CULL_CW; + else if (g_draw.cullModeBits == 2) state |= BGFX_STATE_CULL_CCW; bgfx::setState(state); - bgfx::setStencil(g_shadowStencilFront, g_shadowStencilBack); + bgfx::setStencil(g_draw.shadowStencilFront, g_draw.shadowStencilBack); - bgfx::setVertexBuffer(0, g_currentBgfxVB); + bgfx::setVertexBuffer(0, g_draw.vb); bgfx::setIndexBuffer(&tib); - bgfx::setTransform(g_bgfxWorld); + bgfx::setTransform(g_frame.world); - bgfx::submit(kBgfxShadowVolumeView, g_shadowVolumeProgram); + bgfx::submit(kBgfxShadowVolumeView, g_device.shadowVolumeProgram); } void BgfxBackend::Apply_Stencil_Shadow_Darken(unsigned shadow_color, @@ -3965,7 +3249,7 @@ void BgfxBackend::Apply_Stencil_Shadow_Darken(unsigned shadow_color, { DX8Backend::Apply_Stencil_Shadow_Darken(shadow_color, stencil_read_mask, stencil_ref); - if (!g_bgfxInitialized || !bgfx::isValid(g_shadowApplyProgram)) + if (!g_device.initialized || !bgfx::isValid(g_device.shadowApplyProgram)) return; bgfx::TransientVertexBuffer tvb; @@ -3994,8 +3278,8 @@ void BgfxBackend::Apply_Stencil_Shadow_Darken(unsigned shadow_color, color[1] = static_cast((shadow_color >> 8) & 0xFF) / 255.0f; color[2] = static_cast((shadow_color ) & 0xFF) / 255.0f; color[3] = static_cast((shadow_color >> 24) & 0xFF) / 255.0f; - if (bgfx::isValid(g_uShadowColor)) - bgfx::setUniform(g_uShadowColor, color); + if (bgfx::isValid(g_uniforms.uShadowColor)) + bgfx::setUniform(g_uniforms.uShadowColor, color); uint64_t state = BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_DEPTH_TEST_ALWAYS @@ -4010,34 +3294,34 @@ void BgfxBackend::Apply_Stencil_Shadow_Darken(unsigned shadow_color, | BGFX_STENCIL_OP_PASS_Z_KEEP; bgfx::setStencil(stencil); - bgfx::submit(kBgfxShadowApplyView, g_shadowApplyProgram); + bgfx::submit(kBgfxShadowApplyView, g_device.shadowApplyProgram); } void BgfxBackend::Set_Stencil_Enable(bool enable) { DX8Backend::Set_Stencil_Enable(enable); - g_stencilEnabled = enable; + g_draw.stencilEnabled = enable; UpdateShadowStencilState(); } void BgfxBackend::Set_Stencil_Func(CompareFunc f) { DX8Backend::Set_Stencil_Func(f); - g_stencilFuncBits = MapCmpFuncToBgfxStencilTest(static_cast(f)); + g_draw.stencilFuncBits = MapCmpFuncToBgfxStencilTest(static_cast(f)); UpdateShadowStencilState(); } void BgfxBackend::Set_Stencil_Ref(unsigned ref) { DX8Backend::Set_Stencil_Ref(ref); - g_stencilRef = ref; + g_draw.stencilRef = ref; UpdateShadowStencilState(); } void BgfxBackend::Set_Stencil_Mask(unsigned mask) { DX8Backend::Set_Stencil_Mask(mask); - g_stencilReadMask = mask; + g_draw.stencilReadMask = mask; UpdateShadowStencilState(); } @@ -4050,21 +3334,21 @@ void BgfxBackend::Set_Stencil_Write_Mask(unsigned mask) void BgfxBackend::Set_Stencil_Pass_Op(StencilOp op) { DX8Backend::Set_Stencil_Pass_Op(op); - g_stencilPassOpBits = MapStencilOpToBgfxPassZ(static_cast(op)); + g_draw.stencilPassOpBits = MapStencilOpToBgfx(static_cast(op), BGFX_STENCIL_OP_PASS_Z_SHIFT); UpdateShadowStencilState(); } void BgfxBackend::Set_Stencil_Fail_Op(StencilOp op) { DX8Backend::Set_Stencil_Fail_Op(op); - g_stencilFailOpBits = MapStencilOpToBgfxFailS(static_cast(op)); + g_draw.stencilFailOpBits = MapStencilOpToBgfx(static_cast(op), BGFX_STENCIL_OP_FAIL_S_SHIFT); UpdateShadowStencilState(); } void BgfxBackend::Set_Stencil_ZFail_Op(StencilOp op) { DX8Backend::Set_Stencil_ZFail_Op(op); - g_stencilZFailOpBits = MapStencilOpToBgfxFailZ(static_cast(op)); + g_draw.stencilZFailOpBits = MapStencilOpToBgfx(static_cast(op), BGFX_STENCIL_OP_FAIL_Z_SHIFT); UpdateShadowStencilState(); } @@ -4073,10 +3357,10 @@ void BgfxBackend::Set_Cull_Mode(CullMode mode) DX8Backend::Set_Cull_Mode(mode); switch (mode) { - case RB_CULL_CW: g_cullModeBits = 1; break; - case RB_CULL_CCW: g_cullModeBits = 2; break; + case RB_CULL_CW: g_draw.cullModeBits = 1; break; + case RB_CULL_CCW: g_draw.cullModeBits = 2; break; case RB_CULL_NONE: - default: g_cullModeBits = 0; break; + default: g_draw.cullModeBits = 0; break; } } @@ -4097,8 +3381,8 @@ void BgfxBackend::Set_Depth_Func(CompareFunc func) const unsigned idx = static_cast(func); if (idx < 9) { - g_currentBgfxState &= ~BGFX_STATE_DEPTH_TEST_MASK; - g_currentBgfxState |= kDepthMap[idx]; + g_draw.state &= ~BGFX_STATE_DEPTH_TEST_MASK; + g_draw.state |= kDepthMap[idx]; } } @@ -4106,14 +3390,14 @@ void BgfxBackend::Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass { DX8Backend::Set_Render_Target_With_Z(texture, ztexture); - if (texture == nullptr || !g_bgfxInitialized) + if (texture == nullptr || !g_device.initialized) { - g_renderToTexture = false; + g_views.renderToTexture = false; return; } - auto it = g_framebufferCache.find(texture); - if (it == g_framebufferCache.end()) + auto it = g_caches.framebuffer.find(texture); + if (it == g_caches.framebuffer.end()) { TextureClass * tex2d = texture->As_TextureClass(); const uint16_t w = tex2d ? static_cast(tex2d->Get_Width()) : 64; @@ -4130,8 +3414,8 @@ void BgfxBackend::Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass bgfx::FrameBufferHandle fb = bgfx::createFrameBuffer(2, attachments, true); BgfxFramebufferEntry entry = { fb, colorTex, w, h }; - g_framebufferCache[texture] = entry; - it = g_framebufferCache.find(texture); + g_caches.framebuffer[texture] = entry; + it = g_caches.framebuffer.find(texture); WWDEBUG_SAY(("[BgfxBackend] RTT framebuffer created %dx%d for tex=%p", w, h, texture)); @@ -4146,17 +3430,14 @@ void BgfxBackend::Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass 0x000000ff, 1.0f, 0); bgfx::touch(kBgfxRTTView); - g_renderToTexture = true; + g_views.renderToTexture = true; } void BgfxBackend::Clear_State_Overrides() { - g_blendOverrideActive = false; - g_atestOverrideActive = false; - g_suppressBgfxDraw = false; - g_colorWriteOverride = -1; - g_currentTexcoordSelect[0] = 0.0f; - // Do NOT clear g_currentTexcoordSelect[1] (terrain blend) here. + g_overrides.Reset(); + g_draw.texcoordSelect[0] = 0.0f; + // Do NOT clear g_draw.texcoordSelect[1] (terrain blend) here. // Override_Terrain_Blend is called from the shader manager BEFORE // Set_Shader, so clearing it in Set_Shader (which calls us) would // undo the terrain blend flag every frame. Terrain blend is reset @@ -4170,9 +3451,9 @@ void BgfxBackend::Set_Light_Environment(LightEnvironmentClass * light_env) if (light_env != nullptr) { const Vector3 & ambient = light_env->Get_Equivalent_Ambient(); - g_currentSceneAmbient[0] = ambient.X; - g_currentSceneAmbient[1] = ambient.Y; - g_currentSceneAmbient[2] = ambient.Z; + g_draw.sceneAmbient[0] = ambient.X; + g_draw.sceneAmbient[1] = ambient.Y; + g_draw.sceneAmbient[2] = ambient.Z; const int count = light_env->Get_Light_Count(); for (int i = 0; i < 4; ++i) @@ -4181,18 +3462,18 @@ void BgfxBackend::Set_Light_Environment(LightEnvironmentClass * light_env) { const Vector3 & dir = light_env->Get_Light_Direction(i); const Vector3 & dif = light_env->Get_Light_Diffuse(i); - g_currentLightDirs[i][0] = -dir.X; - g_currentLightDirs[i][1] = -dir.Y; - g_currentLightDirs[i][2] = -dir.Z; - g_currentLightDirs[i][3] = 1.0f; // enabled - g_currentLightColors[i][0] = dif.X; - g_currentLightColors[i][1] = dif.Y; - g_currentLightColors[i][2] = dif.Z; - g_currentLightColors[i][3] = 1.0f; + g_draw.lightDirs[i][0] = -dir.X; + g_draw.lightDirs[i][1] = -dir.Y; + g_draw.lightDirs[i][2] = -dir.Z; + g_draw.lightDirs[i][3] = 1.0f; // enabled + g_draw.lightColors[i][0] = dif.X; + g_draw.lightColors[i][1] = dif.Y; + g_draw.lightColors[i][2] = dif.Z; + g_draw.lightColors[i][3] = 1.0f; } else { - g_currentLightDirs[i][3] = 0.0f; // disabled + g_draw.lightDirs[i][3] = 0.0f; // disabled } } } @@ -4213,16 +3494,16 @@ void BgfxBackend::Set_Transform(TransformKind transform, const Matrix4x4 & m) switch (transform) { case RB_TRANSFORM_WORLD: - W3DMatrix4ToBgfx(m, g_bgfxWorld); + W3DMatrix4ToBgfx(m, g_frame.world); break; case RB_TRANSFORM_VIEW: - W3DMatrix4ToBgfx(m, g_bgfxView); - g_bgfxViewProjDirty = true; - g_2DOverlayActive = false; + W3DMatrix4ToBgfx(m, g_frame.view); + g_frame.cameraProjDirty = true; + g_views.overlay2DActive = false; break; case RB_TRANSFORM_PROJECTION: - W3DMatrix4ToBgfx(m, g_bgfxProj); - g_bgfxViewProjDirty = true; + W3DMatrix4ToBgfx(m, g_frame.proj); + g_frame.cameraProjDirty = true; break; default: break; @@ -4236,12 +3517,12 @@ void BgfxBackend::Set_Transform(TransformKind transform, const Matrix3D & m) switch (transform) { case RB_TRANSFORM_WORLD: - W3DMatrix3DToBgfx(m, g_bgfxWorld); + W3DMatrix3DToBgfx(m, g_frame.world); break; case RB_TRANSFORM_VIEW: - W3DMatrix3DToBgfx(m, g_bgfxView); - g_bgfxViewProjDirty = true; - g_2DOverlayActive = false; + W3DMatrix3DToBgfx(m, g_frame.view); + g_frame.cameraProjDirty = true; + g_views.overlay2DActive = false; break; default: break; @@ -4254,15 +3535,15 @@ void BgfxBackend::Set_Transform(TransformKind transform, const Matrix3D & m) void BgfxBackend::Set_World_Identity() { DX8Backend::Set_World_Identity(); - IdentityMatrix(g_bgfxWorld); + IdentityMatrix(g_frame.world); } void BgfxBackend::Set_View_Identity() { DX8Backend::Set_View_Identity(); - IdentityMatrix(g_bgfxView); - g_bgfxViewProjDirty = true; - g_2DOverlayActive = true; + IdentityMatrix(g_frame.view); + g_frame.cameraProjDirty = true; + g_views.overlay2DActive = true; } void BgfxBackend::Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & matrix, @@ -4270,8 +3551,8 @@ void BgfxBackend::Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & matrix, { DX8Backend::Set_Projection_Transform_With_Z_Bias(matrix, znear, zfar); - W3DMatrix4ToBgfx(matrix, g_bgfxProj); - g_bgfxViewProjDirty = true; + W3DMatrix4ToBgfx(matrix, g_frame.proj); + g_frame.cameraProjDirty = true; } @@ -4301,28 +3582,28 @@ void SubmitEngineDraw(unsigned short start_index, unsigned short min_vertex_index, unsigned short vertex_count) { - if (g_suppressBgfxDraw) + if (g_overrides.suppressDraw) { bgfx::discard(BGFX_DISCARD_ALL); return; } // Detect when the engine has restored the back buffer. The water/shadow // code calls DX8Wrapper::Set_Render_Target(nullptr) directly, bypassing - // g_renderBackend. Poll the DX8 state to keep g_renderToTexture in sync. - if (g_renderToTexture && !DX8Wrapper::Is_Render_To_Texture()) + // g_renderBackend. Poll the DX8 state to keep g_views.renderToTexture in sync. + if (g_views.renderToTexture && !DX8Wrapper::Is_Render_To_Texture()) { - g_renderToTexture = false; + g_views.renderToTexture = false; } - if (!g_bgfxInitialized) + if (!g_device.initialized) { return; } - if (!bgfx::isValid(g_currentBgfxProgram)) + if (!bgfx::isValid(g_draw.program)) { return; } - const bool have_vb = g_currentUseTransientVB || bgfx::isValid(g_currentBgfxVB); - const bool have_ib = g_currentUseTransientIB || bgfx::isValid(g_currentBgfxIB); + const bool have_vb = g_draw.useTransientVB || bgfx::isValid(g_draw.vb); + const bool have_ib = g_draw.useTransientIB || bgfx::isValid(g_draw.ib); if (!have_vb || !have_ib) { return; @@ -4336,14 +3617,14 @@ void SubmitEngineDraw(unsigned short start_index, // Secondary 2D detection: if the view matrix is identity and the // projection has no perspective (w-divide), this is a 2D overlay // draw even if Set_View_Identity wasn't called. This catches draws - // where DX8 state restores clear g_2DOverlayActive between the + // where DX8 state restores clear g_views.overlay2DActive between the // Set_View_Identity call and the actual Draw_Triangles. - bool is2D = g_2DOverlayActive; - if (!is2D && !g_renderToTexture && !g_waterOverrideActive - && !g_effectOverlayActive && !g_inSortFlush) + bool is2D = g_views.overlay2DActive; + if (!is2D && !g_views.renderToTexture && !g_views.waterOverrideActive + && !g_views.effectOverlayActive && !g_views.inSortFlush) { // Check if view matrix is identity (2D mode) - const float *v = g_bgfxView; + const float *v = g_frame.view; if (v[0] == 1.0f && v[5] == 1.0f && v[10] == 1.0f && v[15] == 1.0f && v[1] == 0.0f && v[2] == 0.0f && v[4] == 0.0f && v[6] == 0.0f) { @@ -4356,19 +3637,19 @@ void SubmitEngineDraw(unsigned short start_index, { submitView = kBgfxUIView; } - else if (g_renderToTexture) + else if (g_views.renderToTexture) { submitView = kBgfxRTTView; } - else if (g_waterOverrideActive) + else if (g_views.waterOverrideActive) { submitView = kBgfxWaterView; } - else if (g_effectOverlayActive) + else if (g_views.effectOverlayActive) { submitView = kBgfxEffectOverlayView; } - else if (g_inSortFlush) + else if (g_views.inSortFlush) { submitView = kBgfxEngineSortView; } @@ -4377,42 +3658,42 @@ void SubmitEngineDraw(unsigned short start_index, submitView = kBgfxEngineView; } - const float * worldMtx = g_inSortFlush ? g_bgfxSortWorld : g_bgfxWorld; + const float * worldMtx = g_views.inSortFlush ? g_frame.sortWorld : g_frame.world; // Push the engine view+projection when they change. setViewTransform // applies until the next change so we do not need to call it per // submit, only when the engine has updated either matrix. Sort view - // draws never touch g_bgfxView so the opaque view is never stomped. - if (!g_inSortFlush && !g_renderToTexture && !g_2DOverlayActive && g_bgfxViewProjDirty) + // draws never touch g_frame.view so the opaque view is never stomped. + if (!g_views.inSortFlush && !g_views.renderToTexture && !g_views.overlay2DActive && g_frame.cameraProjDirty) { // Capture the camera view+proj at the first opaque draw of each // frame. Later Set_Projection calls (water, shadows, sneak attack) - // may overwrite g_bgfxProj with a different frustum. We re-apply + // may overwrite g_frame.proj with a different frustum. We re-apply // the camera projection to view 1 at End_Scene time. - if (!g_bgfxCameraCaptured) + if (!g_frame.cameraCaptured) { - std::memcpy(g_bgfxCameraView, g_bgfxView, sizeof(g_bgfxCameraView)); - std::memcpy(g_bgfxCameraProj, g_bgfxProj, sizeof(g_bgfxCameraProj)); - g_bgfxCameraCaptured = true; + std::memcpy(g_frame.cameraView, g_frame.view, sizeof(g_frame.cameraView)); + std::memcpy(g_frame.cameraProj, g_frame.proj, sizeof(g_frame.cameraProj)); + g_frame.cameraCaptured = true; } // CSM: update light transform every frame so // shadows follow the camera as it pans/zooms. - if (!g_shadowLightCaptured) + if (!g_frame.shadowLightCaptured) { UpdateShadowLightTransform(); } - bgfx::setViewTransform(kBgfxEngineView, g_bgfxView, g_bgfxProj); + bgfx::setViewTransform(kBgfxEngineView, g_frame.view, g_frame.proj); // Shadow-volume view shares the engine camera; push the same // view+proj so the extrusion geometry lands where the opaque // geometry in view 1 landed. - bgfx::setViewTransform(kBgfxShadowVolumeView, g_bgfxView, g_bgfxProj); - g_bgfxViewProjDirty = false; + bgfx::setViewTransform(kBgfxShadowVolumeView, g_frame.view, g_frame.proj); + g_frame.cameraProjDirty = false; } // During RTT, push the current (reflected/shadow) view+proj to the RTT view. - if (g_renderToTexture && g_bgfxViewProjDirty) + if (g_views.renderToTexture && g_frame.cameraProjDirty) { - bgfx::setViewTransform(kBgfxRTTView, g_bgfxView, g_bgfxProj); - g_bgfxViewProjDirty = false; + bgfx::setViewTransform(kBgfxRTTView, g_frame.view, g_frame.proj); + g_frame.cameraProjDirty = false; } // World matrix is per-submit. bgfx consumes the value at submit time @@ -4426,20 +3707,20 @@ void SubmitEngineDraw(unsigned short start_index, // index during the draw, i.e. effective_vertex = VB[IB[start+i] + // base]. The bgfx equivalent is setVertexBuffer's _startVertex // parameter, NOT an IB offset. Previously SubmitEngineDraw was - // incorrectly adding g_currentIBOffset to the IB start - that + // incorrectly adding g_draw.ibOffset to the IB start - that // shifted which indices were read instead of which vertex they // resolved to, so meshes with a non-zero base_vertex_offset drew // garbled geometry from other meshes' vertex data. - const uint32_t base_vertex = static_cast(g_currentIBOffset); - if (g_currentUseTransientVB) + const uint32_t base_vertex = static_cast(g_draw.ibOffset); + if (g_draw.useTransientVB) { - if (g_inSortFlush) + if (g_views.inSortFlush) { // Sort flush: use 2-arg overload (binds entire buffer). // Sort indices are absolute offsets into the full transient, // so no base vertex offset is needed. The 4-arg overload // would restrict the vertex range and clip high indices. - bgfx::setVertexBuffer(0, &g_currentTransientVB); + bgfx::setVertexBuffer(0, &g_draw.transientVB); } else { @@ -4448,24 +3729,24 @@ void SubmitEngineDraw(unsigned short start_index, // different base offset (from Set_Index_Buffer_Index_Offset). // Without this, all mesh parts read from vertex 0 and // infantry/vehicles are invisible or garbled. - bgfx::setVertexBuffer(0, &g_currentTransientVB, + bgfx::setVertexBuffer(0, &g_draw.transientVB, base_vertex, vertex_count); } } else { - bgfx::setVertexBuffer(0, g_currentBgfxVB, base_vertex, vertex_count); + bgfx::setVertexBuffer(0, g_draw.vb, base_vertex, vertex_count); } - if (g_currentUseTransientIB) + if (g_draw.useTransientIB) { - bgfx::setIndexBuffer(&g_currentTransientIB, + bgfx::setIndexBuffer(&g_draw.transientIB, start_index, static_cast(polygon_count) * 3); } else { - bgfx::setIndexBuffer(g_currentBgfxIB, + bgfx::setIndexBuffer(g_draw.ib, start_index, static_cast(polygon_count) * 3); } @@ -4496,10 +3777,10 @@ void SubmitEngineDraw(unsigned short start_index, // sorting renderer's Apply_Render_State calls DX8Wrapper::Set_Texture // which has a cache check — if the texture pointer matches the cached // one, it skips BgfxBackend::Set_Texture entirely. This leaves - // g_currentBgfxTexture0-3 stale (e.g., pointing at font glyphs instead + // g_draw.tex[0]-3 stale (e.g., pointing at font glyphs instead // of particle textures). Force-sync bgfx handles from DX8Wrapper's // current state before each sorted draw. - if (g_inSortFlush) + if (g_views.inSortFlush) { RenderStateStruct sortRS; DX8Wrapper::Get_Render_State(sortRS); @@ -4509,13 +3790,13 @@ void SubmitEngineDraw(unsigned short start_index, bgfx::TextureHandle h = EnsureBgfxTexture( static_cast(sortRS.Textures[si])); if (!bgfx::isValid(h)) - h = g_defaultWhiteTexture; + h = g_device.defaultWhiteTexture; switch (si) { - case 0: g_currentBgfxTexture0 = h; break; - case 1: g_currentBgfxTexture1 = h; break; - case 2: g_currentBgfxTexture2 = h; break; - case 3: g_currentBgfxTexture3 = h; break; + case 0: g_draw.tex[0] = h; break; + case 1: g_draw.tex[1] = h; break; + case 2: g_draw.tex[2] = h; break; + case 3: g_draw.tex[3] = h; break; } } } @@ -4534,32 +3815,32 @@ void SubmitEngineDraw(unsigned short start_index, if (rs.LightEnable[li]) { const D3DLIGHT8 & dl = rs.Lights[li]; - g_currentLightDirs[li][0] = -dl.Direction.x; - g_currentLightDirs[li][1] = -dl.Direction.y; - g_currentLightDirs[li][2] = -dl.Direction.z; - g_currentLightDirs[li][3] = 1.0f; - g_currentLightColors[li][0] = dl.Diffuse.r; - g_currentLightColors[li][1] = dl.Diffuse.g; - g_currentLightColors[li][2] = dl.Diffuse.b; - g_currentLightColors[li][3] = 1.0f; + g_draw.lightDirs[li][0] = -dl.Direction.x; + g_draw.lightDirs[li][1] = -dl.Direction.y; + g_draw.lightDirs[li][2] = -dl.Direction.z; + g_draw.lightDirs[li][3] = 1.0f; + g_draw.lightColors[li][0] = dl.Diffuse.r; + g_draw.lightColors[li][1] = dl.Diffuse.g; + g_draw.lightColors[li][2] = dl.Diffuse.b; + g_draw.lightColors[li][3] = 1.0f; } else { - g_currentLightDirs[li][3] = 0.0f; + g_draw.lightDirs[li][3] = 0.0f; } } const unsigned ambientColor = DX8Wrapper::Get_DX8_Render_State(D3DRS_AMBIENT); - g_currentSceneAmbient[0] = ((ambientColor >> 16) & 0xFF) / 255.0f; - g_currentSceneAmbient[1] = ((ambientColor >> 8) & 0xFF) / 255.0f; - g_currentSceneAmbient[2] = ((ambientColor >> 0) & 0xFF) / 255.0f; - } - if (bgfx::isValid(g_uLightDirs)) - bgfx::setUniform(g_uLightDirs, g_currentLightDirs, 4); - if (bgfx::isValid(g_uLightColors)) - bgfx::setUniform(g_uLightColors, g_currentLightColors, 4); - if (bgfx::isValid(g_uSceneAmbient)) - bgfx::setUniform(g_uSceneAmbient, g_currentSceneAmbient); - if (bgfx::isValid(g_uLightingEnabled)) + g_draw.sceneAmbient[0] = ((ambientColor >> 16) & 0xFF) / 255.0f; + g_draw.sceneAmbient[1] = ((ambientColor >> 8) & 0xFF) / 255.0f; + g_draw.sceneAmbient[2] = ((ambientColor >> 0) & 0xFF) / 255.0f; + } + if (bgfx::isValid(g_uniforms.uLightDirs)) + bgfx::setUniform(g_uniforms.uLightDirs, g_draw.lightDirs, 4); + if (bgfx::isValid(g_uniforms.uLightColors)) + bgfx::setUniform(g_uniforms.uLightColors, g_draw.lightColors, 4); + if (bgfx::isValid(g_uniforms.uSceneAmbient)) + bgfx::setUniform(g_uniforms.uSceneAmbient, g_draw.sceneAmbient); + if (bgfx::isValid(g_uniforms.uLightingEnabled)) { // TheSuperHackers @bugfix bobtista 15/04/2026 force lighting off // for additive-blend draws. Particle / dazzle / scanner sprites @@ -4569,7 +3850,7 @@ void SubmitEngineDraw(unsigned short start_index, // tex * lit_color, which renders these effects at full intensity // even when they should be invisible — producing bright white // halos around things like the microwave-scanner dome. - const uint64_t blendBitsForLight = g_currentBgfxState & BGFX_STATE_BLEND_MASK; + const uint64_t blendBitsForLight = g_draw.state & BGFX_STATE_BLEND_MASK; const uint64_t kAddONE_ONE = BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_ONE); const uint64_t kAddSA_ONE = BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_SRC_ALPHA, @@ -4580,14 +3861,14 @@ void SubmitEngineDraw(unsigned short start_index, const uint64_t kAlphaSA_ISA = BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_SRC_ALPHA, BGFX_STATE_BLEND_INV_SRC_ALPHA); if (blendBitsForLight == kAddONE_ONE || blendBitsForLight == kAddSA_ONE - || (blendBitsForLight == kAlphaSA_ISA && g_currentTssOps0[0] < 1.5f)) + || (blendBitsForLight == kAlphaSA_ISA && g_draw.tssOps0[0] < 1.5f)) { float forced[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; - bgfx::setUniform(g_uLightingEnabled, forced); + bgfx::setUniform(g_uniforms.uLightingEnabled, forced); } else { - bgfx::setUniform(g_uLightingEnabled, g_currentLightingEnabled); + bgfx::setUniform(g_uniforms.uLightingEnabled, g_draw.lightingEnabled); } } @@ -4605,7 +3886,7 @@ void SubmitEngineDraw(unsigned short start_index, if (tci & D3DTSS_TCI_CAMERASPACEPOSITION) { shroudDetected = true; - g_currentTexcoordSelect[2] = 1.0f; + g_draw.texcoordSelect[2] = 1.0f; // Extract shroud offset+scale by cancelling inv(view) // from the texture matrix: view * texMtx = T * S. D3DMATRIX texMtx; @@ -4629,22 +3910,22 @@ void SubmitEngineDraw(unsigned short start_index, ts.m[0][0], ts.m[1][1] }; - if (bgfx::isValid(g_uShroudParams)) - bgfx::setUniform(g_uShroudParams, shroudParams); + if (bgfx::isValid(g_uniforms.uShroudParams)) + bgfx::setUniform(g_uniforms.uShroudParams, shroudParams); } } } if (!shroudDetected) { - g_currentTexcoordSelect[2] = 0.0f; + g_draw.texcoordSelect[2] = 0.0f; } } - if (bgfx::isValid(g_uTexcoordSelect)) - bgfx::setUniform(g_uTexcoordSelect, g_currentTexcoordSelect); + if (bgfx::isValid(g_uniforms.uTexcoordSelect)) + bgfx::setUniform(g_uniforms.uTexcoordSelect, g_draw.texcoordSelect); - uint64_t state = (g_currentBgfxState != 0) - ? g_currentBgfxState + uint64_t state = (g_draw.state != 0) + ? g_draw.state : (BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); state |= BGFX_STATE_MSAA; @@ -4663,18 +3944,18 @@ void SubmitEngineDraw(unsigned short start_index, } // Apply post-ShaderClass blend override (terrain blend, W3DCustomEdging, etc.) - if (g_blendOverrideActive) + if (g_overrides.blendActive) { state &= ~BGFX_STATE_BLEND_MASK; - state |= g_blendOverrideBits; + state |= g_overrides.blendBits; } // Apply post-ShaderClass alpha test override - if (g_atestOverrideActive) + if (g_overrides.atestActive) { - if (bgfx::isValid(g_uAtestParams)) + if (bgfx::isValid(g_uniforms.uAtestParams)) { - float atestParams[4] = { g_atestOverrideRef, 0.0f, 0.0f, 0.0f }; - bgfx::setUniform(g_uAtestParams, atestParams); + float atestParams[4] = { g_overrides.atestRef, 0.0f, 0.0f, 0.0f }; + bgfx::setUniform(g_uniforms.uAtestParams, atestParams); } } @@ -4687,18 +3968,18 @@ void SubmitEngineDraw(unsigned short start_index, // Apply color write mask override from Set_Color_Write_Enable. // This handles both alpha-only writes (shoreline depth gradient) // and RGB-only writes (terrain with alpha writes disabled). - if (g_colorWriteOverride >= 0) + if (g_overrides.colorWriteOverride >= 0) { state &= ~(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); - state |= static_cast(g_colorWriteOverride); + state |= static_cast(g_overrides.colorWriteOverride); } - if (g_waterOverrideActive) + if (g_views.waterOverrideActive) { - g_waterOverrideActive = false; + g_views.waterOverrideActive = false; } - if (g_shadowVolumeActive && bgfx::isValid(g_shadowVolumeProgram)) + if (g_views.shadowVolumeActive && bgfx::isValid(g_device.shadowVolumeProgram)) { bgfx::discard(BGFX_DISCARD_ALL); return; @@ -4709,17 +3990,17 @@ void SubmitEngineDraw(unsigned short start_index, // CSM: compute light view-projection once for both // receiver uniform and caster submit. float lightVP[16]; - bx::mtxMul(lightVP, g_shadowLightView, g_shadowLightProj); + bx::mtxMul(lightVP, g_frame.shadowLightView, g_frame.shadowLightProj); - if (bgfx::isValid(g_uShadowLightViewProj)) + if (bgfx::isValid(g_uniforms.uShadowLightViewProj)) { // Compose model * lightView * lightProj per-draw. - // Use the CORRECT model matrix: g_bgfxSortWorldRaw for sorted - // draws (no camera view baked in), g_bgfxWorld for opaque. + // Use the CORRECT model matrix: g_frame.sortWorldRaw for sorted + // draws (no camera view baked in), g_frame.world for opaque. float shadowMVP[16]; - const float * modelMtx = g_inSortFlush ? g_bgfxSortWorldRaw : worldMtx; + const float * modelMtx = g_views.inSortFlush ? g_frame.sortWorldRaw : worldMtx; bx::mtxMul(shadowMVP, modelMtx, lightVP); - bgfx::setUniform(g_uShadowLightViewProj, shadowMVP); + bgfx::setUniform(g_uniforms.uShadowLightViewProj, shadowMVP); } // TheSuperHackers @fix bobtista 19/04/2026 bind s_shadowMap @@ -4727,24 +4008,24 @@ void SubmitEngineDraw(unsigned short start_index, // comparison sampler unconditionally, so slot 4 must be bound for // every submit of the uber program regardless of whether the // lightViewProj uniform is present. - if (bgfx::isValid(g_shadowMapDepth) && bgfx::isValid(g_sShadowMap)) + if (bgfx::isValid(g_device.shadowMapDepth) && bgfx::isValid(g_uniforms.sShadowMap)) { - bgfx::setTexture(4, g_sShadowMap, g_shadowMapDepth); + bgfx::setTexture(4, g_uniforms.sShadowMap, g_device.shadowMapDepth); } // The tree / grass sway shader takes over the program slot // and uploads its own constants when active. Otherwise fall back - // to whatever ShaderClass picked (g_currentBgfxProgram). - bgfx::ProgramHandle program = g_currentBgfxProgram; - if (g_treeShaderActive && bgfx::isValid(g_treeProgram)) - { - program = g_treeProgram; - if (bgfx::isValid(g_uSwayTable)) - bgfx::setUniform(g_uSwayTable, g_currentSwayTable, kSwayTableEntries); - if (bgfx::isValid(g_uShroudOffset)) - bgfx::setUniform(g_uShroudOffset, g_currentShroudOffset); - if (bgfx::isValid(g_uShroudScale)) - bgfx::setUniform(g_uShroudScale, g_currentShroudScale); + // to whatever ShaderClass picked (g_draw.program). + bgfx::ProgramHandle program = g_draw.program; + if (g_views.treeShaderActive && bgfx::isValid(g_device.treeProgram)) + { + program = g_device.treeProgram; + if (bgfx::isValid(g_uniforms.uSwayTable)) + bgfx::setUniform(g_uniforms.uSwayTable, g_draw.swayTable, kSwayTableEntries); + if (bgfx::isValid(g_uniforms.uShroudOffset)) + bgfx::setUniform(g_uniforms.uShroudOffset, g_draw.shroudOffset); + if (bgfx::isValid(g_uniforms.uShroudScale)) + bgfx::setUniform(g_uniforms.uShroudScale, g_draw.shroudScale); } bgfx::submit(submitView, program); @@ -4755,53 +4036,53 @@ void SubmitEngineDraw(unsigned short start_index, // the caster rasterizes from the light's POV. const bool isShadowCaster = (submitView == kBgfxEngineView || submitView == kBgfxEngineSortView) - && !g_2DOverlayActive; - const bool hasVB = g_currentUseTransientVB || bgfx::isValid(g_currentBgfxVB); + && !g_views.overlay2DActive; + const bool hasVB = g_draw.useTransientVB || bgfx::isValid(g_draw.vb); if (isShadowCaster - && bgfx::isValid(g_shadowCasterProgram) + && bgfx::isValid(g_device.shadowCasterProgram) && hasVB) { // Re-bind VB/IB/transform (bgfx consumes them per-submit). - if (g_currentUseTransientVB) + if (g_draw.useTransientVB) { - if (g_inSortFlush) - bgfx::setVertexBuffer(0, &g_currentTransientVB); + if (g_views.inSortFlush) + bgfx::setVertexBuffer(0, &g_draw.transientVB); else - bgfx::setVertexBuffer(0, &g_currentTransientVB, - static_cast(g_currentIBOffset), + bgfx::setVertexBuffer(0, &g_draw.transientVB, + static_cast(g_draw.ibOffset), vertex_count); } else { - bgfx::setVertexBuffer(0, g_currentBgfxVB, - static_cast(g_currentIBOffset), + bgfx::setVertexBuffer(0, g_draw.vb, + static_cast(g_draw.ibOffset), vertex_count); } - if (g_currentUseTransientIB) - bgfx::setIndexBuffer(&g_currentTransientIB, + if (g_draw.useTransientIB) + bgfx::setIndexBuffer(&g_draw.transientIB, start_index, static_cast(polygon_count) * 3); else - bgfx::setIndexBuffer(g_currentBgfxIB, + bgfx::setIndexBuffer(g_draw.ib, start_index, static_cast(polygon_count) * 3); // Use the world transform used by the just-issued engine - // submit. For sort-flush draws that's g_bgfxSortWorld (which + // submit. For sort-flush draws that's g_frame.sortWorld (which // has the sort's batch transform baked in). For regular - // opaque it's g_bgfxWorld. + // opaque it's g_frame.world. bgfx::setTransform(worldMtx); { float casterMVP[16]; - const float * casterModel = g_inSortFlush ? g_bgfxSortWorldRaw : worldMtx; + const float * casterModel = g_views.inSortFlush ? g_frame.sortWorldRaw : worldMtx; bx::mtxMul(casterMVP, casterModel, lightVP); - bgfx::setUniform(g_uShadowLightViewProj, casterMVP); + bgfx::setUniform(g_uniforms.uShadowLightViewProj, casterMVP); } const uint64_t casterState = BGFX_STATE_WRITE_Z | BGFX_STATE_DEPTH_TEST_LESS | BGFX_STATE_CULL_CW; bgfx::setState(casterState); - bgfx::submit(kBgfxShadowMapView, g_shadowCasterProgram); + bgfx::submit(kBgfxShadowMapView, g_device.shadowCasterProgram); } } } @@ -4816,9 +4097,9 @@ void BgfxBackend::Draw_Triangles(unsigned short start_index, // If DX8Wrapper::Draw_Sorting_IB_VB already submitted // the draw with correctly remapped args against its internal dynamic // buffers, skip the outer submit. - if (g_skipNextSubmitEngineDraw) + if (g_views.skipNextSubmitEngineDraw) { - g_skipNextSubmitEngineDraw = false; + g_views.skipNextSubmitEngineDraw = false; return; } SubmitEngineDraw(start_index, polygon_count, min_vertex_index, vertex_count); @@ -4832,9 +4113,9 @@ void BgfxBackend::Draw_Triangles(unsigned int buffer_type, { DX8Backend::Draw_Triangles(buffer_type, start_index, polygon_count, min_vertex_index, vertex_count); - if (g_skipNextSubmitEngineDraw) + if (g_views.skipNextSubmitEngineDraw) { - g_skipNextSubmitEngineDraw = false; + g_views.skipNextSubmitEngineDraw = false; return; } SubmitEngineDraw(start_index, polygon_count, min_vertex_index, vertex_count); @@ -4850,9 +4131,9 @@ void BgfxBackend::Draw_Strip(unsigned short start_index, { DX8Backend::Draw_Strip(start_index, index_count, min_vertex_index, vertex_count); - if (g_skipNextSubmitEngineDraw) + if (g_views.skipNextSubmitEngineDraw) { - g_skipNextSubmitEngineDraw = false; + g_views.skipNextSubmitEngineDraw = false; return; } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h index 16ebf9ed7c6..0b997b4a04f 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h @@ -105,7 +105,7 @@ class BgfxBackend : public DX8Backend virtual void End_Sorted_Batch_Pass() override; virtual void Capture_Sorted_Batch_Transforms(const Matrix4x4 & world, const Matrix4x4 & view) override; - virtual void Capture_Sorted_Batch_Light(const D3DLIGHT8 & light, bool enabled) override; + virtual void Capture_Sorted_Batch_Light(const RenderBackendLight & light, bool enabled) override; virtual void Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, const DynamicIBAccessClass & dyn_ib, unsigned short polygon_count, @@ -123,8 +123,8 @@ class BgfxBackend : public DX8Backend virtual void Set_Light_Environment(LightEnvironmentClass * light_env) override; virtual void Set_Ambient(const Vector3 & color) override; virtual void Set_Blend_Factors(BlendFactor src, BlendFactor dest) override; - virtual void Override_Blend(unsigned srcBlend, unsigned dstBlend) override; - virtual void Override_Alpha_Test(bool enable, unsigned ref, unsigned func) override; + virtual void Override_Blend(BlendFactor srcBlend, BlendFactor dstBlend) override; + virtual void Override_Alpha_Test(bool enable, unsigned ref, CompareFunc func) override; virtual void Override_Alpha_Blend_Enable(bool enable) override; virtual void Override_Texcoord_Index(unsigned stage, unsigned uvIndex) override; virtual void Override_Terrain_Blend(bool enable) override; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackendState.h b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackendState.h new file mode 100644 index 00000000000..0513c457b36 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackendState.h @@ -0,0 +1,307 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program 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, either version 3 of the License, or +** (at your option) any later version. +*/ + +// TheSuperHackers @refactor bobtista 21/04/2026 Shared render-state structs for the bgfx backend. Included by BgfxBackend.cpp (which defines the instances) and BgfxBackendTextures.cpp (which references them). See BgfxBackend.cpp for the design rationale of the 7-struct split. +#pragma once + +#include +#include +#include + +// d3d8 types leak into D3DPtrInfo (we shadow the D3D8 base-texture pointer). +#include + +#include + +#include "ww3dformat.h" + +// Forward declarations — full headers are included by the .cpp files that need the method bodies. +class TextureBaseClass; +class TextureClass; +class VertexBufferClass; +class IndexBufferClass; +class DynamicVBAccessClass; +class DynamicIBAccessClass; + +// --- Helper types ----------------------------------------------------------- + +struct BgfxFramebufferEntry +{ + bgfx::FrameBufferHandle fb; + bgfx::TextureHandle colorTex; + uint16_t width; + uint16_t height; +}; + +struct BgfxVbCacheEntry { + bgfx::DynamicVertexBufferHandle handle; + uint32_t num_verts; + uint32_t stride; +}; + +struct BgfxIbCacheEntry { + bgfx::DynamicIndexBufferHandle handle; + uint32_t num_indices; +}; + +struct D3DPtrInfo { IDirect3DBaseTexture8 * ptr; uint16_t w; uint16_t h; }; + +struct PendingTransientVB +{ + bool valid; + const DynamicVBAccessClass * owner; + bgfx::TransientVertexBuffer tvb; +}; + +struct PendingTransientIB +{ + bool valid; + const DynamicIBAccessClass * owner; + bgfx::TransientIndexBuffer tib; +}; + +// --- The 7 render-state structs -------------------------------------------- + +// Device: created in Initialize(), released in Shutdown(). Never reset during frames. +struct BgfxDevice +{ + bool initialized = false; + HWND window = nullptr; + int width = 800; + int height = 600; + // bgfx debug-log callback is a file-local global in BgfxBackend.cpp (g_bgfxCallback); it needs the full BgfxLoggingCallback class definition and only BgfxBackend.cpp uses it. + + // Programs + bgfx::ProgramHandle uberProgram = BGFX_INVALID_HANDLE; // single uber program; all TSS combos via uniforms. + bgfx::ProgramHandle passthroughProgram = BGFX_INVALID_HANDLE; + bgfx::ProgramHandle treeProgram = BGFX_INVALID_HANDLE; // vs_trees + fs_uber; enabled via Set_Tree_Vertex_Shader_Active for swaying grass, else reverts to uberProgram. + bgfx::ProgramHandle shadowCasterProgram = BGFX_INVALID_HANDLE; + bgfx::ProgramHandle shadowVolumeProgram = BGFX_INVALID_HANDLE; + bgfx::ProgramHandle shadowApplyProgram = BGFX_INVALID_HANDLE; + + // Shadow map RT + bgfx::FrameBufferHandle shadowMapFB = BGFX_INVALID_HANDLE; + bgfx::TextureHandle shadowMapDepth = BGFX_INVALID_HANDLE; + + // Default textures + helper VB + bgfx::TextureHandle defaultWhiteTexture = BGFX_INVALID_HANDLE; + bgfx::TextureHandle defaultTransparentTexture = BGFX_INVALID_HANDLE; + // Static VB for a fullscreen black triangle submitted on view 0 every frame. bgfx::setViewClear alone does not emit ClearRenderTargetView on our backbuffer view when only activated via bgfx::touch, so persisted pixels can leak between frames. A real submit makes bgfx process the view fully. See commit ad575e6be. + bgfx::VertexBufferHandle fullscreenClearVB = BGFX_INVALID_HANDLE; + + // Vertex layouts + bgfx::VertexLayout triangleLayout; + bgfx::VertexLayout layoutP; + bgfx::VertexLayout layoutPN; + bgfx::VertexLayout layoutPNT1; + bgfx::VertexLayout layoutPNT2; + bgfx::VertexLayout layoutPT1; + bgfx::VertexLayout layoutPDT1; + bgfx::VertexLayout layoutPNDT1; + bgfx::VertexLayout layoutPNDT2; +}; + +// Uniforms: all uniform handles. Created once in Initialize, never reset. +struct BgfxUniforms +{ + // Texture samplers + bgfx::UniformHandle sTex0 = BGFX_INVALID_HANDLE; + bgfx::UniformHandle sTex1 = BGFX_INVALID_HANDLE; + bgfx::UniformHandle sTex2 = BGFX_INVALID_HANDLE; + bgfx::UniformHandle sTex3 = BGFX_INVALID_HANDLE; + bgfx::UniformHandle sShadowMap = BGFX_INVALID_HANDLE; + bgfx::UniformHandle sCloudMap = BGFX_INVALID_HANDLE; + + // Material / TSS + bgfx::UniformHandle uMatDiffuse = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uMatEmissive = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uAtestParams = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uTssOps0 = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uTssOps1 = BGFX_INVALID_HANDLE; + + // Lighting + bgfx::UniformHandle uLightDirs = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uLightColors = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uSceneAmbient = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uLightingEnabled = BGFX_INVALID_HANDLE; + + // Misc per-draw flags / params + bgfx::UniformHandle uTexcoordSelect = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uGrayscaleEnable = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uSwayTable = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uShroudOffset = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uShroudScale = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uShroudParams = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uCloudParams = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uShadowLightViewProj = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uShadowColor = BGFX_INVALID_HANDLE; + // Polygon-offset equivalent for stencil-shadow-volume passes. bgfx has no state bit for polygon offset, so the post-projection Z bias is applied in vs_shadow_volume.sc. .x is the offset; negative = toward the camera. + bgfx::UniformHandle uShadowBias = BGFX_INVALID_HANDLE; +}; + +// Draw: per-draw pipeline state + uniform values consumed by SubmitEngineDraw. +struct BgfxDraw +{ + // Pipeline state + bgfx::ProgramHandle program = BGFX_INVALID_HANDLE; + uint64_t state = 0; + + // Textures + per-stage sampler flags + bgfx::TextureHandle tex[4] = { + BGFX_INVALID_HANDLE, BGFX_INVALID_HANDLE, + BGFX_INVALID_HANDLE, BGFX_INVALID_HANDLE + }; + uint32_t samplerFlags[4] = { 0, 0, 0, 0 }; + + // Buffers (static + transient variants) + bgfx::DynamicVertexBufferHandle vb = BGFX_INVALID_HANDLE; + bgfx::DynamicIndexBufferHandle ib = BGFX_INVALID_HANDLE; + unsigned short ibOffset = 0; + bool useTransientVB = false; + bgfx::TransientVertexBuffer transientVB = {}; + bool useTransientIB = false; + bgfx::TransientIndexBuffer transientIB = {}; + PendingTransientVB pendingVB = { false, nullptr, {} }; + PendingTransientIB pendingIB = { false, nullptr, {} }; + + // Cull + stencil + int cullModeBits = 0; // 0=NONE, 1=CW, 2=CCW + bool stencilEnabled = false; + uint32_t stencilRef = 0; + uint32_t stencilReadMask = 0xFF; + uint32_t stencilFuncBits = BGFX_STENCIL_TEST_ALWAYS; + uint32_t stencilPassOpBits = BGFX_STENCIL_OP_PASS_Z_KEEP; + uint32_t stencilFailOpBits = BGFX_STENCIL_OP_FAIL_S_KEEP; + uint32_t stencilZFailOpBits = BGFX_STENCIL_OP_FAIL_Z_KEEP; + uint32_t shadowStencilFront = BGFX_STENCIL_NONE; + uint32_t shadowStencilBack = BGFX_STENCIL_NONE; + + // Uniform VALUES pushed via bgfx::setUniform per submit + float matDiffuse[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + float matEmissive[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float grayscaleEnable[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float tssOps0[4] = { 3.0f, 3.0f, 0.0f, 0.0f }; + float tssOps1[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float atestRef = 0.0f; + float texcoordSelect[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float cloudParams[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + bgfx::TextureHandle cloudTex = BGFX_INVALID_HANDLE; + float lightDirs[4][4] = { + { 0.35f, 0.55f, 0.75f, 1.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f } + }; + float lightColors[4][4] = { + { 0.75f, 0.75f, 0.75f, 1.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f } + }; + float sceneAmbient[4] = { 0.45f, 0.45f, 0.45f, 1.0f }; + float lightingEnabled[4] = { 1.0f, 0.0f, 0.0f, 0.0f }; + float swayTable[11][4] = {{0}}; + float shroudOffset[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float shroudScale[4] = { 0.0f, 0.0f, 1.0f, 1.0f }; +}; + +// Overrides: transient per-shader overrides. Reset by Clear_State_Overrides (called from Set_Shader). +struct BgfxOverrides +{ + bool blendActive = false; + uint64_t blendBits = 0; + bool atestActive = false; + float atestRef = 0.0f; + bool suppressDraw = false; + int colorWriteOverride = -1; + + void Reset() + { + blendActive = false; + blendBits = 0; + atestActive = false; + atestRef = 0.0f; + suppressDraw = false; + colorWriteOverride = -1; + } + + void SetBlend(uint64_t bits) + { + blendActive = true; + blendBits = bits; + } +}; + +// Views: flags that control which bgfx view a submit routes to + ephemeral per-pass state. +struct BgfxViewFlags +{ + bool overlay2DActive = false; + bool renderToTexture = false; + bool waterOverrideActive = false; + bool effectOverlayActive = false; + bool inSortFlush = false; + bool treeShaderActive = false; + bool shadowVolumeActive = false; + bool skipNextSubmitEngineDraw = false; +}; + +// Frame: per-frame matrices and captured view/proj copies. +struct BgfxFrame +{ + float world[16] = {}; + float view[16] = {}; + float proj[16] = {}; + bool cameraProjDirty = true; + + float cameraView[16] = {}; + float cameraProj[16] = {}; + bool cameraCaptured = false; + + float sortWorld[16] = {}; + float sortWorldRaw[16] = {}; + float sortProj[16] = {}; + bool sortProjCaptured = false; + + float shadowLightView[16] = {}; + float shadowLightProj[16] = {}; + bool shadowLightCaptured = false; + + float shadowSunPosX = 0.0f; + float shadowSunPosY = 0.0f; + float shadowSunPosZ = 1500.0f; + bool shadowSunPosSet = false; +}; + +// Caches: long-lived resource maps. +struct BgfxCaches +{ + std::unordered_map vb; + std::unordered_map ib; + std::unordered_map texture; + std::unordered_map d3dPtr; + std::unordered_map framebuffer; + std::unordered_map renderTarget; + std::vector deferredDestroys; // current frame + std::vector deferredDestroysPrev; // previous frame, safe to destroy +}; + +// --- Shared globals --------------------------------------------------------- +// Defined in BgfxBackend.cpp. +extern BgfxDevice g_device; +extern BgfxUniforms g_uniforms; +extern BgfxDraw g_draw; +extern BgfxOverrides g_overrides; +extern BgfxViewFlags g_views; +extern BgfxFrame g_frame; +extern BgfxCaches g_caches; + +// --- Helpers shared across BgfxBackend*.cpp --------------------------------- +// Defined in BgfxBackendTextures.cpp. +bgfx::TextureHandle EnsureBgfxTexture(TextureBaseClass * tex); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackendTextures.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackendTextures.cpp new file mode 100644 index 00000000000..c211725d97e --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackendTextures.cpp @@ -0,0 +1,539 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program 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, either version 3 of the License, or +** (at your option) any later version. +*/ + +// TheSuperHackers @refactor bobtista 21/04/2026 Texture-cache half of the +// bgfx backend. Split out of BgfxBackend.cpp to keep each file under +// reasonable length. Shared state lives in BgfxBackendState.h. +// +// Contents: format translator, alpha-fixup helper, EnsureBgfxTexture +// (the core upload-or-reuse path), plus the three BgfxBackend class +// methods that touch the cache: Invalidate_Cached_Texture, +// Release_Cached_Texture, Capture_Shroud_Texture. + +#include +#include +#include +#include + +#include + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include + +#include "dx8wrapper.h" +#include "texture.h" +#include "surfaceclass.h" +#include "wwdebug.h" + +#include "BgfxBackend.h" +#include "BgfxBackendState.h" + +namespace { + +bgfx::TextureFormat::Enum TranslateWW3DFormat(WW3DFormat fmt) +{ + switch (fmt) + { + case WW3D_FORMAT_A8R8G8B8: + case WW3D_FORMAT_X8R8G8B8: + // D3D8 stores ARGB as 0xAARRGGBB which on little-endian + // memory is BB GG RR AA - matches bgfx BGRA8 byte order. + return bgfx::TextureFormat::BGRA8; + case WW3D_FORMAT_R5G6B5: return bgfx::TextureFormat::R5G6B5; + case WW3D_FORMAT_A1R5G5B5: return bgfx::TextureFormat::BGR5A1; + case WW3D_FORMAT_A4R4G4B4: return bgfx::TextureFormat::BGRA4; + case WW3D_FORMAT_A8: return bgfx::TextureFormat::A8; + case WW3D_FORMAT_L8: return bgfx::TextureFormat::R8; + case WW3D_FORMAT_DXT1: return bgfx::TextureFormat::BC1; + case WW3D_FORMAT_DXT2: + case WW3D_FORMAT_DXT3: return bgfx::TextureFormat::BC2; + case WW3D_FORMAT_DXT4: + case WW3D_FORMAT_DXT5: return bgfx::TextureFormat::BC3; + default: return bgfx::TextureFormat::Unknown; + } +} +// TheSuperHackers @refactor bobtista 20/04/2026 D3D8 ignores the X byte of X8R8G8B8 and samples alpha as 1.0. bgfx BGRA8 samples memory literally, so FFmpeg-produced procedural frames (BGR0, alpha byte = 0) would draw transparent under SRC_ALPHA blending. Force alpha=0xFF only when the texture has no file path, so TGA-loaded X8R8G8B8 textures (scorch marks, decals) keep their real alpha data. +static void ForceOpaqueIfProceduralX8R8G8B8(TextureClass * tex2d, + bgfx::TextureFormat::Enum bgfxFmt, const bgfx::Memory * mem, + unsigned expectedPitch, unsigned numRows) +{ + if (tex2d == nullptr || mem == nullptr) + return; + if (bgfxFmt != bgfx::TextureFormat::BGRA8) + return; + if (tex2d->Get_Texture_Format() != WW3D_FORMAT_X8R8G8B8) + return; + if (!tex2d->Get_Full_Path().Is_Empty()) + return; + uint8_t * px = mem->data; + const unsigned pixelCount = (expectedPitch / 4) * numRows; + for (unsigned i = 0; i < pixelCount; ++i) + px[i * 4 + 3] = 0xff; +} + +} // end anonymous namespace + +// External linkage: called from BgfxBackend.cpp's Set_Texture path. +bgfx::TextureHandle EnsureBgfxTexture(TextureBaseClass * tex) +{ + if (tex == nullptr) + { + return BGFX_INVALID_HANDLE; + } + IDirect3DBaseTexture8 * curD3D = tex->Peek_D3D_Base_Texture(); + + auto it = g_caches.texture.find(tex); + if (it != g_caches.texture.end()) + { + auto d3dIt = g_caches.d3dPtr.find(tex); + bool d3dPtrMatch = (d3dIt != g_caches.d3dPtr.end() && d3dIt->second.ptr == curD3D); + if (d3dPtrMatch) + { + return it->second; + } + // D3D8 texture pointer changed — the TextureClass* address was reused + // for a different texture, OR the engine invalidated via + // Invalidate_Cached_Texture after writing new pixels (e.g. video + // frame, font atlas). Try to UPDATE the existing bgfx handle in + // place (no handle churn) if dimensions and format match. + // Only fall back to destroy+recreate if they differ. + // TheSuperHackers @bugfix bobtista 20/04/2026 Capture the stored + // dimensions BEFORE overwriting the cache entry — the in-place + // update path below checks against them, and the iterator aliases + // the same entry we are about to stomp. + uint16_t cachedW = 0; + uint16_t cachedH = 0; + if (d3dIt != g_caches.d3dPtr.end()) + { + cachedW = d3dIt->second.w; + cachedH = d3dIt->second.h; + } + g_caches.d3dPtr[tex] = { curD3D, 0, 0 }; + if (bgfx::isValid(it->second)) + { + TextureClass * tex2d = tex->As_TextureClass(); + if (tex2d != nullptr && tex->Get_Pool() != TextureBaseClass::POOL_DEFAULT) + { + IDirect3DTexture8 * d3dTex = static_cast(curD3D); + D3DSURFACE_DESC desc; + if (SUCCEEDED(d3dTex->GetLevelDesc(0, &desc))) + { + const bgfx::TextureFormat::Enum bgfxFmt = TranslateWW3DFormat(tex2d->Get_Texture_Format()); + // Check if dimensions match the existing bgfx handle + if (desc.Width == cachedW + && desc.Height == cachedH + && bgfxFmt != bgfx::TextureFormat::Unknown) + { + D3DLOCKED_RECT locked = { 0 }; + if (SUCCEEDED(d3dTex->LockRect(0, &locked, NULL, D3DLOCK_READONLY))) + { + const bool isCompressed = (bgfxFmt >= bgfx::TextureFormat::BC1 && bgfxFmt <= bgfx::TextureFormat::BC7); + const unsigned bpp = isCompressed ? 0 : + (bgfxFmt == bgfx::TextureFormat::BGRA4 || bgfxFmt == bgfx::TextureFormat::R5G6B5 || bgfxFmt == bgfx::TextureFormat::BGR5A1) ? 2 : + (bgfxFmt == bgfx::TextureFormat::A8 || bgfxFmt == bgfx::TextureFormat::R8) ? 1 : 4; + const unsigned expectedPitch = isCompressed ? 0 : desc.Width * bpp; + const unsigned numRows = isCompressed ? ((desc.Height + 3) / 4) : desc.Height; + const unsigned srcPitch = static_cast(locked.Pitch); + const unsigned rowBytes = (expectedPitch > 0) ? expectedPitch : srcPitch; + const unsigned totalBytes = numRows * rowBytes; + const bgfx::Memory * mem = bgfx::alloc(totalBytes); + if (srcPitch == expectedPitch || isCompressed) + std::memcpy(mem->data, locked.pBits, totalBytes); + else + for (unsigned row = 0; row < numRows; ++row) + std::memcpy(mem->data + row * rowBytes, + static_cast(locked.pBits) + row * srcPitch, rowBytes); + d3dTex->UnlockRect(0); + + ForceOpaqueIfProceduralX8R8G8B8(tex2d, bgfxFmt, mem, expectedPitch, numRows); + + bgfx::updateTexture2D(it->second, 0, 0, 0, 0, + static_cast(desc.Width), + static_cast(desc.Height), mem); + // Store dimensions so subsequent invalidations + // can still match the in-place update path. + g_caches.d3dPtr[tex] = { curD3D, + static_cast(desc.Width), + static_cast(desc.Height) }; + return it->second; // Reused handle, no churn + } + } + } + } + // Dimensions or format changed — must destroy and recreate + g_caches.deferredDestroys.push_back(it->second); + } + g_caches.texture.erase(it); + } + else + { + g_caches.d3dPtr[tex] = { curD3D, 0, 0 }; + } + + // Only handle TextureClass (regular 2D) for now. Cube and volume + // textures take a different path and would need their own helpers. + TextureClass * tex2d = tex->As_TextureClass(); + if (tex2d == nullptr) + { + g_caches.texture[tex] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + if (tex->Get_Pool() == TextureBaseClass::POOL_DEFAULT) + { + auto fbIt = g_caches.framebuffer.find(tex); + if (fbIt != g_caches.framebuffer.end()) + { + static bool s_loggedRTTResolve = false; + if (!s_loggedRTTResolve) + { + s_loggedRTTResolve = true; + WWDEBUG_SAY(("[BgfxBackend] RTT RESOLVE: POOL_DEFAULT tex=%p " + "resolved to framebuffer color texture %dx%d", + tex, fbIt->second.width, fbIt->second.height)); + } + return fbIt->second.colorTex; + } + + g_caches.renderTarget[tex] = true; + return BGFX_INVALID_HANDLE; + } + + const bgfx::TextureFormat::Enum bgfxFmt = TranslateWW3DFormat(tex2d->Get_Texture_Format()); + if (bgfxFmt == bgfx::TextureFormat::Unknown) + { + static bool s_loggedUnknownFmt = false; + if (!s_loggedUnknownFmt) + { + s_loggedUnknownFmt = true; + WWDEBUG_SAY(("[BgfxBackend] UNKNOWN texture format: %s ww3dfmt=%d", + tex2d->Get_Full_Path().str(), + static_cast(tex2d->Get_Texture_Format()))); + } + g_caches.texture[tex] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + IDirect3DBaseTexture8 * baseTex = tex->Peek_D3D_Base_Texture(); + if (baseTex == nullptr) + { + static bool s_loggedNullBase = false; + if (!s_loggedNullBase) + { + s_loggedNullBase = true; + WWDEBUG_SAY(("[BgfxBackend] EnsureBgfxTexture: Peek_D3D_Base_Texture returned null for %s", + tex2d->Get_Full_Path().str())); + } + return BGFX_INVALID_HANDLE; + } + + IDirect3DTexture8 * d3dTex = static_cast(baseTex); + + D3DSURFACE_DESC desc; + if (FAILED(d3dTex->GetLevelDesc(0, &desc))) + { + static bool s_loggedGetLevelDesc = false; + if (!s_loggedGetLevelDesc) + { + s_loggedGetLevelDesc = true; + WWDEBUG_SAY(("[BgfxBackend] EnsureBgfxTexture: GetLevelDesc(0) failed for %s", + tex2d->Get_Full_Path().str())); + } + g_caches.texture[tex] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + D3DLOCKED_RECT locked = { 0 }; + HRESULT hr = d3dTex->LockRect(0, &locked, NULL, D3DLOCK_READONLY); + if (FAILED(hr) || locked.pBits == NULL) + { + WWDEBUG_SAY(("[BgfxBackend] EnsureBgfxTexture LockRect failed hr=0x%08x", static_cast(hr))); + g_caches.texture[tex] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + // TheSuperHackers @bugfix bobtista 16/04/2026 Compute tightly-packed row + // pitch that bgfx expects and re-pack if the D3D8 locked pitch differs + // (drivers may add row padding for alignment). bgfx asserts that the + // memory size equals its internal storageSize, so we must match exactly. + const bool isCompressed = + bgfxFmt == bgfx::TextureFormat::BC1 || + bgfxFmt == bgfx::TextureFormat::BC2 || + bgfxFmt == bgfx::TextureFormat::BC3; + + unsigned expectedPitch = 0; + unsigned numRows = 0; + if (isCompressed) + { + const unsigned blockWidth = 4; + const unsigned blockHeight = 4; + const unsigned blockSize = (bgfxFmt == bgfx::TextureFormat::BC1) ? 8 : 16; + expectedPitch = ((desc.Width + blockWidth - 1) / blockWidth) * blockSize; + numRows = (desc.Height + blockHeight - 1) / blockHeight; + } + else + { + unsigned bpp = 0; + switch (bgfxFmt) + { + case bgfx::TextureFormat::BGRA8: bpp = 32; break; + case bgfx::TextureFormat::R5G6B5: bpp = 16; break; + case bgfx::TextureFormat::BGR5A1: bpp = 16; break; + case bgfx::TextureFormat::BGRA4: bpp = 16; break; + case bgfx::TextureFormat::A8: bpp = 8; break; + case bgfx::TextureFormat::R8: bpp = 8; break; + default: bpp = 32; break; + } + expectedPitch = desc.Width * bpp / 8; + numRows = desc.Height; + } + + const unsigned totalBytes = numRows * expectedPitch; + const unsigned srcPitch = static_cast(locked.Pitch); + + const bgfx::Memory * mem = bgfx::alloc(totalBytes); + if (srcPitch == expectedPitch) + { + std::memcpy(mem->data, locked.pBits, totalBytes); + } + else + { + const unsigned copyPitch = (srcPitch < expectedPitch) ? srcPitch : expectedPitch; + const uint8_t * src = static_cast(locked.pBits); + uint8_t * dst = mem->data; + for (unsigned row = 0; row < numRows; ++row) + { + std::memcpy(dst, src, copyPitch); + if (copyPitch < expectedPitch) + { + std::memset(dst + copyPitch, 0, expectedPitch - copyPitch); + } + src += srcPitch; + dst += expectedPitch; + } + } + d3dTex->UnlockRect(0); + + // TheSuperHackers @fix bobtista 20/04/2026 Team-colored textures + // (name prefixed with "##" via W3DAssetManager's Munge_Texture_Name) + // go through Remap_Palette which forces alpha to 0xff on every pixel. + // The original TGA stored its "transparent" regions as pure-black RGB + // with full alpha, relying on a D3D8-era color-key-on-RGB mechanism + // (or implicit alpha-test on luminance) that we haven't reproduced in + // bgfx. Without it, the black background paints over the stone bib + // around sub-faction emblems (the long-standing "black rectangle" + // bug). Emulate color-keying at upload time: for #-prefixed BGRA8 + // textures, scan the pixel data and set alpha=0 on any pixel whose + // RGB is all zero. Scorch marks / shadows use different names so are + // unaffected. + if (!isCompressed && bgfxFmt == bgfx::TextureFormat::BGRA8) + { + TextureClass * t = tex->As_TextureClass(); + const char * texName = t ? t->Get_Full_Path().str() : nullptr; + if (texName != nullptr && texName[0] == '#') + { + uint8_t * pix = mem->data; + const unsigned pixelCount = expectedPitch / 4 * numRows; + for (unsigned i = 0; i < pixelCount; ++i) + { + // BGRA8: byte order B, G, R, A. Transparent if B=G=R=0. + if (pix[i * 4 + 0] == 0 && pix[i * 4 + 1] == 0 && pix[i * 4 + 2] == 0) + { + pix[i * 4 + 3] = 0; + } + } + } + } + + if (!isCompressed) + ForceOpaqueIfProceduralX8R8G8B8(tex2d, bgfxFmt, mem, expectedPitch, numRows); + + // TheSuperHackers @fix bobtista 19/04/2026 Create textures WITHOUT + // initial data so they are mutable. Passing data to createTexture2D + // makes the texture immutable, silently rejecting later updates (font + // atlas glyph additions, dynamic texture changes). Create empty, then + // upload via updateTexture2D. + bgfx::TextureHandle h = bgfx::createTexture2D( + static_cast(desc.Width), + static_cast(desc.Height), + false, 1, + bgfxFmt, + BGFX_TEXTURE_NONE | BGFX_SAMPLER_MIN_ANISOTROPIC | BGFX_SAMPLER_MAG_ANISOTROPIC, + nullptr); + if (bgfx::isValid(h) && mem != nullptr) + { + bgfx::updateTexture2D(h, 0, 0, 0, 0, + static_cast(desc.Width), + static_cast(desc.Height), + mem); + } + + g_caches.texture[tex] = h; + // Record dimensions so future reuse can update in place + g_caches.d3dPtr[tex] = { curD3D, static_cast(desc.Width), static_cast(desc.Height) }; + return h; +} +void BgfxBackend::Invalidate_Cached_Texture(TextureBaseClass * texture) +{ + if (texture == nullptr) + return; + // Set the D3D pointer to nullptr (sentinel) so the next EnsureBgfxTexture + // call detects a "change" and re-uploads pixel data. KEEP the dimensions + // so the in-place update path can check if the bgfx handle is reusable. + auto d3dIt = g_caches.d3dPtr.find(texture); + if (d3dIt != g_caches.d3dPtr.end()) + d3dIt->second.ptr = nullptr; +} + +void BgfxBackend::Release_Cached_Texture(TextureBaseClass * texture) +{ + if (texture == nullptr) + return; + // Called from TextureBaseClass::~TextureBaseClass before the D3D8 + // texture is released. Queue the bgfx handle for deferred destruction + // (in-flight draws may still reference it this frame) and erase the + // cache entries so a later allocation reusing this TextureBaseClass* + // address cannot inherit the stale handle. + auto it = g_caches.texture.find(texture); + if (it != g_caches.texture.end()) + { + if (bgfx::isValid(it->second)) + g_caches.deferredDestroys.push_back(it->second); + g_caches.texture.erase(it); + } + g_caches.d3dPtr.erase(texture); + g_caches.renderTarget.erase(texture); + + // Framebuffer-backed textures (render targets) own a framebuffer whose + // color attachment IS the cached handle above. Destroying the + // framebuffer also destroys its attached textures, so we do that + // immediately and do NOT queue the colorTex for deferred destroy. + auto fbIt = g_caches.framebuffer.find(texture); + if (fbIt != g_caches.framebuffer.end()) + { + if (bgfx::isValid(fbIt->second.fb)) + bgfx::destroy(fbIt->second.fb); + g_caches.framebuffer.erase(fbIt); + } +} +void BgfxBackend::Capture_Shroud_Texture(TextureClass * dst_texture, + const void * pixel_data, + unsigned dst_width, + unsigned dst_height, + unsigned src_width, + unsigned src_height, + unsigned dst_x, + unsigned dst_y, + unsigned pitch, + WW3DFormat format) +{ + if (!g_device.initialized || dst_texture == nullptr || pixel_data == nullptr) + { + return; + } + + const bgfx::TextureFormat::Enum bgfxFmt = TranslateWW3DFormat(format); + if (bgfxFmt == bgfx::TextureFormat::Unknown) + { + return; + } + + const unsigned bpp = (bgfxFmt == bgfx::TextureFormat::BGRA4 || + bgfxFmt == bgfx::TextureFormat::R5G6B5) ? 2 : 4; + + // TheSuperHackers @bugfix bobtista 17/04/2026 invalidate stale cache + // entries when the shroud texture pointer changes (save/load destroys + // and recreates m_pDstTexture). Without this, the old bgfx handle + // stays in g_caches.texture keyed by the freed pointer, and if the + // address is reused, updateTexture2D crashes with mismatched dimensions. + static TextureClass * s_lastShroudDst = nullptr; + static unsigned s_lastShroudW = 0; + static unsigned s_lastShroudH = 0; + if (dst_texture != s_lastShroudDst + || dst_width != s_lastShroudW + || dst_height != s_lastShroudH) + { + if (s_lastShroudDst != nullptr) + { + auto oldIt = g_caches.texture.find(s_lastShroudDst); + if (oldIt != g_caches.texture.end()) + { + if (bgfx::isValid(oldIt->second)) + bgfx::destroy(oldIt->second); + g_caches.texture.erase(oldIt); + } + g_caches.renderTarget.erase(s_lastShroudDst); + } + s_lastShroudDst = dst_texture; + s_lastShroudW = dst_width; + s_lastShroudH = dst_height; + } + + auto it = g_caches.texture.find(dst_texture); + if (it == g_caches.texture.end() || !bgfx::isValid(it->second)) + { + bgfx::TextureHandle h = bgfx::createTexture2D( + static_cast(dst_width), + static_cast(dst_height), + false, 1, + bgfxFmt, + BGFX_TEXTURE_NONE | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + g_caches.texture[dst_texture] = h; + + if (bgfx::isValid(h)) + { + WWDEBUG_SAY(("[BgfxBackend] Shroud texture created: dst=%ux%u src=%ux%u " + "fmt=%d bgfxFmt=%d bpp=%u off=(%u,%u)", + dst_width, dst_height, src_width, src_height, + static_cast(format), static_cast(bgfxFmt), + bpp, dst_x, dst_y)); + } + else + { + WWDEBUG_SAY(("[BgfxBackend] Shroud texture CREATE FAILED: dst=%ux%u fmt=%d", + dst_width, dst_height, static_cast(format))); + } + } + + bgfx::TextureHandle h = g_caches.texture[dst_texture]; + if (!bgfx::isValid(h)) + { + return; + } + + // Build the shroud image into a persistent buffer and use makeRef + // to avoid copying into bgfx's command buffer (which has a 64KB + // default limit and overflows when combined with texture creation + // bursts like the satellite reveal). The persistent buffer stays + // valid until the next frame's update overwrites it. + const unsigned fullSize = dst_width * dst_height * bpp; + if (fullSize == 0 || src_width == 0 || src_height == 0 + || dst_width > 0xFFFF || dst_height > 0xFFFF) + { + return; + } + const bgfx::Memory * mem = bgfx::alloc(fullSize); + std::memset(mem->data, 0xFF, fullSize); + const unsigned rowBytes = src_width * bpp; + for (unsigned row = 0; row < src_height; ++row) + { + const unsigned dstOffset = ((dst_y + row) * dst_width + dst_x) * bpp; + const unsigned srcOffset = row * pitch; + if (dstOffset + rowBytes <= fullSize && srcOffset + rowBytes <= src_height * pitch) + { + std::memcpy(mem->data + dstOffset, static_cast(pixel_data) + srcOffset, rowBytes); + } + } + bgfx::updateTexture2D(h, 0, 0, 0, 0, + static_cast(dst_width), + static_cast(dst_height), + mem, static_cast(dst_width * bpp)); +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt index f72c7fe2caf..fd166a2d4bd 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt @@ -248,6 +248,8 @@ if(GGC_RENDER_BACKEND STREQUAL "bgfx") target_sources(corei_ww3d2 INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/BgfxBackend.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BgfxBackend.h + ${CMAKE_CURRENT_SOURCE_DIR}/BgfxBackendState.h + ${CMAKE_CURRENT_SOURCE_DIR}/BgfxBackendTextures.cpp ) # TheSuperHackers @refactor bobtista 11/04/2026 shader diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp index 2d4a90c32d5..82adccec846 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp @@ -61,17 +61,17 @@ bool DX8Backend::Is_Device_Lost() const return DX8Wrapper::Is_Device_Lost(); } -bool DX8Backend::Has_Stencil() +bool DX8Backend::Has_Stencil() const { return DX8Wrapper::Has_Stencil(); } -WW3DFormat DX8Backend::Get_Back_Buffer_Format() +WW3DFormat DX8Backend::Get_Back_Buffer_Format() const { return DX8Wrapper::getBackBufferFormat(); } -SurfaceClass * DX8Backend::Get_Back_Buffer(unsigned int num) +SurfaceClass * DX8Backend::Get_Back_Buffer(unsigned int num) const { return DX8Wrapper::_Get_DX8_Back_Buffer(num); } @@ -425,7 +425,7 @@ void DX8Backend::Set_Transform(TransformKind transform, const Matrix3D & m) DX8Wrapper::Set_Transform(static_cast(transform), m); } -void DX8Backend::Get_Transform(TransformKind transform, Matrix4x4 & m) +void DX8Backend::Get_Transform(TransformKind transform, Matrix4x4 & m) const { DX8Wrapper::Get_Transform(static_cast(transform), m); } @@ -440,12 +440,12 @@ void DX8Backend::Set_View_Identity() DX8Wrapper::Set_View_Identity(); } -bool DX8Backend::Is_World_Identity() +bool DX8Backend::Is_World_Identity() const { return DX8Wrapper::Is_World_Identity(); } -bool DX8Backend::Is_View_Identity() +bool DX8Backend::Is_View_Identity() const { return DX8Wrapper::Is_View_Identity(); } @@ -554,7 +554,7 @@ void DX8Backend::Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass DX8Wrapper::Set_Render_Target_With_Z(texture, ztexture); } -bool DX8Backend::Is_Render_To_Texture() +bool DX8Backend::Is_Render_To_Texture() const { return DX8Wrapper::Is_Render_To_Texture(); } @@ -564,7 +564,7 @@ void DX8Backend::Set_Shadow_Map(int idx, ZTextureClass * ztex) DX8Wrapper::Set_Shadow_Map(idx, ztex); } -ZTextureClass * DX8Backend::Get_Shadow_Map(int idx) +ZTextureClass * DX8Backend::Get_Shadow_Map(int idx) const { return DX8Wrapper::Get_Shadow_Map(idx); } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h index 66902916c68..22dd2315434 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h @@ -41,9 +41,9 @@ class DX8Backend : public IRenderBackend // -- Device state queries ------------------------------------------------- virtual bool Is_Device_Lost() const; - virtual bool Has_Stencil(); - virtual WW3DFormat Get_Back_Buffer_Format(); - virtual SurfaceClass * Get_Back_Buffer(unsigned int num); + virtual bool Has_Stencil() const; + virtual WW3DFormat Get_Back_Buffer_Format() const; + virtual SurfaceClass * Get_Back_Buffer(unsigned int num) const; virtual void Set_Gamma(float gamma, float bright, float contrast, bool calibrate, bool uselimit); // -- Frame lifecycle ------------------------------------------------------ @@ -112,11 +112,11 @@ class DX8Backend : public IRenderBackend virtual void Set_Transform(TransformKind transform, const Matrix4x4 & m); virtual void Set_Transform(TransformKind transform, const Matrix3D & m); - virtual void Get_Transform(TransformKind transform, Matrix4x4 & m); + virtual void Get_Transform(TransformKind transform, Matrix4x4 & m) const; virtual void Set_World_Identity(); virtual void Set_View_Identity(); - virtual bool Is_World_Identity(); - virtual bool Is_View_Identity(); + virtual bool Is_World_Identity() const; + virtual bool Is_View_Identity() const; virtual void Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & matrix, float znear, float zfar); // -- Lighting and fog ----------------------------------------------------- @@ -156,7 +156,7 @@ class DX8Backend : public IRenderBackend virtual TextureClass * Create_Render_Target(int width, int height, WW3DFormat format); virtual void Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass * ztexture); - virtual bool Is_Render_To_Texture(); + virtual bool Is_Render_To_Texture() const; virtual void Set_Shadow_Map(int idx, ZTextureClass * ztex); - virtual ZTextureClass * Get_Shadow_Map(int idx); + virtual ZTextureClass * Get_Shadow_Map(int idx) const; }; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h index 1b0440df4d5..a7269256d0f 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/IRenderBackend.h @@ -44,13 +44,18 @@ class LightEnvironmentClass; class Matrix4x4; class Matrix3D; class Vector3; -struct _D3DLIGHT8; -typedef struct _D3DLIGHT8 D3DLIGHT8; // ----------------------------------------------------------------------------- // POD types owned by the interface // ----------------------------------------------------------------------------- +// A single light captured for a sorted batch. Backend-neutral subset of D3DLIGHT8: only the fields consumed by the shader (direction vector + diffuse RGB). Direction follows the D3D8 convention — from the light toward the surface. +struct RenderBackendLight +{ + float direction[3]; + float diffuse[3]; +}; + enum TransformKind { // Values chosen so they can be mapped directly to D3DTS_* inside the @@ -222,9 +227,9 @@ class IRenderBackend // ------------------------------------------------------------------------- virtual bool Is_Device_Lost() const = 0; - virtual bool Has_Stencil() = 0; - virtual WW3DFormat Get_Back_Buffer_Format() = 0; - virtual SurfaceClass * Get_Back_Buffer(unsigned int num) = 0; + virtual bool Has_Stencil() const = 0; + virtual WW3DFormat Get_Back_Buffer_Format() const = 0; + virtual SurfaceClass * Get_Back_Buffer(unsigned int num) const = 0; virtual void Set_Gamma(float gamma, float bright, float contrast, bool calibrate, bool uselimit) = 0; // ------------------------------------------------------------------------- @@ -307,7 +312,7 @@ class IRenderBackend virtual void End_Sorted_Batch_Pass() {} virtual void Capture_Sorted_Batch_Transforms(const Matrix4x4 & /*world*/, const Matrix4x4 & /*view*/) {} - virtual void Capture_Sorted_Batch_Light(const D3DLIGHT8 & /*light*/, bool /*enabled*/) {} + virtual void Capture_Sorted_Batch_Light(const RenderBackendLight & /*light*/, bool /*enabled*/) {} // TheSuperHackers @refactor bobtista 11/04/2026 sorted // direct-draw path hook. DX8Wrapper::Draw_Sorting_IB_VB handles @@ -443,11 +448,11 @@ class IRenderBackend virtual void Set_Transform(TransformKind transform, const Matrix4x4 & m) = 0; virtual void Set_Transform(TransformKind transform, const Matrix3D & m) = 0; - virtual void Get_Transform(TransformKind transform, Matrix4x4 & m) = 0; + virtual void Get_Transform(TransformKind transform, Matrix4x4 & m) const = 0; virtual void Set_World_Identity() = 0; virtual void Set_View_Identity() = 0; - virtual bool Is_World_Identity() = 0; - virtual bool Is_View_Identity() = 0; + virtual bool Is_World_Identity() const = 0; + virtual bool Is_View_Identity() const = 0; virtual void Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & matrix, float znear, float zfar) = 0; @@ -467,8 +472,8 @@ class IRenderBackend // and other systems set D3D blend/alpha-test state AFTER ShaderClass // applies. These methods let the bgfx backend capture the overrides. // Empty defaults = forward to DX8Wrapper only (DX8Backend behavior). - virtual void Override_Blend(unsigned srcBlend, unsigned dstBlend) {} - virtual void Override_Alpha_Test(bool enable, unsigned ref, unsigned func) {} + virtual void Override_Blend(BlendFactor srcBlend, BlendFactor dstBlend) {} + virtual void Override_Alpha_Test(bool enable, unsigned ref, CompareFunc func) {} virtual void Override_Alpha_Blend_Enable(bool enable) {} virtual void Override_Texcoord_Index(unsigned stage, unsigned uvIndex) {} virtual void Override_Terrain_Blend(bool enable) {} @@ -604,7 +609,7 @@ class IRenderBackend virtual TextureClass * Create_Render_Target(int width, int height, WW3DFormat format = WW3D_FORMAT_UNKNOWN) = 0; virtual void Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass * ztexture = nullptr) = 0; - virtual bool Is_Render_To_Texture() = 0; + virtual bool Is_Render_To_Texture() const = 0; virtual void Set_Shadow_Map(int idx, ZTextureClass * ztex) = 0; - virtual ZTextureClass * Get_Shadow_Map(int idx) = 0; + virtual ZTextureClass * Get_Shadow_Map(int idx) const = 0; }; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp index dc6f916722d..0f3bbd5d5bd 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp @@ -275,27 +275,38 @@ bool DX8Wrapper::Init(void * hwnd, bool lite) #if defined(GGC_RENDER_BACKEND_BGFX) _GameHwndForBgfx = (HWND)hwnd; { - WNDCLASSEXW wc = {}; - wc.cbSize = sizeof(WNDCLASSEXW); - wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; - wc.lpfnWndProc = DefWindowProcW; - wc.hInstance = GetModuleHandleW(nullptr); - wc.hCursor = LoadCursor(nullptr, IDC_ARROW); - wc.lpszClassName = L"GGC_DX8RefWindow"; - RegisterClassExW(&wc); - - // Create the window hidden — it will be shown later after - // the game's input system is fully initialized. Creating it - // visible during Init steals focus and blocks mouse capture. + HINSTANCE hInst = GetModuleHandleW(nullptr); + // Idempotent registration: RegisterClassExW fails if the class already exists (e.g. Init/Shutdown/Init cycles where Shutdown missed the unregister). GetClassInfoExW probe keeps the path recoverable. + WNDCLASSEXW existing = {}; + existing.cbSize = sizeof(WNDCLASSEXW); + if (!GetClassInfoExW(hInst, L"GGC_DX8RefWindow", &existing)) + { + WNDCLASSEXW wc = {}; + wc.cbSize = sizeof(WNDCLASSEXW); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.lpfnWndProc = DefWindowProcW; + wc.hInstance = hInst; + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.lpszClassName = L"GGC_DX8RefWindow"; + if (RegisterClassExW(&wc) == 0) + { + WWDEBUG_SAY(("[DX8Wrapper] RegisterClassExW(GGC_DX8RefWindow) failed lastError=0x%lx", (unsigned long)GetLastError())); + return false; + } + } + + // Create the window hidden — it will be shown later after the game's input system is fully initialized. Creating it visible during Init steals focus and blocks mouse capture. Initial 800x600 is a placeholder; Resize_And_Position_Window will move and size it to match the main game window once the resolution is known. HWND refWnd = CreateWindowExW( WS_EX_NOACTIVATE, L"GGC_DX8RefWindow", L"DX8 reference", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, - nullptr, nullptr, GetModuleHandleW(nullptr), nullptr); - if (refWnd) + nullptr, nullptr, hInst, nullptr); + if (refWnd == nullptr) { - hwnd = refWnd; + WWDEBUG_SAY(("[DX8Wrapper] CreateWindowExW(GGC_DX8RefWindow) failed lastError=0x%lx", (unsigned long)GetLastError())); + return false; } + hwnd = refWnd; } #endif diff --git a/Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp index 7a4cbf1f999..21777745dc8 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp @@ -220,7 +220,7 @@ void SegLineRendererClass::Render ) { Matrix4x4 view; - DX8Wrapper::Get_Transform(D3DTS_VIEW,view); + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW,view); Matrix4x4 identity(true); g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,identity); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc index f2e8d2b2b45..26ca2a77de3 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc @@ -41,6 +41,16 @@ uniform vec4 u_cloudParams; // xy = scroll, z = stretch, w > 0.5 = modulate clou #define SRC_DIFFUSE 1.0 #define SRC_CURRENT 2.0 +// Shadow map size — must match kShadowMapResolution in BgfxBackend.cpp. +#define SHADOW_MAP_RESOLUTION 4096.0 +// Shadow map depth bias. Terrain uses a larger value to kill self-shadow acne on near-flat slopes; general meshes need a tighter value so curved / thin geometry keeps its contact shadow. +#define SHADOW_BIAS_TERRAIN 0.005 +#define SHADOW_BIAS_GENERAL 0.002 +// BT.601 luminance weights (matches the BGRA bytes of the D3D8 TFACTOR=0x80A5CA8E cascade used by the disabled-button grayscale path). +#define LUMA_WEIGHTS vec3(0.299, 0.587, 0.114) +// Multiplier applied to shadowed pixels. 1.0 = unshadowed, 0.0 = fully black; we darken to 60% for visible but not crushed shadows. +#define SHADOW_DARKNESS 0.6 + vec3 applyColorOp(float op, vec3 arg1, vec3 arg2) { // SELECTARG1 @@ -73,9 +83,25 @@ float applyAlphaOp(float op, float arg1, float arg2) if (op < 5.5) return arg1 + arg2; if (op < 6.5) return arg1 + arg2 - 0.5; if (op < 7.5) return arg1 - arg2; + // BLENDTEXALPHA / BLENDCURALPHA (8, 9) — handled specially by caller. + // ADDSMOOTH — emitted by secAlphaOp under DETAILALPHA_INVSCALE; keep in sync with applyColorOp. + if (op > 9.5 && op < 10.5) return arg1 + arg2 - arg1 * arg2; return arg1; } +// 4-tap PCF sample of the shadow map. Returns a shadow factor in [SHADOW_DARKNESS, 1]. Same logic for terrain and general meshes; only the bias differs. +float sampleShadow(vec2 shadowUV, float refZ, float bias) +{ + float biasedZ = refZ - bias; + float texelSize = 1.0 / SHADOW_MAP_RESOLUTION; + float s0 = shadow2D(s_shadowMap, vec3(shadowUV + vec2(-0.5, -0.5) * texelSize, biasedZ)); + float s1 = shadow2D(s_shadowMap, vec3(shadowUV + vec2( 0.5, -0.5) * texelSize, biasedZ)); + float s2 = shadow2D(s_shadowMap, vec3(shadowUV + vec2(-0.5, 0.5) * texelSize, biasedZ)); + float s3 = shadow2D(s_shadowMap, vec3(shadowUV + vec2( 0.5, 0.5) * texelSize, biasedZ)); + float visibility = (s0 + s1 + s2 + s3) * 0.25; + return mix(SHADOW_DARKNESS, 1.0, visibility); +} + void main() { vec4 diffuse = v_color0; @@ -112,15 +138,7 @@ void main() && tshadowUV.y >= 0.0 && tshadowUV.y <= 1.0 && trefZ >= 0.0 && trefZ <= 1.0) { - float tbiasedZ = trefZ - 0.005; - float texelSize = 1.0 / 4096.0; - float s0 = shadow2D(s_shadowMap, vec3(tshadowUV + vec2(-0.5, -0.5) * texelSize, tbiasedZ)); - float s1 = shadow2D(s_shadowMap, vec3(tshadowUV + vec2( 0.5, -0.5) * texelSize, tbiasedZ)); - float s2 = shadow2D(s_shadowMap, vec3(tshadowUV + vec2(-0.5, 0.5) * texelSize, tbiasedZ)); - float s3 = shadow2D(s_shadowMap, vec3(tshadowUV + vec2( 0.5, 0.5) * texelSize, tbiasedZ)); - float tvisibility = (s0 + s1 + s2 + s3) * 0.25; - float tshadowFactor = mix(0.6, 1.0, tvisibility); - result.rgb *= tshadowFactor; + result.rgb *= sampleShadow(tshadowUV, trefZ, SHADOW_BIAS_TERRAIN); } gl_FragColor = result; @@ -271,15 +289,7 @@ void main() && shadowUV.y >= 0.0 && shadowUV.y <= 1.0 && refZ >= 0.0 && refZ <= 1.0) { - float biasedZ = refZ - 0.002; - float texelSize = 1.0 / 4096.0; - float s0 = shadow2D(s_shadowMap, vec3(shadowUV + vec2(-0.5, -0.5) * texelSize, biasedZ)); - float s1 = shadow2D(s_shadowMap, vec3(shadowUV + vec2( 0.5, -0.5) * texelSize, biasedZ)); - float s2 = shadow2D(s_shadowMap, vec3(shadowUV + vec2(-0.5, 0.5) * texelSize, biasedZ)); - float s3 = shadow2D(s_shadowMap, vec3(shadowUV + vec2( 0.5, 0.5) * texelSize, biasedZ)); - float visibility = (s0 + s1 + s2 + s3) * 0.25; - float shadowFactor = mix(0.6, 1.0, visibility); - current.rgb *= shadowFactor; + current.rgb *= sampleShadow(shadowUV, refZ, SHADOW_BIAS_GENERAL); } // Cloud-shadow modulation. DX8 ST_TERRAIN_BASE_NOISE1 / _NOISE12 @@ -300,7 +310,7 @@ void main() // to dot-product RGB with luminance weights (0.299, 0.587, 0.114). if (u_grayscaleEnable.x > 0.5) { - float luma = dot(current.rgb, vec3(0.299, 0.587, 0.114)); + float luma = dot(current.rgb, LUMA_WEIGHTS); current.rgb = vec3(luma, luma, luma); } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/sortingrenderer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/sortingrenderer.cpp index 6a39411a0c6..9c3cbbab858 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/sortingrenderer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/sortingrenderer.cpp @@ -401,8 +401,17 @@ static void Apply_Render_State(RenderStateStruct& render_state) return; //no point changing lights if they are ignored. //prevLight = render_state.lightsHash; - g_renderBackend->Capture_Sorted_Batch_Light( - render_state.Lights[0], render_state.LightEnable[0]); + { + const D3DLIGHT8 & src = render_state.Lights[0]; + RenderBackendLight rbLight; + rbLight.direction[0] = src.Direction.x; + rbLight.direction[1] = src.Direction.y; + rbLight.direction[2] = src.Direction.z; + rbLight.diffuse[0] = src.Diffuse.r; + rbLight.diffuse[1] = src.Diffuse.g; + rbLight.diffuse[2] = src.Diffuse.b; + g_renderBackend->Capture_Sorted_Batch_Light(rbLight, render_state.LightEnable[0]); + } if (render_state.LightEnable[0]) { DX8Wrapper::Set_DX8_Light(0,&render_state.Lights[0]); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/streakRender.cpp b/Core/Libraries/Source/WWVegas/WW3D2/streakRender.cpp index ef3fefc2c8f..283020ccd20 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/streakRender.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/streakRender.cpp @@ -313,7 +313,7 @@ void StreakRendererClass::RenderStreak ) { Matrix4x4 view; - DX8Wrapper::Get_Transform(D3DTS_VIEW,view); + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW,view); Matrix4x4 identity(true); g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,identity); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp index 67da43d6e6e..77408e7824f 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp @@ -480,7 +480,7 @@ Int W3DProjectedShadowManager::renderProjectedTerrainShadow(W3DProjectedShadow * Int numPolys = (endX - startX)*(endY - startY)*2; //2 triangles per cell - g_renderBackend->Override_Alpha_Test(true, 0, 8); //should reject background pixels + g_renderBackend->Override_Alpha_Test(true, 0, RB_CMP_ALWAYS); //should reject background pixels g_renderBackend->Set_Stencil_Enable(true); g_renderBackend->Set_Stencil_Func(RB_CMP_ALWAYS); g_renderBackend->Set_Stencil_Ref(0x1); @@ -509,7 +509,7 @@ Int W3DProjectedShadowManager::renderProjectedTerrainShadow(W3DProjectedShadow * g_renderBackend->Set_Shadow_Volume_Shader_Active(false); } - g_renderBackend->Override_Alpha_Test(false, 0, 0); //should reject background pixels + g_renderBackend->Override_Alpha_Test(false, 0, RB_CMP_ALWAYS); //disable atest g_renderBackend->Set_Stencil_Enable(false); // m_pDev->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); g_renderBackend->Set_Lighting_Enable(true); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DCustomEdging.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DCustomEdging.cpp index 9dcd32e7250..5604970f10d 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DCustomEdging.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DCustomEdging.cpp @@ -367,7 +367,7 @@ void W3DCustomEdging::drawEdging(WorldHeightMap *pMap, Int minX, Int maxX, Int m g_renderBackend->Set_Texture(1,edgeTex); g_renderBackend->Apply_Render_State_Changes(); - g_renderBackend->Override_Alpha_Test(true, 0x7B, D3DCMP_LESSEQUAL); + g_renderBackend->Override_Alpha_Test(true, 0x7B, RB_CMP_LESS_EQUAL); g_renderBackend->Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); g_renderBackend->Set_Texture(0,edgeTex); @@ -375,7 +375,7 @@ void W3DCustomEdging::drawEdging(WorldHeightMap *pMap, Int minX, Int maxX, Int m // Draw the custom edge. g_renderBackend->Apply_Render_State_Changes(); - g_renderBackend->Override_Alpha_Test(true, 0x84, D3DCMP_GREATEREQUAL); + g_renderBackend->Override_Alpha_Test(true, 0x84, RB_CMP_GREATER_EQUAL); g_renderBackend->Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); #if 0 // Dumps out unmasked data. @@ -403,8 +403,8 @@ void W3DCustomEdging::drawEdging(WorldHeightMap *pMap, Int minX, Int maxX, Int m DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2 ); DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXCOORDINDEX, 1 ); #endif - g_renderBackend->Override_Alpha_Test(true, 0x80, D3DCMP_NOTEQUAL); - g_renderBackend->Override_Blend(D3DBLEND_DESTCOLOR, D3DBLEND_ZERO); + g_renderBackend->Override_Alpha_Test(true, 0x80, RB_CMP_NOT_EQUAL); + g_renderBackend->Override_Blend(RB_BLEND_DEST_COLOR, RB_BLEND_ZERO); g_renderBackend->Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); } if (noiseTexture) { @@ -414,8 +414,8 @@ void W3DCustomEdging::drawEdging(WorldHeightMap *pMap, Int minX, Int maxX, Int m g_renderBackend->Set_Texture(1,edgeTex); g_renderBackend->Apply_Render_State_Changes(); - g_renderBackend->Override_Alpha_Test(true, 0x80, D3DCMP_NOTEQUAL); - g_renderBackend->Override_Blend(D3DBLEND_DESTCOLOR, D3DBLEND_ZERO); + g_renderBackend->Override_Alpha_Test(true, 0x80, RB_CMP_NOT_EQUAL); + g_renderBackend->Override_Blend(RB_BLEND_DEST_COLOR, RB_BLEND_ZERO); g_renderBackend->Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); } } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 33aa48c9577..fee8cf7749b 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -918,14 +918,6 @@ void W3DDisplay::init() WW3D::Set_Collision_Box_Display_Mask(0x00); ///Has_Shader_Pipeline()); - WW3D::Set_Screen_UV_Bias( shaderPipeline ? FALSE : TRUE ); ///< TRUE makes text look good on D3D8 WW3D::Set_Texture_Bitdepth(32); setWindowed( TheGlobalData->m_windowed ); @@ -1031,6 +1023,11 @@ void W3DDisplay::init() return; } + // TheSuperHackers @fix bobtista 21/04/2026 D3D8 half-pixel UV bias causes sub-pixel misalignment on D3D11/bgfx (visible stripes on UI atlases from half-texel sampling offsets). Call this AFTER Set_Render_Device because the render backend is only constructed by Do_Onetime_Device_Dependent_Inits which Set_Render_Device triggers — calling earlier would read g_renderBackend=nullptr and incorrectly enable the bias on bgfx. + const bool shaderPipeline = + (g_renderBackend != nullptr && g_renderBackend->Has_Shader_Pipeline()); + WW3D::Set_Screen_UV_Bias( shaderPipeline ? FALSE : TRUE ); ///< TRUE makes text look good on D3D8 + //Check if level was never set and default to setting most suitable for system. if (TheGameLODManager->getStaticLODLevel() == STATIC_GAME_LOD_UNKNOWN) { From 9862ba8b99ccd930106fecf7691d2e3550d36194 Mon Sep 17 00:00:00 2001 From: bobtista Date: Mon, 1 Jun 2026 11:03:39 +1000 Subject: [PATCH 058/523] fix(dx8): forward Override_* render-state overrides 1:1 to DX8Wrapper --- .../Source/WWVegas/WW3D2/DX8Backend.cpp | 26 +++++++++++++++++++ .../Source/WWVegas/WW3D2/DX8Backend.h | 12 +++++++++ 2 files changed, 38 insertions(+) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp index 82adccec846..cc59bdea9e5 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp @@ -413,6 +413,32 @@ void DX8Backend::Set_Cull_Mode(CullMode mode) DX8Wrapper::Set_DX8_Render_State(D3DRS_CULLMODE, static_cast(mode)); } +// TheSuperHackers @bugfix bobtista 01/06/2026 Forward Override_* state +// overrides 1:1 to the legacy DX8Wrapper render-state calls. See header +// for the failure mode they fix. +void DX8Backend::Override_Blend(BlendFactor srcBlend, BlendFactor dstBlend) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, static_cast(srcBlend)); + DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, static_cast(dstBlend)); +} + +void DX8Backend::Override_Alpha_Test(bool enable, unsigned ref, CompareFunc func) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHATESTENABLE, enable ? TRUE : FALSE); + DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF, ref); + DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC, static_cast(func)); +} + +void DX8Backend::Override_Alpha_Blend_Enable(bool enable) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE, enable ? TRUE : FALSE); +} + +void DX8Backend::Override_Texcoord_Index(unsigned stage, unsigned uvIndex) +{ + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_TEXCOORDINDEX, uvIndex); +} + // -- Transforms -------------------------------------------------------------- void DX8Backend::Set_Transform(TransformKind transform, const Matrix4x4 & m) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h index 22dd2315434..ed07eba9156 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h @@ -108,6 +108,18 @@ class DX8Backend : public IRenderBackend virtual void Set_Texture_Factor(unsigned argb); virtual void Set_Cull_Mode(CullMode mode); + // TheSuperHackers @bugfix bobtista 01/06/2026 Forward Override_* state + // overrides 1:1 to the legacy DX8Wrapper render-state calls so the dx8 + // backend reproduces the pre-refactor rendering. Without these, the + // terrain 2-pass blend, road alpha-test, water destalpha trick, and + // custom-edging passes all execute with stale render state (e.g. terrain + // pass 1 keeps texcoord index 0, ALPHABLENDENABLE off) so terrain tiles + // never write to the framebuffer. + virtual void Override_Blend(BlendFactor srcBlend, BlendFactor dstBlend) override; + virtual void Override_Alpha_Test(bool enable, unsigned ref, CompareFunc func) override; + virtual void Override_Alpha_Blend_Enable(bool enable) override; + virtual void Override_Texcoord_Index(unsigned stage, unsigned uvIndex) override; + // -- Transforms ----------------------------------------------------------- virtual void Set_Transform(TransformKind transform, const Matrix4x4 & m); From 256f6f3d4f9efc7c6d1404d6dc8ae2b1be10d651 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 23 Jun 2026 21:13:26 -0400 Subject: [PATCH 059/523] build: enable Tracy profiler on the macOS bgfx build --- CMakePresets.json | 11 ++++++++++- Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt | 7 +++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CMakePresets.json b/CMakePresets.json index 95f8d6bc709..7a7234edba8 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -190,6 +190,15 @@ "GGC_BGFX_STANDALONE": "ON" } }, + { + "name": "macos-generalsmd-sdl3-bgfx-profile", + "inherits": "macos-generalsmd-sdl3-bgfx", + "displayName": "macOS GeneralsMD SDL3 bgfx Profile (Tracy)", + "cacheVariables": { + "RTS_BUILD_OPTION_PROFILE_TRACY": "ON", + "RTS_CRASHDUMP_ENABLE": "OFF" + } + }, { "name": "mingw-w64-i686", "displayName": "MinGW-w64 32-bit (i686) Release", @@ -518,4 +527,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt index fd166a2d4bd..5a9a4705664 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt @@ -304,6 +304,13 @@ target_link_libraries(corei_ww3d2 INTERFACE # Link the backend's native libraries into whoever consumes corei_ww3d2. if(GGC_RENDER_BACKEND STREQUAL "bgfx") target_link_libraries(corei_ww3d2 INTERFACE bgfx bx bimg) + # TheSuperHackers @perf bobtista 24/06/2026 When the Tracy profiler is built + # in (RTS_BUILD_OPTION_PROFILE_TRACY=ON), turn on bgfx's internal profiler + # callbacks so its submit/encode work can be forwarded into the same Tracy + # timeline (see BgfxLoggingCallback). No effect on default builds. + if(RTS_BUILD_OPTION_PROFILE_TRACY) + target_compile_definitions(bgfx PUBLIC BGFX_CONFIG_PROFILER=1) + endif() elseif(GGC_RENDER_BACKEND STREQUAL "diligent") target_link_libraries(corei_ww3d2 INTERFACE Diligent-GraphicsEngineD3D11-static From c87d84a8d785ef61b937fb3899b302d8fda892a3 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 23 Jun 2026 21:26:34 -0400 Subject: [PATCH 060/523] perf: forward bgfx profiler callbacks into Tracy --- .../Source/WWVegas/WW3D2/BgfxBackend.cpp | 63 ++++++++++++++++++- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp index 522d9eb7224..7a5a1f998a5 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -49,6 +49,11 @@ #include +#include +#if defined(RTS_PROFILE_TRACY) +#include +#endif + // Including the bgfx header here is intentional: it forces a compile-time // dependency on the bgfx headers when GGC_RENDER_BACKEND=bgfx. If bgfx // isn't available the build fails here, which is the right place to @@ -133,9 +138,54 @@ class BgfxLoggingCallback : public bgfx::CallbackI while (len > 0 && (buf[len-1] == '\n' || buf[len-1] == '\r')) { buf[--len] = '\0'; } WWDEBUG_SAY(("[bgfx] %s:%u: %s", filePath ? filePath : "?", line, buf)); } - void profilerBegin(const char *, uint32_t, const char *, uint16_t) override {} - void profilerBeginLiteral(const char *, uint32_t, const char *, uint16_t) override {} - void profilerEnd() override {} + void profilerBegin(const char * name, uint32_t /*abgr*/, const char * filePath, uint16_t line) override + { +#if defined(RTS_PROFILE_TRACY) + if (m_profilerDepth >= kProfilerStackMax) + { + ++m_profilerOverflow; + return; + } + const uint64_t srcloc = ___tracy_alloc_srcloc_name( + line, filePath, std::strlen(filePath), + name, std::strlen(name), + name, std::strlen(name), 0); + m_profilerStack[m_profilerDepth] = ___tracy_emit_zone_begin_alloc(srcloc, 1); + ++m_profilerDepth; +#else + (void)name; (void)filePath; (void)line; +#endif + } + void profilerBeginLiteral(const char * name, uint32_t /*abgr*/, const char * filePath, uint16_t line) override + { +#if defined(RTS_PROFILE_TRACY) + if (m_profilerDepth >= kProfilerStackMax) + { + ++m_profilerOverflow; + return; + } + const struct ___tracy_source_location_data srcloc = { name, name, filePath, line, 0 }; + m_profilerStack[m_profilerDepth] = ___tracy_emit_zone_begin(&srcloc, 1); + ++m_profilerDepth; +#else + (void)name; (void)filePath; (void)line; +#endif + } + void profilerEnd() override + { +#if defined(RTS_PROFILE_TRACY) + if (m_profilerOverflow > 0) + { + --m_profilerOverflow; + return; + } + if (m_profilerDepth > 0) + { + --m_profilerDepth; + ___tracy_emit_zone_end(m_profilerStack[m_profilerDepth]); + } +#endif + } uint32_t cacheReadSize(uint64_t) override { return 0; } bool cacheRead(uint64_t, void *, uint32_t) override { return false; } void cacheWrite(uint64_t, const void *, uint32_t) override {} @@ -144,6 +194,13 @@ class BgfxLoggingCallback : public bgfx::CallbackI void captureBegin(uint32_t, uint32_t, uint32_t, bgfx::TextureFormat::Enum, bool) override {} void captureEnd() override {} void captureFrame(const void *, uint32_t) override {} + +#if defined(RTS_PROFILE_TRACY) + static const int kProfilerStackMax = 64; + TracyCZoneCtx m_profilerStack[kProfilerStackMax]; + int m_profilerDepth; + int m_profilerOverflow; +#endif }; BgfxLoggingCallback g_bgfxCallback; From 90b2c99ac7c5d651365760e486147070d5b7d46f Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 23 Jun 2026 21:37:17 -0400 Subject: [PATCH 061/523] perf: add bgfx render-path Tracy zones and per-frame stats plots --- .../Source/WWVegas/WW3D2/BgfxBackend.cpp | 18 ++++++++++++++++++ .../GameEngine/Source/Common/GameEngine.cpp | 3 +++ .../Source/W3DDevice/GameClient/W3DDisplay.cpp | 11 ++++++++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp index 7a5a1f998a5..6310915806d 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -1819,6 +1819,7 @@ void BgfxBackend::Set_Viewport(const RenderBackendViewport & viewport) void BgfxBackend::Begin_Scene() { + PROFILER_SECTION_NAME("bgfx Begin_Scene"); if (!g_device.initialized) { return; @@ -1925,6 +1926,7 @@ void BgfxBackend::Begin_Scene() void BgfxBackend::End_Scene(bool /*flip_frame*/) { + PROFILER_SECTION_NAME("bgfx End_Scene"); if (!g_device.initialized) { return; @@ -1987,6 +1989,22 @@ void BgfxBackend::End_Scene(bool /*flip_frame*/) bgfx::setViewOrder(kBgfxDebugView, BX_COUNTOF(viewOrder), viewOrder); bgfx::frame(); + // TheSuperHackers @perf bobtista 24/06/2026 Feed per-frame bgfx stats to + // Tracy plots so CPU time, draw count, and GPU time share one timeline. +#if defined(RTS_PROFILE_TRACY) + { + const bgfx::Stats * pstats = bgfx::getStats(); + if (pstats != NULL && pstats->cpuTimerFreq != 0 && pstats->gpuTimerFreq != 0) + { + const double toMsCpu = 1000.0 / double(pstats->cpuTimerFreq); + const double toMsGpu = 1000.0 / double(pstats->gpuTimerFreq); + PROFILER_PLOT("draws", double(pstats->numDraw)); + PROFILER_PLOT("cpu frame ms", double(pstats->cpuTimeEnd - pstats->cpuTimeBegin) * toMsCpu); + PROFILER_PLOT("gpu frame ms", double(pstats->gpuTimeEnd - pstats->gpuTimeBegin) * toMsGpu); + PROFILER_PLOT("transient vb kb", double(pstats->transientVbUsed) / 1024.0); + } + } +#endif // Rotate deferred texture destroy buffers. Current frame's deferred // handles move to "prev" — they'll be destroyed at the NEXT Begin_Scene diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp index 7e6a8408456..2bbcf2e3d18 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp @@ -107,6 +107,7 @@ #include "GameNetwork/GameSpy/GameResultsThread.h" #include "Common/version.h" +#include //------------------------------------------------------------------------------------------------- @@ -920,6 +921,7 @@ void GameEngine::update() USE_PERF_TIMER(GameEngine_update) { { + PROFILER_SECTION_NAME("client update"); // VERIFY CRC needs to be in this code block. Please to not pull TheGameLogic->update() inside this block. VERIFY_CRC @@ -940,6 +942,7 @@ void GameEngine::update() // TheSuperHackers @info Ignores frozen time because the script engine needs updating in the logic update regardless. if (canUpdateGameLogic(FramePacer::IgnoreFrozenTime)) { + PROFILER_SECTION_NAME("logic update"); TheGameLogic->UPDATE(); if (!TheFramePacer->isTimeFrozen()) diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index fee8cf7749b..0334d6230ee 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -39,6 +39,7 @@ static void drawFramerateBar(); #include #include #include +#include // USER INCLUDES ////////////////////////////////////////////////////////////// #include "Common/FramePacer.h" @@ -1907,6 +1908,7 @@ void W3DDisplay::step() //DECLARE_PERF_TIMER(W3DDisplay_draw) void W3DDisplay::draw() { + PROFILER_SECTION; //USE_PERF_TIMER(W3DDisplay_draw) extern HWND ApplicationHWnd; @@ -2056,8 +2058,13 @@ void W3DDisplay::draw() { //Checking if we have the device before updating views because the heightmap crashes otherwise while //trying to refresh the visible terrain geometry. // if(TheGlobalData->m_loadScreenRender != TRUE) + { + PROFILER_SECTION_NAME("update views"); updateViews(); - TheParticleSystemManager->update();//LORENZEN AND WILCZYNSKI MOVED THIS FROM ITS NATIVE POSITION, ABOVE + } + { + PROFILER_SECTION_NAME("particle update"); + TheParticleSystemManager->update();//LORENZEN AND WILCZYNSKI MOVED THIS FROM ITS NATIVE POSITION, ABOVE //FOR THE PURPOSE OF LETTING THE PARTICLE SYSTEM LOOK UP THE RENDER OBJECT"S //TRANSFORM MATRIX, WHILE IT IS STILL VALID (HAVING DONE ITS CLIENT TRANSFORMS //BUT NOT YET RESETTING TOT HE LOGICAL TRANSFORM) @@ -2065,8 +2072,10 @@ void W3DDisplay::draw() //MOVE WITH THE CLIENT TRANSFORMS, NOW. //REVOLUTIONARY! //-LORENZEN + } + PROFILER_SECTION_NAME("render to texture"); if (TheWaterRenderObj && TheGlobalData->m_waterType == 2) TheWaterRenderObj->updateRenderTargetTextures(primaryW3DView->get3DCamera()); //do a render into each texture From 125d99053cf0b7a1853bf207fee336f1fce9e936 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 23 Jun 2026 21:45:28 -0400 Subject: [PATCH 062/523] perf: initialize bgfx Tracy callback counters and document TracyC include --- Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp index 6310915806d..46138c59493 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -50,6 +50,7 @@ #include #include +// TheSuperHackers @perf bobtista 24/06/2026 rts/profile.h pulls in Tracy.hpp (C++ API) but the bgfx profiler callbacks use the Tracy C API. #if defined(RTS_PROFILE_TRACY) #include #endif @@ -122,6 +123,13 @@ namespace class BgfxLoggingCallback : public bgfx::CallbackI { public: +#if defined(RTS_PROFILE_TRACY) + BgfxLoggingCallback() + : m_profilerDepth(0) + , m_profilerOverflow(0) + { + } +#endif ~BgfxLoggingCallback() override {} void fatal(const char * filePath, uint16_t line, bgfx::Fatal::Enum code, const char * str) override From 725f23653c2300f17783be26732fa7805504fdb6 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Mon, 27 Apr 2026 10:13:49 +1000 Subject: [PATCH 063/523] perf(bgfx): Add TSS fast paths and restructure op cascade in fs_uber (cherry picked from commit 05546f94a437e1245d7d9a98f80f5aa4fe2e6c92) --- .../Source/WWVegas/WW3D2/shaders/fs_uber.sc | 201 ++++++++++++------ 1 file changed, 132 insertions(+), 69 deletions(-) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc index 26ca2a77de3..bed95ac0e09 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc @@ -53,39 +53,69 @@ uniform vec4 u_cloudParams; // xy = scroll, z = stretch, w > 0.5 = modulate clou vec3 applyColorOp(float op, vec3 arg1, vec3 arg2) { - // SELECTARG1 - if (op < 1.5) return arg1; - // SELECTARG2 - if (op < 2.5) return arg2; - // MODULATE - if (op < 3.5) return arg1 * arg2; - // MODULATE2X - if (op < 4.5) return min(arg1 * arg2 * 2.0, vec3_splat(1.0)); - // ADD - if (op < 5.5) return arg1 + arg2; - // ADDSIGNED - if (op < 6.5) return arg1 + arg2 - vec3_splat(0.5); - // SUBTRACT - if (op < 7.5) return arg1 - arg2; - // BLENDTEXALPHA — handled specially by caller - // BLENDCURALPHA — handled specially by caller - // ADDSMOOTH - if (op > 9.5 && op < 10.5) return arg1 + arg2 - arg1 * arg2; + // Binary split to reduce worst-case from 7 sequential comparisons to 4. + if (op < 5.5) + { + if (op < 2.5) + { + return (op < 1.5) ? arg1 : arg2; + } + if (op < 3.5) + { + return arg1 * arg2; + } + if (op < 4.5) + { + return min(arg1 * arg2 * 2.0, vec3_splat(1.0)); + } + return arg1 + arg2; + } + // ADDSIGNED(6), SUBTRACT(7), BLENDTEX(8)/BLENDCUR(9) handled by caller, ADDSMOOTH(10) + if (op < 6.5) + { + return arg1 + arg2 - vec3_splat(0.5); + } + if (op < 7.5) + { + return arg1 - arg2; + } + if (op > 9.5) + { + return arg1 + arg2 - arg1 * arg2; + } return arg1; } float applyAlphaOp(float op, float arg1, float arg2) { - if (op < 1.5) return arg1; - if (op < 2.5) return arg2; - if (op < 3.5) return arg1 * arg2; - if (op < 4.5) return min(arg1 * arg2 * 2.0, 1.0); - if (op < 5.5) return arg1 + arg2; - if (op < 6.5) return arg1 + arg2 - 0.5; - if (op < 7.5) return arg1 - arg2; - // BLENDTEXALPHA / BLENDCURALPHA (8, 9) — handled specially by caller. - // ADDSMOOTH — emitted by secAlphaOp under DETAILALPHA_INVSCALE; keep in sync with applyColorOp. - if (op > 9.5 && op < 10.5) return arg1 + arg2 - arg1 * arg2; + if (op < 5.5) + { + if (op < 2.5) + { + return (op < 1.5) ? arg1 : arg2; + } + if (op < 3.5) + { + return arg1 * arg2; + } + if (op < 4.5) + { + return min(arg1 * arg2 * 2.0, 1.0); + } + return arg1 + arg2; + } + if (op < 6.5) + { + return arg1 + arg2 - 0.5; + } + if (op < 7.5) + { + return arg1 - arg2; + } + if (op > 9.5) + { + return arg1 + arg2 - arg1 * arg2; + } return arg1; } @@ -150,66 +180,99 @@ void main() vec4 tex2 = texture2D(s_tex2, v_texcoord0); vec4 tex3 = texture2D(s_tex3, v_texcoord0); - // --- Stage 0: Primary (texture0 vs diffuse) --- + // --- TSS stage evaluation --- + // u_tssOps0 = (priColorOp, priAlphaOp, secColorOp, secAlphaOp) + // u_tssOps1 = (priCArg1Src, priAArg1Src, secCArg1Src, secAArg1Src) float priColorOp = u_tssOps0.x; - float priAlphaOp = u_tssOps0.y; + float secColorOp = u_tssOps0.z; + float secAlphaOp = u_tssOps0.w; float priArg1Src = u_tssOps1.x; - float priAlphaArg1Src = u_tssOps1.y; - // Determine arg1/arg2 for primary stage - vec4 priArg1 = (priArg1Src < 0.5) ? tex0 : diffuse; - vec4 priArg2 = (priArg1Src < 0.5) ? diffuse : tex0; - vec4 priAlphaA1 = (priAlphaArg1Src < 0.5) ? tex0 : diffuse; - vec4 priAlphaA2 = (priAlphaArg1Src < 0.5) ? diffuse : tex0; + vec4 current; - vec3 priColor; - float priAlpha; + // Fast paths for the most common TSS combos. These are uniform branches + // (all fragments in a draw take the same path) so the GPU skips the + // not-taken side entirely. For the ~90% of draws that hit a fast path, + // applyColorOp/applyAlphaOp and the secondary stage are never entered. - if (priColorOp < 0.5) + if (priColorOp > 2.5 && priColorOp < 3.5 + && secColorOp < 0.5 && secAlphaOp < 0.5 + && priArg1Src < 0.5) { - // DISABLE — pass through diffuse (no texture) - priColor = diffuse.rgb; - priAlpha = diffuse.a; + // Fast path: MODULATE primary, DISABLE secondary (~80% of draws). + // tex0 * diffuse for both color and alpha. + current = vec4(tex0.rgb * diffuse.rgb, tex0.a * diffuse.a); } - else + else if (priColorOp > 0.5 && priColorOp < 1.5 + && secColorOp < 0.5 && secAlphaOp < 0.5 + && priArg1Src < 0.5) { - priColor = applyColorOp(priColorOp, priArg1.rgb, priArg2.rgb); - priAlpha = applyAlphaOp(priAlphaOp, priAlphaA1.a, priAlphaA2.a); + // Fast path: SELECTARG1 primary, DISABLE secondary. + // Texture only — shell map, pre-lit terrain, baked-lit textures. + current = tex0; } + else if (priColorOp > 1.5 && priColorOp < 2.5 + && secColorOp < 0.5 && secAlphaOp < 0.5) + { + // Fast path: SELECTARG2 primary, DISABLE secondary. + // Diffuse only — untextured lit meshes. + current = diffuse; + } + else + { + // General TSS path — handles all remaining combinations via + // applyColorOp/applyAlphaOp. This covers detail textures, + // additive blending, bump env map fallback, etc. + float priAlphaOp = u_tssOps0.y; + float priAlphaArg1Src = u_tssOps1.y; - vec4 current = vec4(priColor, priAlpha); - - // --- Stage 1: Secondary/Detail (texture1 vs primary result) --- - float secColorOp = u_tssOps0.z; - float secAlphaOp = u_tssOps0.w; + vec4 priArg1 = (priArg1Src < 0.5) ? tex0 : diffuse; + vec4 priArg2 = (priArg1Src < 0.5) ? diffuse : tex0; + vec4 priAlphaA1 = (priAlphaArg1Src < 0.5) ? tex0 : diffuse; + vec4 priAlphaA2 = (priAlphaArg1Src < 0.5) ? diffuse : tex0; - if (secColorOp > 0.5) - { - vec3 secArg1 = tex1.rgb; - vec3 secArg2 = current.rgb; + vec3 priColor; + float priAlpha; - // Handle BLENDTEXTUREALPHA and BLENDCURRENTALPHA specially - if (secColorOp > 7.5 && secColorOp < 8.5) + if (priColorOp < 0.5) { - // BLENDTEXTUREALPHA: lerp(arg2, arg1, tex1.a) - current.rgb = mix(secArg2, secArg1, tex1.a); + priColor = diffuse.rgb; + priAlpha = diffuse.a; } - else if (secColorOp > 8.5 && secColorOp < 9.5) + else { - // BLENDCURRENTALPHA: lerp(arg2, arg1, current.a) - current.rgb = mix(secArg2, secArg1, current.a); + priColor = applyColorOp(priColorOp, priArg1.rgb, priArg2.rgb); + priAlpha = applyAlphaOp(priAlphaOp, priAlphaA1.a, priAlphaA2.a); } - else + + current = vec4(priColor, priAlpha); + + // Secondary/detail stage + if (secColorOp > 0.5) { - current.rgb = applyColorOp(secColorOp, secArg1, secArg2); + vec3 secArg1 = tex1.rgb; + vec3 secArg2 = current.rgb; + + if (secColorOp > 7.5 && secColorOp < 8.5) + { + current.rgb = mix(secArg2, secArg1, tex1.a); + } + else if (secColorOp > 8.5 && secColorOp < 9.5) + { + current.rgb = mix(secArg2, secArg1, current.a); + } + else + { + current.rgb = applyColorOp(secColorOp, secArg1, secArg2); + } } - } - if (secAlphaOp > 0.5) - { - float secAArg1 = tex1.a; - float secAArg2 = current.a; - current.a = applyAlphaOp(secAlphaOp, secAArg1, secAArg2); + if (secAlphaOp > 0.5) + { + float secAArg1 = tex1.a; + float secAArg2 = current.a; + current.a = applyAlphaOp(secAlphaOp, secAArg1, secAArg2); + } } // --- Stages 2-3: only multiply when stage 1 is active (stages 2-3 From ab6355ec6003e0b57e79195d6f97dd4e1379dd34 Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 28 Apr 2026 10:54:37 +1000 Subject: [PATCH 064/523] perf(bgfx): batch water and sorted effect draws (cherry picked from commit c567a1237552676e73f0597e27cac116d268fca5) --- .../Include/W3DDevice/GameClient/W3DWater.h | 8 + .../W3DDevice/GameClient/W3DWaterTracks.h | 2 + .../W3DDevice/GameClient/Water/W3DWater.cpp | 394 +++++++++++++++++- .../GameClient/Water/W3DWaterTracks.cpp | 74 +++- .../Source/WWVegas/WW3D2/sortingrenderer.cpp | 91 +++- 5 files changed, 537 insertions(+), 32 deletions(-) diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DWater.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DWater.h index b0074fa734a..50fb00602a1 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DWater.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DWater.h @@ -38,6 +38,8 @@ #include "Common/GameType.h" #include "Common/Snapshot.h" +#include + #define INVALID_WATER_HEIGHT 0.0f ///water height guaranteed to be below all terrain. #define NUM_BUMP_FRAMES 32 ///number of animation frames in bump map @@ -219,6 +221,11 @@ class WaterRenderObjClass : public Snapshot, TimeOfDay m_tod; ///